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:
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:
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_ERROR—databaseNameis 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).
Import Workspace and IdbProvider from the same entry point (wative-core). A provider from a different bundle is refused — see the same-entry-point rule.
Durability and quota
After create() resolves, two read-only fields describe what the browser granted:
| Field | Type | Meaning |
|---|---|---|
provider.durability | StorageDurability | "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.quota | StorageQuota | A 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.databaseName | string | The 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:
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.{ acknowledgeEvictionRisk: true } — appropriate only when the workspace is a deliberate cache whose keys are backed up elsewhere.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" });acknowledgeEvictionRisk is required to create a container in evictable storage; it is ignored when opening one that already exists.
Options and types
IdbProviderOptions
| Option | Type | Default | Purpose |
|---|---|---|---|
acknowledgeEvictionRisk | boolean | false | Proceed even when the browser refuses persistent storage. Required to create a container in evictable storage; ignored when opening one. |
indexedDB | IDBFactory | globalThis.indexedDB | Injection seam for tests and non-browser hosts. |
storageManager | Pick<StorageManager, "persist" | "persisted" | "estimate"> | null | navigator.storage | Injection seam for tests. Pass null to skip negotiation entirely. |
IdbDestroyOptions
Extends Pick<IdbProviderOptions, "indexedDB"> and adds:
| Option | Type | Default | Purpose |
|---|---|---|---|
blockedTimeoutMs | number | 5000 | How 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.
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.
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);importContainer refuses a target that already holds records unless you pass { overwrite: true }, which replaces the target rather than merging. See the ContainerProvider reference.
Common error codes
code | When |
|---|---|
STORAGE_NOT_DURABLE | Creating a new workspace in evictable storage without acknowledgement. |
PERMISSION_DENIED | IndexedDB unavailable, or a SecurityError / InvalidStateError from the store. |
DISK_FULL | The browser's storage quota was exceeded (QuotaExceededError). |
PROVIDER_IO | A generic IndexedDB failure, a destroy() that could not be confirmed, or a provider whose connection was closed out from under it. |
RECORD_NOT_FOUND | A read for a key that does not exist. |