Library / SDK
servicetitan/Stl.Fusion avatar
servicetitan/Stl.Fusion

Fusion tracks which cached values depend on which, and that is the whole distributed story

Build real-time apps (Blazor included) with less than 1% of extra code responsible for real-time updates. Host 10-1000x faster APIs relying on transparent and nearly 100% consistent caching. We call it DREAM, or Distributed REActive Memoization, and it's here to turn real-time on!

1,884 stars106 forksC#MIT

At a glance

What is it?
A .NET library built on DREAM, Distributed REActive Memoization, which memoises method calls, records the dependencies between them and invalidates transitively, including across a WebSocket RPC boundary. The interesting part is not the caching but the third message in the protocol.
Who is it for?
Adopt Fusion if you have a .NET application where a cache, a real time update channel and client state management are three separate problems you currently solve with three separate stacks, and you want one mechanism to unify them. Do not adopt it for a simple CRUD API, because the interception proxy and the dependency graph are machinery you pay for whether or not you need them.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 129 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem list, and what Fusion claims to replace

Fusion is a .NET library that implements DREAM, Distributed REActive Memoization, described as a novel abstraction somewhat similar to MobX or Flux, but designed to deal with an arbitrary large state spanning across your backend microservices, API servers, and reaching even every client of your app.

The README then lists the problems it claims to solve with a single hammer, and the table is the fastest way to understand the scope.

Caching, so you do not need Redis or memcached. Real-time cache invalidation, where the table says there are no good solutions and links to the Martin Fowler article on the two hard things in computer science. Real-time updates, so you do not need SignalR, WebSockets or gRPC. Network chattiness, and offline mode support, both of which the table describes as a fair amount of code. Client-side state management, so you do not need MobX, Flux or Redux, or Recoil. And a single codebase for Blazor WebAssembly, Server, and Hybrid/MAUI, for which the table says there are no good alternatives.

That list is wide enough that the library is a framework rather than a utility, and it is worth testing the claim against your own problems. If you have a Blazor application with a SignalR channel and a Redis cache and some client state management, that is precisely the shape Fusion targets, and the payoff is that those three things are derived from one mechanism rather than three.

The claim about the code is that Fusion does all of that transparently, so Fusion-based code is almost identical to code that does not involve it. The setup is four steps. Implement `IComputeService`, a tagging interface, on your Fusion service so a call intercepting proxy is generated at compile time. Mark methods requiring Fusion behaviour with `[ComputeMethod]` and declare them as `virtual`. Register the service with `serviceCollection.AddFusion().AddService<MyService>()`. Then resolve and use them as usual, passing them as dependencies and calling their methods.

The virtual requirement is not a detail. The proxy that intercepts the call has to override the method, which is why it has to be virtual.

Memoisation that records its own dependencies

The mechanism is what happens when a `[ComputeMethod]` is invoked, and it is described in two steps.

First, when Fusion knows that a value for a given call, with the cache key being the service instance, the method and the arguments, is still consistent, it returns it instantly without letting the method run.

Second, when the value is not cached or is tagged as inconsistent, Fusion lets the method run, but captures the new value's dependencies in process. A dependency is one `[ComputeMethod]` call triggered during the evaluation of another `[ComputeMethod]` call.

That second step is the interesting one. It is what allows Fusion to track which values are expected to change when one of them changes. The README compares it to lot traceability, but implemented for arbitrary functions rather than manufacturing processes, and that analogy is apt because traceability is exactly the same problem in a different domain: a graph of which inputs affect which outputs.

The consequences of the dependency tracking are visible in the invalidation model. `Computed.Invalidate()` allows you to tag cached results as inconsistent with the ground truth, and the invalidation is always transitive. If `GetUserProfile(3)` calls `GetUserAvatar(3:ava1)`, and `GetUserAvatar(3:ava1)` gets invalidated, `GetUserProfile(3)` gets invalidated as well.

The pattern for using it is a using block.

cs
var avatars = await GetUserAvatars(userId);
using (Computed.Invalidate()) {
    _ = userService.GetUser(userId);
    foreach (var avatar in avatars)
        _ = userAvatarService.GetAvatar(userId, avatar.Id);
}

Any `[ComputeMethod]` invoked inside this block does not run normally but invalidates the result of the identical call instead, and such calls complete synchronously and return a completed Task, so you do not need to await them.

The synchronous completion is worth noting, because it is what makes invalidation usable from a command handler without turning every mutation into an async operation. The cost of that convenience is that an invalidated call gives you no fresh value, so the caller has to fetch again.

Computed values are observable and recomputable

To make all of this work, Fusion maintains a dictionary-like structure tracking recent and observed call results. The key is the tuple of service instance, method and call arguments. The value is a `Computed<T>`, which stores the result, the consistency state, which is `Computing`, `Consistent` or `Invalidated`, and the dependent dependency links.

`Computed<T>` instances are nearly immutable: once constructed, they can only transition to the inconsistent state. That is a strong statement about the design, because it means a computed value never changes underneath you. It is invalidated, and then a new one is produced.

You can pull the `Computed<T>` instance backing a given call.

cs
var computed1 = await Computed.Capture(() => GetUserProfile(3));
await computed1.WhenInvalidated();
Assert.IsFalse(computed1.IsConsistent());
var computed2 = await computed1.Recompute();

So any computed value is observable. You can await its invalidation, you can check whether it is still consistent, and you can recompute it.

The `WhenInvalidated` method is the primitive that makes real time work. A component that awaits invalidation of a computed value is a component that updates when the underlying data changes, with no subscription channel, no event bus and no client state store. The dependency graph is the subscription.

The repository structure reflects that this is a library with a large surface. There is a `src/` directory with the implementation, `tests/`, `samples/`, `docs/`, `scripts/`, `build/` and a `Stl.Fusion.sln`. The samples directory has a `HelloCart/` sample, a `MiniRpc/` sample, a `MultiServerRpc/` sample and a `TodoApp/` sample, along with scripts to run the console client and the multi host variant, which tells you the intended progression from a local application to a distributed one.

The third message is what makes the graph distributable

This is the part of the design that is genuinely novel, and it is worth following slowly.

Any computed value can be a replica of a remote computed value that mirrors its state in your local process, so the dependency graph can be distributed. To make that work, Fusion uses its own WebSocket-based RPC protocol, which the README says is quite similar to any other RPC protocol, with three steps.

First, to send a call to a remote peer, the client sends a call message. Second, the peer responds with a call result message. So far there is no difference with any other RPC protocol. Third, and this is the unique step, the peer may later send a message telling the client that the call result it sent earlier was invalidated.

The README is clear that step 3 does not change much in terms of network traffic. It is either zero or one extra message per call, so three messages instead of two in the worst case. But that small addition allows compute service clients to know precisely when a given cached call result becomes inconsistent.

And the presence of step 3 makes a huge difference, because any cached and still consistent result is as good as the data you would get from the remote server. So it is fine to resolve a call that hits such a result locally, with no network round trip at all.

That is the crux. Without invalidation propagation, a client cache is a guess. With it, the client cache is authoritative until told otherwise, and the local resolution is not a compromise but the correct answer.

The service placement is then abstracted away. A compute service client is registered with `fusion.AddClient<TInterface>()` while a local compute service is registered with `fusion.AddServer<TInterface, TService>()`, and the code calling them is identical. You cannot tell from the call site whether `userService` is local or remote, and the README notes that if `GetUserName` is itself a computed method, the computed value backing the `GetUser(id)` call it makes automatically extends the dependency graph for the `GetUserName(id)` call.

The cost of that abstraction is real and the README does not hide it. The placement of a service becomes a deployment decision rather than a code decision, and moving a service to another process is a configuration change rather than a refactor.

The test environment runs four databases at once

The docker-compose file is the clearest statement of the library's testing surface, and it is a large one.

yaml
services:
  redis:
    image: "redis"
    ports:
     - "6379:6379"
  postgres:
    image: "postgres"
    entrypoint: ["docker-entrypoint.sh", "-c", "shared_buffers=256MB", "-c", "max_connections=1000"]
  mariadb:
    image: "mariadb"
  sqlserver:
    image: "mcr.microsoft.com/mssql/server"

Four database engines, Redis for the cache, Postgres with its buffer and connection settings tuned, MariaDB and SQL Server. A library that only touches one ORM would not need this. Testing against four engines is how you find out that a query or a transaction behaves the same everywhere, which matters for a library that claims to sit under your data access layer.

The SQL Server entry has the standard container caveat, that the SA password must be at least eight characters with upper case, lower case, a digit and a symbol, and the ACCEPT_EULA has to be set. Those are the details that make a SQL Server container start.

The repository has scripts for starting and stopping the databases, `Docker-Start-DBs.cmd` and `Docker-Stop-DBs.cmd`, and performance test runners in both a `.cmd` and a `.sh` flavour, which is what you would expect for a library that makes performance claims. There is a `Stl.Fusion.snk` file, so the assemblies are strong named, and a `THIRD-PARTY-NOTICES.TXT` alongside the MIT LICENSE, plus a `SECURITY.md` and a `Packages.props` for central package version management.

The licence is MIT, and the last push to the repository was on 2026-06-16 with the repository not archived. There are no GitHub releases, so the version history lives in the NuGet package and in `version.json` at the root, and the package is published as Stl.Fusion.

Editorial conclusion

Adopt Fusion if you have a .NET application where a cache, a real time update channel and client state management are three separate problems you currently solve with three separate stacks, and you want one mechanism to unify them. Do not adopt it for a simple CRUD API, because the interception proxy and the dependency graph are machinery you pay for whether or not you need them. Verify first that your services can be expressed as compute methods with virtual methods and a compute service interface, that you are prepared to own the invalidation calls that mark a cached value stale, and that the cross process invalidation path works in your deployment, since that is where the design is most ambitious.

Frequently asked questions

What is Fusion and what problem does it solve?

Fusion is a .NET library implementing DREAM, Distributed REActive Memoization, an abstraction similar to MobX or Flux but designed for state spanning microservices, API servers and clients. It claims to replace caching, real time cache invalidation, real time updates, network chattiness, offline mode support, client state management and separate Blazor codebases with one mechanism.

How do I set up a service to work with Fusion?

Implement IComputeService on your service so a call intercepting proxy is generated at compile time, mark the methods that need Fusion behaviour with [ComputeMethod] and declare them as virtual, register the service with serviceCollection.AddFusion().AddService<MyService>(), then use it as a normal dependency.

How does Fusion invalidate a cached value?

Using a Computed.Invalidate() block. Any [ComputeMethod] invoked inside the block does not run normally but invalidates the result of the identical call instead, completing synchronously so the calls do not need to be awaited. Invalidation is always transitive through the dependency graph.

What makes the dependency graph work across processes?

Fusion uses a WebSocket based RPC protocol where the peer may later send an extra message telling the client that a call result it already sent was invalidated. That third message means a client can trust a locally cached result and skip the network round trip, because it knows precisely when the remote value changed.

What databases does the Fusion test suite use?

The docker-compose file runs Redis, Postgres, MariaDB and SQL Server, with a helper script to start and stop them. Testing against several engines is how the library checks that behaviour is consistent across data stores.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. servicetitan/Stl.Fusion on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/servicetitan-stl-fusion.svg)](https://hysenlabs.com/projects/servicetitan-stl-fusion)

Was this useful?