Storage ProvidersIdbProvider

IdbProvider

The browser IndexedDB backend — IdbProvider.create(name, opts), negotiated durability with the STORAGE_NOT_DURABLE guard and acknowledgeEvictionRisk, the quota snapshot, and exportContainer/importContainer backups.

IdbProvider is the browser storage backend. It keeps every sealed record in a single IndexedDB object store, so a container written in a browser is byte-identical to one written by the filesystem backends — the medium differs, the bytes do not. All encryption, record framing and the container session come from ContainerProvider; IdbProvider supplies only the IndexedDB primitives and the durability negotiation that browser storage demands.

It is registered as the browser default, so most code never names it:

browser-default.ts
import { Workspace } from "wative-core";

// Uses the default IdbProvider under the database name "wative".
const ws = await Workspace.open("my-wallet", "wsp-pwd");

You construct it explicitly when you need to negotiate durable storage, react to the durability result, or inject an IndexedDB factory.

IdbProvider.create(name, opts?)

IdbProvider has no public constructor — requesting persistence is asynchronous and must not be skippable, which a constructor could not await. Build one with the static async factory instead:

create-explicit.ts
import { Workspace, IdbProvider } from "wative-core";

const provider = await IdbProvider.create("my-wallet");
if (provider.durability !== "persistent") {
  // Storage is evictable — surface a backup prompt to the user.
}
const ws = await Workspace.open({ provider, password: "wsp-pwd" });

create(databaseName: string, opts?: IdbProviderOptions): Promise<IdbProvider> opens (or creates) the backing database and negotiates persistence. It throws a WativeError:

  • PARAMETER_ERRORdatabaseName is not a non-empty string.
  • PERMISSION_DENIED — IndexedDB is unavailable (private-browsing windows and some embedded webviews block it entirely; there is no safe fallback for key storage).

Durability and quota

After create() resolves, two read-only fields describe what the browser granted:

FieldTypeMeaning
provider.durabilityStorageDurability"persistent" if the browser granted persistent storage; "best-effort" if it declined or the API is unsupported. Best-effort storage can be evicted without warning.
provider.quotaStorageQuotaA best-effort usage/quota snapshot taken at create(){ usage?, quota? } in bytes. Either field may be absent if the browser does not report it.
provider.databaseNamestringThe IndexedDB database name this provider is bound to.

Negotiation asks navigator.storage.persisted() first — an already-granted origin is never re-prompted — and only calls persist() if persistence is not already held.

The eviction-risk guard

Browser storage is evictable and this library holds private keys, so IdbProvider refuses to create a new workspace in non-durable storage. The guard fires from initialize(), which runs only when Workspace.open() finds an empty container — so it gates creation without ever blocking access to keys that already exist. Opening an existing container is always allowed; refusing there would strand a user's keys.

When durability is "best-effort" and the risk has not been acknowledged, creation throws a WativeError with code === "STORAGE_NOT_DURABLE". There are two ways past it:

Prompt for persistence from a user gesture. Call IdbProvider.create() from inside a click or tap handler — browsers grant navigator.storage.persist() far more readily in response to a user action. If the grant succeeds, durability is "persistent" and the guard does not fire.
Acknowledge the risk explicitly. Pass { acknowledgeEvictionRisk: true } — appropriate only when the workspace is a deliberate cache whose keys are backed up elsewhere.
acknowledge-risk.ts
import { Workspace, IdbProvider } from "wative-core";

// Only when the workspace is a disposable cache with keys backed up elsewhere.
const provider = await IdbProvider.create("scratch-wallet", {
  acknowledgeEvictionRisk: true,
});
const ws = await Workspace.open({ provider, password: "wsp-pwd" });

Options and types

IdbProviderOptions

OptionTypeDefaultPurpose
acknowledgeEvictionRiskbooleanfalseProceed even when the browser refuses persistent storage. Required to create a container in evictable storage; ignored when opening one.
indexedDBIDBFactoryglobalThis.indexedDBInjection seam for tests and non-browser hosts.
storageManagerPick<StorageManager, "persist" | "persisted" | "estimate"> | nullnavigator.storageInjection seam for tests. Pass null to skip negotiation entirely.

IdbDestroyOptions

Extends Pick<IdbProviderOptions, "indexedDB"> and adds:

OptionTypeDefaultPurpose
blockedTimeoutMsnumber5000How long to keep waiting once the delete reports another connection is holding the database open, before reporting that the erase could not be confirmed.

StorageDurability

type StorageDurability = "persistent" | "best-effort";

StorageQuota

interface StorageQuota {
  readonly usage?: number;
  readonly quota?: number;
}

Erasing a database: IdbProvider.destroy(name, opts?)

destroy(databaseName: string, opts?: IdbDestroyOptions): Promise<void> deletes the entire backing database. It is irreversible — every key in it is gone — and it resolves only when the delete actually completed.

If another connection (this origin in another tab, or a provider from an older build) is holding the database open, the delete is blocked. destroy() waits up to blockedTimeoutMs, and if the delete still has not landed it rejects with a WativeError (code === "PROVIDER_IO") worded as could not be confirmed — the request may still complete on its own, nothing is known to have been erased, and destroy() is idempotent, so retrying is always safe. It rejects with PERMISSION_DENIED if IndexedDB is unavailable in the calling context.

destroy.ts
import { IdbProvider } from "wative-core";

// Remove this wallet from this browser entirely.
await IdbProvider.destroy("my-wallet");

Backups: exportContainer / importContainer

Because browser storage is evictable, a workspace that lives only in IndexedDB needs a way to reach durable storage and back. IdbProvider inherits exportContainer() and importContainer() from ContainerProvider: records are copied as sealed bytes, never decrypted, so an export requires no unlock and re-keys nothing. And because every backend shares the same framing, a dump taken in a browser imports into a filesystem workspace unchanged.

backup.ts
import { IdbProvider } from "wative-core";

const provider = await IdbProvider.create("my-wallet");
const entries = await provider.exportContainer(); // ReadonlyArray<ContainerEntry>
// Persist `entries` somewhere durable (a download, a synced file, a server).
// Later, restore into a fresh provider:
await provider.importContainer(entries);

Common error codes

codeWhen
STORAGE_NOT_DURABLECreating a new workspace in evictable storage without acknowledgement.
PERMISSION_DENIEDIndexedDB unavailable, or a SecurityError / InvalidStateError from the store.
DISK_FULLThe browser's storage quota was exceeded (QuotaExceededError).
PROVIDER_IOA generic IndexedDB failure, a destroy() that could not be confirmed, or a provider whose connection was closed out from under it.
RECORD_NOT_FOUNDA read for a key that does not exist.

Last updated on