StorageBuckets
Split the origin's storage into named compartments, each with its own quota, persistence, durability and expiry - so the browser can evict the thumbnail cache without taking the draft the user has not sent yet.
@inject Bit.Butil.StorageBuckets storageBucketsMDN reference
navigator.storageBuckets ships in Chromium-based browsers and is not implemented
in Firefox or Safari. Every member here degrades to a false/null/empty answer where the API is
absent, so nothing throws - it simply does nothing. Feature-detect with
IsSupported and keep StorageManager as the fallback.
True when the runtime exposes navigator.storageBuckets.
Bit.Butil.StorageBuckets storageBuckets
if (await storageBuckets.IsSupported())
{
// safe to open buckets
}Open creates the bucket when it is not there and returns it as it is when it is - so it is safe to call on every start-up. The options only apply at creation. Names are lower-case letters, digits, dash and underscore, not starting with a dash or underscore.
StorageBucketInfo? bucket = await storageBuckets.Open("drafts",
persisted: true,
durability: StorageBucketDurability.Strict,
quota: 50 * 1024 * 1024,
expires: DateTimeOffset.UtcNow.AddDays(30));
// asking for persistence does not grant it - read the answer back
var isPersisted = bucket?.Persisted;
string[] names = await storageBuckets.Keys();Persistence is per bucket here rather than per origin: mark the one bucket holding data you cannot re-fetch, and leave the caches evictable. Estimate reports that bucket's own usage and quota.
var granted = await storageBuckets.Persist("drafts");
var isPersisted = await storageBuckets.Persisted("drafts");
StorageEstimate estimate = await storageBuckets.Estimate("drafts");
// estimate.Usage / estimate.Quota, in bytesAn expiry is permission to delete, not a scheduled deletion: the browser may drop the bucket any time after it, and usually keeps it for a while longer. Useful for a bucket holding a season's worth of downloaded content.
await storageBuckets.SetExpires("drafts", DateTimeOffset.UtcNow.AddDays(7));
DateTimeOffset? expires = await storageBuckets.GetExpires("drafts");Each bucket owns a whole file system of its own - the same origin private file system, rooted in the bucket rather than in the origin. That is what makes a bucket a real compartment rather than a label: deleting it takes its files with it.
await storageBuckets.WriteText("drafts", "letters/draft.txt", "unsent");
string? text = await storageBuckets.ReadText("drafts", "letters/draft.txt");
// Anything that isn't text goes through the byte pair - ReadText would UTF-8 decode it away.
await storageBuckets.WriteBytes("drafts", "letters/seal.bin", [0x00, 0xC3, 0x28, 0xFF]);
byte[]? bytes = await storageBuckets.ReadBytes("drafts", "letters/seal.bin");
OpfsEntry[] entries = await storageBuckets.List("drafts", "letters");
await storageBuckets.Remove("drafts", "letters", recursive: true);Deletes the bucket and everything in it - its files, its IndexedDB databases and its caches. This is the operation the whole API exists for: one call that throws away a category of data without touching the rest.
await storageBuckets.Delete("drafts");open() is create-or-get, so reaching for a bucket by name is
what brings it into existence. Butil keeps that to the members where it is the point:
Open, WriteText and WriteBytes. Everything else -
Get, Persisted, Estimate, GetExpires,
List, ReadText, ReadBytes, Persist, SetExpires,
Remove - answers null/false/empty for a name that is not in Keys,
and creates nothing.
API reference
ValueTask<bool> IsSupported()ValueTask<StorageBucketInfo?> Open(string name, bool persisted = false, StorageBucketDurability durability = Default, long? quota = null, DateTimeOffset? expires = null)ValueTask<StorageBucketInfo?> Get(string name)ValueTask<string[]> Keys()ValueTask<bool> Delete(string name)ValueTask<bool> Persist(string name)ValueTask<bool> Persisted(string name)ValueTask<StorageEstimate> Estimate(string name)ValueTask<bool> SetExpires(string name, DateTimeOffset expires)ValueTask<DateTimeOffset?> GetExpires(string name)ValueTask<OpfsEntry[]> List(string name, string path = "")ValueTask<string?> ReadText(string name, string path)ValueTask<byte[]?> ReadBytes(string name, string path)ValueTask<bool> WriteText(string name, string path, string text)ValueTask<bool> WriteBytes(string name, string path, byte[] data)ValueTask<bool> Remove(string name, string path, bool recursive = false)class StorageBucketInfo { string Name; bool Persisted; string? Durability; long? Expires; long? Quota; long? Usage; }enum StorageBucketDurability { Default, Relaxed, Strict }