Build real-time Blazor and MAUI apps while writing just 0.1% of the usual real-time update code. Handle 10× more API requests with the ActualLab.Rpc protocol—or 1000× more with Fusion’s transparent and perfectly coherent caching.
177
stars
5,446
commits
C#
primary language
Sep 10, 2026
updated
ActualLab.Fusion acts as method-call middleware, transparently enriching every call to Fusion-enhanced services with caching, dependency tracking, invalidation, RPC, and more — with almost no changes to application code.
You can think of Fusion as make or msbuild, but operating on functions and their outputs instead of source files and build artifacts. Like MSBuild, Fusion uses lazy computation:
Fusion solves a set of infamously hard problems with a 🦄 single abstraction:
| Problem | Fusion's Answer | So you don't need... |
|---|---|---|
| ⚡ Caching | In-memory memoization by call arguments | Redis, memcached, ... |
| 🔄 Cache invalidation | Automatic dependency tracking + cascading invalidation | Manual tracking – an infamously hard problem |
| 🪄 Real-time updates | Fusion client automatically propagates server-side cache invalidation back to the client making it aware of state changes on the server side | SignalR, WebSockets, custom pub/sub, ... |
| ✈️ Offline operation | Client-side persistent cache support (IndexedDB, SQLite) is integrated right into the Fusion RPC client, so you get offline mode for free! | Service workers, sync logic, conflict resolution, ... |
| 🤬 Network chattiness | Persistent cache enables speculative execution on the client side, allowing ActualLab.Rpc to batch hundreds of RPC calls into a single transmission frame | Request batching, debouncing, manual optimization, ... |
| 📡 Network traffic | ActualLab.Rpc is 2-7x faster than gRPC and SignalR even in its "raw" mode; Fusion integration, if enabled, turns it into an efficiency beast by adding "cache match" responses | Protocol tuning, custom serialization, ... |
| 🧩 Client-side state management | Same abstractions everywhere: Computed<T>, MutableState<T>, and compute methods | MobX, Flux/Redux, Recoil, ... |
| 💰 Single codebase | Single codebase for Blazor Server, WebAssembly, and MAUI (iOS, Android, Windows, macOS, and more) | Platform-specific code, multiple implementations, ... |
The best part: you get all of this without turning your code into a mess. You can think of Fusion as a call middleware or a decorator. That's why Fusion-based code looks as if there is no Fusion at all! So you can focus on building your app and ship faster — and save yourself from dealing with a 2–3× larger codebase and a plethora of "why is it stale?" bugs, which are among the hardest to debug.
Documentation is the best place to start from.
If you prefer video, check out:
git clone git@github.com:ActualLab/Fusion.Samples.git Blazor Sample ·
TodoApp Sample ·
TownHall — a live audience Q&A app
("Slido-lite") in its own repository ·
Board Games — a standalone Fusion app
(real-time multiplayer board games) in its own repository.
Below is Fusion+Blazor Sample delivering real-time updates to 3 browser windows:

The sample supports both Blazor Server and Blazor WebAssembly hosting modes. And even if you use different modes in different windows, Fusion still keeps in sync literally every bit of a shared state there, including the sign-in state:

Yes, it's incredibly fast. Here is an RPC call duration distribution for one of the most frequent calls on Voxt.ai:

IChats.GetTile reads a small "chat tile" – typically 5 entries pinned to a specific ID range, so it can be efficiently cached. And even for these calls the typical response time is barely measurable: every X-axis mark is 10x larger than the previous one, so the highest peak you see is at 0.03ms!
The next bump at ~4-5ms is when the service actually goes to the DB – i.e., it's the time you'd expect to see without Fusion. The load would be much higher though, because the calls you see on this chart are only the calls that "made it" to the server – in other words, they weren't eliminated by the client and its Fusion services.
Our benchmarks show Fusion delivering over 500 million calls per second on a consumer CPU (AMD Ryzen 9 9950X3D) with its transparent caching:
| Scenario | Without Fusion | With Fusion | Speedup |
|---|---|---|---|
| Local DAL, almost no writes (peak performance) | 38.61K calls/s | 533.85M calls/s | >13,000x |
| Local repo-like service, non-stop writes | 171.05K calls/s | 344.98M calls/s | ~2,017x |
| Remote repo-like service, non-stop writes | 102.82K calls/s (REST) | 230.16M calls/s | ~2,239x |
These aren't typos – Fusion makes your services thousands of times faster by eliminating redundant computation, RPC, and database access.
Note that these benchmarks test Fusion method calls with no dependency chains. Real-life Fusion-based API services typically call other compute services, forming deep dependency graphs. Each layer multiplies the savings (i.e. when something is recomputed, it's typically recomputed just partially), so real-world speedups are often even higher than what you see here.
ActualLab.Rpc is an extendable RPC protocol powering Fusion's distributed features. It isn't "coupled" to Fusion, so you can use it independently. It outperforms all major alternatives on plain RPC tests, especially on call tests and small-item streaming tests:
| Framework | Calls/s | Streaming |
|---|---|---|
| ActualLab.Rpc | 9.91M | 99.96M items/s |
| SignalR | 4.85M | 17.97M items/s |
| gRPC | 1.28M | 43.78M items/s |
So it's significantly faster than gRPC and SignalR, both for calls and for streaming.
msbuild, but for your method call results: what's computed and consistent is never recomputed.ActualLab.Interception library for method interception. Unlike Castle.DynamicProxy and similar libraries that box arguments and allocate heavily, our interceptors require just 1 allocation per call with zero boxing – making them the fastest on .NET (our microbenchmarks show them ~5-7x faster per call than Castle DynamicProxy).ActualLab.Rpc uses the fastest serializers available on .NET – MemoryPack by default (it doesn't require runtime IL Emit), though you can also use MessagePack (it's slightly faster, but requires IL Emit) or anything else you prefer.Yes. Fusion does something similar to what any MMORPG game engine does: even though the complete game state is huge, it's still possible to run the game in real time for 1M+ players, because every player observes a tiny fraction of a complete game state, and thus all you need is to ensure the observed part of the state fits in RAM.
And that's exactly what Fusion does:
To use Fusion, you need to:
ActualLab.Fusion NuGet packageIComputeService (a tagging interface) on your service[ComputeMethod] and declare them as virtualservices.AddFusion().AddService<MyService>()A typical Compute Service looks as follows:
public class ExampleService : IComputeService
{
[ComputeMethod]
public virtual async Task<string> GetValue(string key)
{
// This method reads the data from non-Fusion "sources",
// so it requires invalidation on write (see SetValue)
return await File.ReadAllTextAsync(_prefix + key);
}
[ComputeMethod]
public virtual async Task<string> GetPair(string key1, string key2)
{
// This method uses only other [ComputeMethod]-s or static data,
// thus it doesn't require invalidation on write
var v1 = await GetValue(key1);
var v2 = await GetValue(key2);
return $"{v1}, {v2}";
}
public async Task SetValue(string key, string value)
{
// This method changes the data read by GetValue and GetPair,
// but since GetPair uses GetValue, it will be invalidated
// automatically once we invalidate GetValue.
await File.WriteAllTextAsync(_prefix + key, value);
using (Invalidation.Begin()) {
// This is how you invalidate what's changed by this method.
// Call arguments matter: you invalidate only a result of a
// call with matching arguments rather than every GetValue
// call result!
_ = GetValue(key);
}
}
}
[ComputeMethod] indicates that every time you call this method, its result is "backed" by a Computed Value, and thus it captures dependencies when it runs and instantly returns the result if the current computed value is still consistent.
Compute services are registered similarly to singletons:
var services = new ServiceCollection();
var fusion = services.AddFusion(); // It's ok to call it many times
// ~ Like service.AddSingleton<[TService, ]TImplementation>()
fusion.AddService<ExampleService>();
Check out CounterService from HelloBlazorServer sample to see the actual code of compute service.
Now, I guess you're curious how the UI code looks with Fusion. You'll be surprised, but it's as simple as it could be:
// MomentsAgoBadge.razor
@inherits ComputedStateComponent<string>
@inject IFusionTime _fusionTime
<span>@State.Value</span>
@code {
[Parameter]
public DateTime Value { get; set; }
protected override Task<string> ComputeState()
=> _fusionTime.GetMomentsAgo(Value);
}
MomentsAgoBadge is a Blazor component that displays an
"N [seconds/minutes/...] ago" string. The code above is almost identical to its
actual code,
which is a bit more complex due to null handling.
You see it uses IFusionTime – one of the built-in compute services that provides GetUtcNow and GetMomentsAgo methods. As you might guess, the results of these methods are invalidated automatically; check out FusionTime service to see how it works.
But what's important here is that MomentsAgoBadge is inherited from
ComputedStateComponent –
an abstract type which provides ComputeState method. As you might guess, this method behaves like a Compute Method.
ComputedStateComponent<T> exposes State property (of ComputedState<T> type),
which allows you to get the most recent output of ComputeState() via its
Value property. "State" is another key Fusion abstraction – it implements a "wait for invalidation and recompute" loop
similar to this one:
var computed = await Computed.Capture(_ => service.Method(...));
while (true) {
await computed.WhenInvalidated();
computed = await computed.Update();
}
The only difference is that it does this in a more robust way - in particular, it allows you to control the delays between the invalidation and the update, access the most recent non-error value, etc.
Finally, ComputedStateComponent automatically calls StateHasChanged()
once its State gets updated to make sure the new value is displayed.
So if you use Fusion, you don't need to code any reactions in the UI. Reactions (i.e. partial updates and re-renders) happen automatically due to dependency chains that connect your UI components with the data providers they use, which in turn are connected to data providers they use, and so on - till the very basic "ingredient providers", i.e. compute methods that are invalidated on changes.
If you want to see a few more examples of similarly simple UI components, check out:
Real-time typically implies you use events to deliver change notifications to every client whose state might be impacted by a change, so you have to:
And Fusion solves all these problems using a single abstraction that allows it to identify and track data dependencies automatically.
Fusion allows you to create truly independent UI components. You can embed them in any part of the UI without any need to worry about how they'll interact with each other.
This makes Fusion a perfect fit for micro-frontends on Blazor: the ability to create loosely coupled UI components is paramount there.
Besides that, if your invalidation logic is correct, Fusion guarantees that your UI state is eventually consistent.
You might think all of this works only in Blazor Server mode. But no, all these UI components work in Blazor WebAssembly mode as well, which is another unique feature Fusion provides. Any Compute Service can be substituted with a Compute Service Client, which doesn't simply proxy the calls, but also completely eliminates the chattiness you'd expect from a regular client-side proxy.
4,889 commits
305 commits
163 commits
49 commits
C#
83.9%
TypeScript
15.9%
Build real-time Blazor and MAUI apps while writing just 0.1% of the usual real-time update code. Handle 10× more API requests with the ActualLab.Rpc protocol—or 1000× more with Fusion’s transparent and perfectly coherent caching.
177
stars
5,446
commits
C#
primary language
Sep 10, 2026
updated
ActualLab.Fusion acts as method-call middleware, transparently enriching every call to Fusion-enhanced services with caching, dependency tracking, invalidation, RPC, and more — with almost no changes to application code.
You can think of Fusion as make or msbuild, but operating on functions and their outputs instead of source files and build artifacts. Like MSBuild, Fusion uses lazy computation:
Fusion solves a set of infamously hard problems with a 🦄 single abstraction:
| Problem | Fusion's Answer | So you don't need... |
|---|---|---|
| ⚡ Caching | In-memory memoization by call arguments | Redis, memcached, ... |
| 🔄 Cache invalidation | Automatic dependency tracking + cascading invalidation | Manual tracking – an infamously hard problem |
| 🪄 Real-time updates | Fusion client automatically propagates server-side cache invalidation back to the client making it aware of state changes on the server side | SignalR, WebSockets, custom pub/sub, ... |
| ✈️ Offline operation | Client-side persistent cache support (IndexedDB, SQLite) is integrated right into the Fusion RPC client, so you get offline mode for free! | Service workers, sync logic, conflict resolution, ... |
| 🤬 Network chattiness | Persistent cache enables speculative execution on the client side, allowing ActualLab.Rpc to batch hundreds of RPC calls into a single transmission frame | Request batching, debouncing, manual optimization, ... |
| 📡 Network traffic | ActualLab.Rpc is 2-7x faster than gRPC and SignalR even in its "raw" mode; Fusion integration, if enabled, turns it into an efficiency beast by adding "cache match" responses | Protocol tuning, custom serialization, ... |
| 🧩 Client-side state management | Same abstractions everywhere: Computed<T>, MutableState<T>, and compute methods | MobX, Flux/Redux, Recoil, ... |
| 💰 Single codebase | Single codebase for Blazor Server, WebAssembly, and MAUI (iOS, Android, Windows, macOS, and more) | Platform-specific code, multiple implementations, ... |
The best part: you get all of this without turning your code into a mess. You can think of Fusion as a call middleware or a decorator. That's why Fusion-based code looks as if there is no Fusion at all! So you can focus on building your app and ship faster — and save yourself from dealing with a 2–3× larger codebase and a plethora of "why is it stale?" bugs, which are among the hardest to debug.
Documentation is the best place to start from.
If you prefer video, check out:
git clone git@github.com:ActualLab/Fusion.Samples.git Blazor Sample ·
TodoApp Sample ·
TownHall — a live audience Q&A app
("Slido-lite") in its own repository ·
Board Games — a standalone Fusion app
(real-time multiplayer board games) in its own repository.
Below is Fusion+Blazor Sample delivering real-time updates to 3 browser windows:

The sample supports both Blazor Server and Blazor WebAssembly hosting modes. And even if you use different modes in different windows, Fusion still keeps in sync literally every bit of a shared state there, including the sign-in state:

Yes, it's incredibly fast. Here is an RPC call duration distribution for one of the most frequent calls on Voxt.ai:

IChats.GetTile reads a small "chat tile" – typically 5 entries pinned to a specific ID range, so it can be efficiently cached. And even for these calls the typical response time is barely measurable: every X-axis mark is 10x larger than the previous one, so the highest peak you see is at 0.03ms!
The next bump at ~4-5ms is when the service actually goes to the DB – i.e., it's the time you'd expect to see without Fusion. The load would be much higher though, because the calls you see on this chart are only the calls that "made it" to the server – in other words, they weren't eliminated by the client and its Fusion services.
Our benchmarks show Fusion delivering over 500 million calls per second on a consumer CPU (AMD Ryzen 9 9950X3D) with its transparent caching:
| Scenario | Without Fusion | With Fusion | Speedup |
|---|---|---|---|
| Local DAL, almost no writes (peak performance) | 38.61K calls/s | 533.85M calls/s | >13,000x |
| Local repo-like service, non-stop writes | 171.05K calls/s | 344.98M calls/s | ~2,017x |
| Remote repo-like service, non-stop writes | 102.82K calls/s (REST) | 230.16M calls/s | ~2,239x |
These aren't typos – Fusion makes your services thousands of times faster by eliminating redundant computation, RPC, and database access.
Note that these benchmarks test Fusion method calls with no dependency chains. Real-life Fusion-based API services typically call other compute services, forming deep dependency graphs. Each layer multiplies the savings (i.e. when something is recomputed, it's typically recomputed just partially), so real-world speedups are often even higher than what you see here.
ActualLab.Rpc is an extendable RPC protocol powering Fusion's distributed features. It isn't "coupled" to Fusion, so you can use it independently. It outperforms all major alternatives on plain RPC tests, especially on call tests and small-item streaming tests:
| Framework | Calls/s | Streaming |
|---|---|---|
| ActualLab.Rpc | 9.91M | 99.96M items/s |
| SignalR | 4.85M | 17.97M items/s |
| gRPC | 1.28M | 43.78M items/s |
So it's significantly faster than gRPC and SignalR, both for calls and for streaming.
msbuild, but for your method call results: what's computed and consistent is never recomputed.ActualLab.Interception library for method interception. Unlike Castle.DynamicProxy and similar libraries that box arguments and allocate heavily, our interceptors require just 1 allocation per call with zero boxing – making them the fastest on .NET (our microbenchmarks show them ~5-7x faster per call than Castle DynamicProxy).ActualLab.Rpc uses the fastest serializers available on .NET – MemoryPack by default (it doesn't require runtime IL Emit), though you can also use MessagePack (it's slightly faster, but requires IL Emit) or anything else you prefer.Yes. Fusion does something similar to what any MMORPG game engine does: even though the complete game state is huge, it's still possible to run the game in real time for 1M+ players, because every player observes a tiny fraction of a complete game state, and thus all you need is to ensure the observed part of the state fits in RAM.
And that's exactly what Fusion does:
To use Fusion, you need to:
ActualLab.Fusion NuGet packageIComputeService (a tagging interface) on your service[ComputeMethod] and declare them as virtualservices.AddFusion().AddService<MyService>()A typical Compute Service looks as follows:
public class ExampleService : IComputeService
{
[ComputeMethod]
public virtual async Task<string> GetValue(string key)
{
// This method reads the data from non-Fusion "sources",
// so it requires invalidation on write (see SetValue)
return await File.ReadAllTextAsync(_prefix + key);
}
[ComputeMethod]
public virtual async Task<string> GetPair(string key1, string key2)
{
// This method uses only other [ComputeMethod]-s or static data,
// thus it doesn't require invalidation on write
var v1 = await GetValue(key1);
var v2 = await GetValue(key2);
return $"{v1}, {v2}";
}
public async Task SetValue(string key, string value)
{
// This method changes the data read by GetValue and GetPair,
// but since GetPair uses GetValue, it will be invalidated
// automatically once we invalidate GetValue.
await File.WriteAllTextAsync(_prefix + key, value);
using (Invalidation.Begin()) {
// This is how you invalidate what's changed by this method.
// Call arguments matter: you invalidate only a result of a
// call with matching arguments rather than every GetValue
// call result!
_ = GetValue(key);
}
}
}
[ComputeMethod] indicates that every time you call this method, its result is "backed" by a Computed Value, and thus it captures dependencies when it runs and instantly returns the result if the current computed value is still consistent.
Compute services are registered similarly to singletons:
var services = new ServiceCollection();
var fusion = services.AddFusion(); // It's ok to call it many times
// ~ Like service.AddSingleton<[TService, ]TImplementation>()
fusion.AddService<ExampleService>();
Check out CounterService from HelloBlazorServer sample to see the actual code of compute service.
Now, I guess you're curious how the UI code looks with Fusion. You'll be surprised, but it's as simple as it could be:
// MomentsAgoBadge.razor
@inherits ComputedStateComponent<string>
@inject IFusionTime _fusionTime
<span>@State.Value</span>
@code {
[Parameter]
public DateTime Value { get; set; }
protected override Task<string> ComputeState()
=> _fusionTime.GetMomentsAgo(Value);
}
MomentsAgoBadge is a Blazor component that displays an
"N [seconds/minutes/...] ago" string. The code above is almost identical to its
actual code,
which is a bit more complex due to null handling.
You see it uses IFusionTime – one of the built-in compute services that provides GetUtcNow and GetMomentsAgo methods. As you might guess, the results of these methods are invalidated automatically; check out FusionTime service to see how it works.
But what's important here is that MomentsAgoBadge is inherited from
ComputedStateComponent –
an abstract type which provides ComputeState method. As you might guess, this method behaves like a Compute Method.
ComputedStateComponent<T> exposes State property (of ComputedState<T> type),
which allows you to get the most recent output of ComputeState() via its
Value property. "State" is another key Fusion abstraction – it implements a "wait for invalidation and recompute" loop
similar to this one:
var computed = await Computed.Capture(_ => service.Method(...));
while (true) {
await computed.WhenInvalidated();
computed = await computed.Update();
}
The only difference is that it does this in a more robust way - in particular, it allows you to control the delays between the invalidation and the update, access the most recent non-error value, etc.
Finally, ComputedStateComponent automatically calls StateHasChanged()
once its State gets updated to make sure the new value is displayed.
So if you use Fusion, you don't need to code any reactions in the UI. Reactions (i.e. partial updates and re-renders) happen automatically due to dependency chains that connect your UI components with the data providers they use, which in turn are connected to data providers they use, and so on - till the very basic "ingredient providers", i.e. compute methods that are invalidated on changes.
If you want to see a few more examples of similarly simple UI components, check out:
Real-time typically implies you use events to deliver change notifications to every client whose state might be impacted by a change, so you have to:
And Fusion solves all these problems using a single abstraction that allows it to identify and track data dependencies automatically.
Fusion allows you to create truly independent UI components. You can embed them in any part of the UI without any need to worry about how they'll interact with each other.
This makes Fusion a perfect fit for micro-frontends on Blazor: the ability to create loosely coupled UI components is paramount there.
Besides that, if your invalidation logic is correct, Fusion guarantees that your UI state is eventually consistent.
You might think all of this works only in Blazor Server mode. But no, all these UI components work in Blazor WebAssembly mode as well, which is another unique feature Fusion provides. Any Compute Service can be substituted with a Compute Service Client, which doesn't simply proxy the calls, but also completely eliminates the chattiness you'd expect from a regular client-side proxy.
4,889 commits
305 commits
163 commits
49 commits
C#
83.9%
TypeScript
15.9%