Persistent Cache Implementation
Fusion clients can use persistent caches (IndexedDB, localStorage, SQLite, etc.) to enable speculative execution — instantly returning cached values while validating them with the server in the background.
The Cache Interface
To enable persistent caching, implement IRemoteComputedCache or extend RemoteComputedCache:
public interface IRemoteComputedCache
{
Task WhenInitialized { get; }
ValueTask<RpcCacheValue?> Get(RpcCacheKey key, CancellationToken cancellationToken = default);
void Set(RpcCacheKey key, RpcCacheValue value);
void Remove(RpcCacheKey key);
Task Clear(CancellationToken cancellationToken = default);
}The base RemoteComputedCache class provides:
- Version management: Automatically clears the cache when your app version changes
- Serialization helpers: Handles
RpcCacheKeyandRpcCacheValueserialization
For persistence with batched writes, extend FlushingRemoteComputedCache which buffers writes and flushes them periodically to reduce I/O overhead.
Example: localStorage Implementation
Here's a complete implementation using browser localStorage (from the TodoApp sample):
public sealed class LocalStorageRemoteComputedCache : RemoteComputedCache
{
public new record Options : RemoteComputedCache.Options
{
public static new readonly Options Default = new() { Version = "1.0" };
public string KeyPrefix { get; init; } = "";
}
private readonly ISyncLocalStorageService _storage;
private readonly string _keyPrefix;
public LocalStorageRemoteComputedCache(Options settings, IServiceProvider services)
: base(settings, services)
{
_keyPrefix = settings.KeyPrefix;
_storage = services.GetRequiredService<ISyncLocalStorageService>();
}
public override ValueTask<RpcCacheValue?> Get(RpcCacheKey key, CancellationToken cancellationToken = default)
{
var sValue = _storage.GetItemAsString(GetStringKey(key));
if (sValue.IsNullOrEmpty())
return default;
var bytes = Convert.FromBase64String(sValue);
var value = MemoryPackSerializer.Deserialize<RpcCacheValue>(bytes);
return new ValueTask<RpcCacheValue?>(value);
}
public override void Set(RpcCacheKey key, RpcCacheValue value)
{
var bytes = MemoryPackSerializer.Serialize(value);
var sValue = Convert.ToBase64String(bytes);
_storage.SetItemAsString(GetStringKey(key), sValue);
}
public override void Remove(RpcCacheKey key)
=> _storage.RemoveItem(GetStringKey(key));
public override Task Clear(CancellationToken cancellationToken = default)
{
_storage.Clear();
return Task.CompletedTask;
}
private string GetStringKey(RpcCacheKey key)
=> string.Concat(_keyPrefix, key.Name, " ", Convert.ToBase64String(key.ArgumentData.Span));
}Registration
Register your cache implementation using the built-in helper:
fusion.AddRemoteComputedCache<LocalStorageRemoteComputedCache, LocalStorageRemoteComputedCache.Options>(
_ => LocalStorageRemoteComputedCache.Options.Default);Or register manually:
services.AddSingleton(_ => LocalStorageRemoteComputedCache.Options.Default);
services.AddSingleton<IRemoteComputedCache>(c => {
var options = c.GetRequiredService<LocalStorageRemoteComputedCache.Options>();
return new LocalStorageRemoteComputedCache(options, c);
});Built-in Implementations
Fusion provides InMemoryRemoteComputedCache for testing:
fusion.AddInMemoryRemoteComputedCache();This is intended for testing only — it doesn't persist across sessions, so you won't get the startup performance benefits of a real persistent cache in production.
