OriginPrivateFileSystem
A real file system, private to your origin, with no picker and no permission prompt. Write files, walk directories, and - through a worker - read and write at a byte offset without rewriting the file. This is the storage a database engine would sit on.
@inject Bit.Butil.OriginPrivateFileSystem opfsMDN reference
OPFS itself ships in every current engine. The synchronous access handles the Sync* members use are a separate check: they only exist on a worker thread, and this service relays those calls to a worker it starts for you.
Bit.Butil.OriginPrivateFileSystem opfs
var supported = await opfs.IsSupported();
var syncSupported = await opfs.IsSyncAccessSupported();Paths are relative to the origin's private root, and directories along the path are created for a write. A write goes through a swap file and commits on close, so a failed write leaves the previous contents rather than half of the new ones.
await opfs.WriteText("notes/today.txt", "hello from C#");
string? text = await opfs.ReadText("notes/today.txt");
// null when there is no file thereThe same two calls for binary content. Nothing is base64'd on the way through - a byte[] crosses the interop boundary as a Uint8Array.
byte[] data = System.Text.Encoding.UTF8.GetBytes("binary payload");
await opfs.WriteBytes("blobs/payload.bin", data);
byte[]? read = await opfs.ReadBytes("blobs/payload.bin");List returns one directory's immediate children - descend by calling it again with a child's Path. CreateDirectory creates every missing directory along the path.
await opfs.CreateDirectory("projects/drafts");
OpfsEntry[] entries = await opfs.List("projects");
foreach (var entry in entries)
{
// entry.Name, entry.Path, entry.IsDirectory
}
var exists = await opfs.Exists("projects/drafts");Size, MIME type and last-modified time without reading the contents - which is what you want before deciding whether to read a file at all.
OpfsFileInfo? info = await opfs.GetFileInfo("notes/today.txt");
if (info is not null)
{
var size = info.Size; // bytes
var modified = DateTimeOffset.FromUnixTimeMilliseconds(info.LastModified);
}Move renames or relocates a file - natively in Chromium, and as a copy-and-delete elsewhere. Remove needs recursive: true for a directory that is not empty. Clear empties the whole private file system.
await opfs.Move("notes/today.txt", "notes/archive/today.txt");
await opfs.Remove("notes/archive/today.txt");
await opfs.Remove("notes", recursive: true);
await opfs.Clear();These read and write at an offset without touching the rest of the file, which is what makes OPFS usable as a backing store for something like SQLite. createSyncAccessHandle only exists on a worker thread, so the calls are relayed to a dedicated worker started on first use and terminated when the service is disposed.
var bytes = System.Text.Encoding.UTF8.GetBytes("record-1;");
// truncate: true (the default) makes the file exactly what was written
await opfs.SyncWrite("db/log.bin", bytes);
// append is the cheap way to keep a log - nothing before it is read or rewritten
await opfs.SyncAppend("db/log.bin", System.Text.Encoding.UTF8.GetBytes("record-2;"));
var size = await opfs.SyncSize("db/log.bin");
// read 9 bytes starting at offset 9, without loading the rest
byte[]? slice = await opfs.SyncRead("db/log.bin", offset: 9, length: 9);
await opfs.SyncTruncate("db/log.bin", 9);API reference
ValueTask<bool> IsSupported()ValueTask<bool> IsSyncAccessSupported()ValueTask<OpfsEntry[]> List(string path = "")ValueTask<bool> CreateDirectory(string path)ValueTask<bool> Exists(string path)ValueTask<OpfsFileInfo?> GetFileInfo(string path)ValueTask<string?> ReadText(string path)ValueTask<byte[]?> ReadBytes(string path)ValueTask<bool> WriteText(string path, string text)ValueTask<bool> WriteBytes(string path, byte[] data)ValueTask<bool> Remove(string path, bool recursive = false)ValueTask<bool> Move(string path, string destination)ValueTask<bool> Clear()ValueTask<byte[]?> SyncRead(string path, long offset = 0, int length = 0)ValueTask<long> SyncWrite(string path, byte[] data, long offset = 0, bool truncate = true)ValueTask<long> SyncAppend(string path, byte[] data)ValueTask<bool> SyncTruncate(string path, long size)ValueTask<long> SyncSize(string path)class OpfsEntry { string Name; string Path; bool IsDirectory; }class OpfsFileInfo { string Name; string Path; long Size; string Type; long LastModified; }