Real-Time Magic
Automatic state sync across your server cluster and every client connected to it. No SignalR hubs. No event handlers. No manual pub/sub.
Add real-time updates and caching to any .NET app with almost no code changes. Get 10βΆ scale headroom. Production-proven. MIT license.
Building real-time apps is hard. Traditional approaches force you into painful trade-offs:
But if you think about it, caching and real-time updates are facets of the same problem. Both require knowing when something changes and who cares. Yet we treat them as separate concerns with separate infrastructure.
Fusion solves all of this:
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.
Fusion doesn't just add real-timeβit makes your app thousands of times faster.
| Scenario | Without Fusion | With Fusion | Speedup |
|---|---|---|---|
| Local service, minimal writes | 38.6K calls/s | 313.8M calls/s | 8,127x |
| Local service, continuous writes | 135.4K calls/s | 266.6M calls/s | 1,968x |
| Remote service, continuous writes | 100.7K calls/s (REST) | 226.7M calls/s | 2,251x |
Benchmarks on AMD Ryzen 9 9950X3D. See full benchmark details.
| Framework | RPC Calls/sec | Streaming Items/sec |
|---|---|---|
| ActualLab.Rpc | 9.33M | 101.17M |
| SignalR | 5.30M | 17.17M |
| gRPC | 1.11M | 39.59M |
| Speedup | 1.8..8.4x | 2.6..5.9x |
8.4x faster than gRPC for calls. 5.9x faster than SignalR for streaming.
Think of Fusion as MSBuild for data processed by your backend, API, and even client-side UI:
GetUser(userId)When GetThumbnail(imgId, 64) is invalidated, the invalidation cascades immediately:
Next request for GetUserProfile(3) triggers partial recomputation β only the affected parts recompute, while GetUser(3) is served from cache:
The invalidation is always immediate and cascading: when you invalidate a given call, its dependency sub-graph is also invalidated, including remote dependencies.
But invalidation doesn't imply immediate recomputation: the recomputation typically happens later, when the call is repeated, typically in a UI component. But old cached values wrapped into Computed<T> instances remain accessible indefinitely, so UI can keep displaying them as long as it needs to (while updates are in progress or even later).
Fusion tracks one Computed<T> per each (service, method, arguments) combination in a WeakMap-style structure β invalidation evicts the entry, so the next call recomputes it, while unrelated entries stay cached:
The dependency graph updates automatically as your methods call each other or when invalidation occurs, so typically you don't even need to know it exists.
This is exactly how incremental builds work: you mark targets as dirty by removing them, but they only rebuild when you run the build, and every artifact that's still consistent is reused.
The same dependency graph extends across network boundaries β invalidation cascades from backend to client, then recomputation flows back from client to backend, reusing every node that's still consistent:
When multiple clients use multiple servers, the reuse rate of already-computed results is extremely high. Typically only the first client requesting data after an invalidation triggers actual computation β nearly everyone else gets a cache hit:
In production, the dependency graph is orders of magnitude larger. Here are real numbers from Voxt β a Fusion-based app:
| Metric | Value |
|---|---|
| Computed instances per client | 5β10K |
| Remote dependencies per client | 1β2K |
| Time from app start to first contact list render | 0.5s |
Having thousands of remote dependencies doesn't slow down startup β they resolve from the local cache first and update as soon as RPC updates arrive.
A typical backend invalidation touches just a few nodes on each client β only a tiny fraction of the graph is ever recomputed per change.
A Fusion service looks almost identical to a regular service:
public class UserService(IServiceProvider services) : DbServiceBase<AppDbContext>(services),
IComputeService // A tagging interface that enables [ComputeMethod] and other Fusion features
{
[ComputeMethod] // Also has to be virtual
public virtual async Task<User?> GetUser(long id, CancellationToken cancellationToken = default)
{
// Fusion services are thread-safe by default, but DbContext is not,
// so we can't use shared DbContext instance here.
// That's why we use DbHub, which provides DbContext-s on demand and pools them.
await using var dbContext = await DbHub.CreateDbContext(cancellationToken);
return await dbContext.Users.FindAsync([id], cancellationToken);
}
[ComputeMethod] // Also has to be virtual
public virtual async Task<UserProfile> GetUserProfile(long id, CancellationToken cancellationToken = default)
{
// Calls other compute methods - dependencies tracked automatically
var user = await GetUser(id, cancellationToken);
var avatar = await GetUserAvatar(id, cancellationToken);
return new UserProfile(user, avatar);
}
// Regular method
public async Task UpdateUser(long id, User update, CancellationToken cancellationToken = default)
{
await using var dbContext = await DbHub.CreateDbContext(readWrite: true, cancellationToken);
var user = await dbContext.Users.FindAsync([id], cancellationToken);
user!.ApplyUpdate(update);
await dbContext.SaveChangesAsync(cancellationToken);
using (Invalidation.Begin()) { // Invalidation block
_ = GetUser(id, default); // Invalidate GetUser(id), this call completes synchronously w/o actual evaluation
}
}
}That's it. No event buses. No cache managers. No subscription tracking.
Fusion provides ComputedStateComponent<T>, which has a State property β a ComputedState<T> instance that holds the latest result of the ComputeState call. Any ComputedState<T> is essentially a compute method + update loop, so it invalidates when any dependency of its last computation gets invalidated, and recomputes after a short delay (configurable via GetStateOptions).
When State gets recomputed, StateHasChanged() is called and the component re-renders.
@inherits ComputedStateComponent<UserProfile>
@inject IUserService UserService
<div class="profile">
<h1>@State.Value.Name</h1>
<p>@State.Value.Bio</p>
<span>Posts: @State.Value.PostCount</span>
</div>
@code {
[Parameter] public long UserId { get; set; }
protected override Task<UserProfile> ComputeState()
=> UserService.GetUserProfile(UserId);
}Skip building real-time and caching infrastructure. Add [ComputeMethod] to your existing services and get both for free.
Same service code works for a single Blazor app or a distributed cluster. Going from prototype to planet-scale is almost a flip of a switch.
With 10,000x faster services, just 100 sharded servers give you a million-fold scale headroom.
No more "it's stale β find out why" debugging sessions. Automatic dependency tracking ensures dependents update when something changes.
Your services run locally or distributed with zero changes. No complex dependencies. AI agents can debug your code by running E2E tests right on your laptop. Or in Docker.
Your code stays focused on business logic, Fusion handles the rest. Forget about the boilerplate for real-time updates or cache invalidation.
Voxt is a real-time chat app built by the creators of Fusion. It features:
Check out how it works at Voxt.ai, or reach out to Alex Y. @ Voxt.ai if you want to chat in real time. Fusion handles everything related to real-time there.
dotnet add package ActualLab.Fusionservices.AddFusion().AddService<UserService>(); // UserService must "implement" tagging IComputeService[ComputeMethod]
public virtual async Task<User> GetUser(long id) { ... }That's the entire setup. Your service now has automatic caching, dependency tracking, and real-time invalidation.
Questions? Want to see how others use Fusion? Join the discussion:
Indirect contributors & everyone else who made Fusion possible:
ActualLab.Fusion is developed by the creators of Voxt and is the successor of Stl.Fusion, originally created at ServiceTitan. Check out The Story Behind Fusion to learn more about its origins.