You're reading the Rask v1.0.0 guides.View source
All guides

IOriginPrivateFileSystem

A private, persistent file tree the app owns — for a local database or any large blob.

  • Wraps: navigator.storage.getDirectory (OPFS)
  • MDN: Origin private file system
  • Home: Rask.Core.Browser (all hosts)
  • Shape: one-shot
  • Availability: Web/Server ✅ · PWA/WASM ✅

Unlike IFileSystemAccess there is no picker and no user gesture. The app addresses files by path and reopens the same paths on every visit, which is what makes OPFS — not IIndexedDb — the right home for a SQLite file or a downloaded bundle.


public sealed partial class Notes(IOriginPrivateFileSystem fs) : Component
{
    private async Task Save(byte[] page)
    {
        // Parent directories are created on write; nothing has to be made explicitly.
        await fs.WriteAsync("db/app.sqlite", offset: 4096, page);
    }

    private async Task<byte[]?> Load() => await fs.ReadAsync("db/app.sqlite", 4096, 4096);
}

Ranges, not whole files

Reads and writes take a byte offset, so a large file is worked in chunks and the payload crossing the interop boundary is bounded by the range you ask for. ReadAllBytesAsync / WriteAllBytesAsync are the single-round-trip convenience over the same store — prefer the ranged calls once a file grows past a few megabytes.

A ranged write leaves the rest of the file intact. Writing past the current end extends the file, zero-filling the gap; TruncateAsync resizes in either direction.

Missing paths return null

Reading a path that does not exist returns null rather than throwing, matching IKeyValueStore.GetAsync. A ranged read that runs past the end of the file returns the bytes that were available — an ordinary short read, not an error.

Durability

OPFS is persistent but not automatically exempt from eviction: a browser may reclaim it under storage pressure. Ask for an exemption through IStorageEstimator, and treat unsynced writes as at risk until it reports true.


if (!await storage.IsPersistedAsync())
{
    await storage.RequestPersistAsync();
}

Chromium decides from engagement heuristics without prompting; Firefox shows a permission prompt, so call it from a user-gesture handler.

Transports

Works on both, but every call is a round trip — under the Server transport that round trip crosses the WebSocket. The local-database scenario this API exists for is in practice a WASM one.

See also