@nihilium/recovery-storage-indexeddb 0.0.0-stage → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,17 @@
1
- # Temporary Holding Version
1
+ # `@nihilium/recovery-storage-indexeddb`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Core's `SealStore` and `SealedDataStore`, and `@nihilium/recovery-sdk`'s three ledger stores, over
4
+ IndexedDB, for a browser wallet. `idbStores(options)` builds all five over one database; add `chains`
5
+ and it is an `OrchestrationDeps`.
6
+
7
+ ```ts
8
+ const deps = { ...idbStores({ dbName: "my-wallet" }), chains };
9
+ ```
10
+
11
+ Opens per operation and closes when the transaction completes, so the conformance suites' "survives a
12
+ reopen" cases are real reopens. The upgrade creates what is missing and drops nothing: a wallet that
13
+ already owns a database keeps its rows. `getSeal` throws on a missing vault rather than resolving
14
+ `undefined`; a duplicate `entryId` is refused by a unique index and surfaces as core's
15
+ `OneWayViolationError`.
16
+
17
+ Run core's and the sdk's conformance suites against it with `fake-indexeddb/auto` under a test runner.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `SealedDataStore` over IndexedDB — the encrypted records, which are inert without the seal.
3
+ *
4
+ * Add-only, no update, no delete, and no listing: a listing would make whoever holds the records
5
+ * a directory of who has a recovery. Every method normalizes the record id first, so the display
6
+ * form and the canonical form hit the same record.
7
+ */
8
+ import { type RecordId, type SealedDataEntry, type SealedDataStore } from "@nihilium/recovery-core";
9
+ import { type IdbOptions } from "./indexeddb.js";
10
+ export declare class IdbSealedDataStore implements SealedDataStore {
11
+ private readonly options;
12
+ readonly domain: string;
13
+ constructor(options?: IdbOptions & {
14
+ domain?: string;
15
+ });
16
+ addEntry(recordId: RecordId, entry: SealedDataEntry): Promise<void>;
17
+ /** Oldest first — insertion order, which is primary-key order for an autoIncrement store. */
18
+ getEntries(recordId: RecordId): Promise<SealedDataEntry[]>;
19
+ }
20
+ //# sourceMappingURL=data-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-store.d.ts","sourceRoot":"","sources":["../src/data-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAIH,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,eAAe,EACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAQ5E,qBAAa,kBAAmB,YAAW,eAAe;IAG1C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEK,OAAO,GAAE,UAAU,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO;IAIrE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBzE,6FAA6F;IACvF,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;CAKnE"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * `SealedDataStore` over IndexedDB — the encrypted records, which are inert without the seal.
3
+ *
4
+ * Add-only, no update, no delete, and no listing: a listing would make whoever holds the records
5
+ * a directory of who has a recovery. Every method normalizes the record id first, so the display
6
+ * form and the canonical form hit the same record.
7
+ */
8
+ import { OneWayViolationError, SEALED_DATA_STORE_SUBJECT, normalizeRecordId, } from "@nihilium/recovery-core";
9
+ import { detach, STORES, withStore } from "./indexeddb.js";
10
+ export class IdbSealedDataStore {
11
+ options;
12
+ domain;
13
+ constructor(options = {}) {
14
+ this.options = options;
15
+ this.domain = options.domain ?? "browser-indexeddb";
16
+ }
17
+ async addEntry(recordId, entry) {
18
+ // First, and not defensively: a malformed id must be refused here rather than becoming a
19
+ // record nobody can look up again.
20
+ const id = normalizeRecordId(recordId);
21
+ const row = {
22
+ recordId: id,
23
+ entryId: entry.entryId,
24
+ entry: { ...entry, storedAt: entry.storedAt ?? Math.floor(Date.now() / 1000) },
25
+ };
26
+ try {
27
+ await withStore(this.options, STORES.records, "readwrite", (store, run) => run(store.add(row)));
28
+ }
29
+ catch (error) {
30
+ // The unique `byEntry` index is what detects this, so the rejection is atomic — no
31
+ // read-then-write window where two appends both see an empty slot.
32
+ if (isConstraintError(error)) {
33
+ throw new OneWayViolationError(id, `entry "${entry.entryId}" already exists and records are never replaced`, SEALED_DATA_STORE_SUBJECT);
34
+ }
35
+ throw error;
36
+ }
37
+ }
38
+ /** Oldest first — insertion order, which is primary-key order for an autoIncrement store. */
39
+ async getEntries(recordId) {
40
+ const id = normalizeRecordId(recordId);
41
+ const rows = await withStore(this.options, STORES.records, "readonly", (store, run) => run(store.index("byRecord").getAll(id)));
42
+ return rows.map((row) => detach(row.entry));
43
+ }
44
+ }
45
+ function isConstraintError(error) {
46
+ return typeof error === "object" && error !== null && "name" in error && error.name === "ConstraintError";
47
+ }
48
+ //# sourceMappingURL=data-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-store.js","sourceRoot":"","sources":["../src/data-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACH,oBAAoB,EACpB,yBAAyB,EACzB,iBAAiB,GAIpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAmB,MAAM,gBAAgB,CAAC;AAQ5E,MAAM,OAAO,kBAAkB;IAGE;IAFpB,MAAM,CAAS;IAExB,YAA6B,UAA4C,EAAE;QAA9C,YAAO,GAAP,OAAO,CAAuC;QACvE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,mBAAmB,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,QAAkB,EAAE,KAAsB;QACrD,yFAAyF;QACzF,mCAAmC;QACnC,MAAM,EAAE,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,GAAG,GAAc;YACnB,QAAQ,EAAE,EAAE;YACZ,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,EAAE;SACjF,CAAC;QACF,IAAI,CAAC;YACD,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACpG,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,mFAAmF;YACnF,mEAAmE;YACnE,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC3B,MAAM,IAAI,oBAAoB,CAAC,EAAE,EAAE,UAAU,KAAK,CAAC,OAAO,iDAAiD,EAAE,yBAAyB,CAAC,CAAC;YAC5I,CAAC;YACD,MAAM,KAAK,CAAC;QAChB,CAAC;IACL,CAAC;IAED,6FAA6F;IAC7F,KAAK,CAAC,UAAU,CAAC,QAAkB;QAC/B,MAAM,EAAE,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAc,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7I,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IAChD,CAAC;CACJ;AAED,SAAS,iBAAiB,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAK,KAA4B,CAAC,IAAI,KAAK,iBAAiB,CAAC;AACtI,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `@nihilium/recovery-storage-indexeddb` — the §12 storage contracts and the sdk's ledger stores
3
+ * over IndexedDB, for a browser wallet.
4
+ *
5
+ * Five stores in one database, opened per call. Pass `dbName` to keep a database a wallet already
6
+ * owns; the upgrade creates what is missing and drops nothing.
7
+ */
8
+ export { DB_VERSION, DEFAULT_DB_NAME, STORES, clearStores, deleteDatabase, detach, withStore } from "./indexeddb.js";
9
+ export type { IdbOptions } from "./indexeddb.js";
10
+ export { IdbSealStore, SealNotFoundError } from "./seal-store.js";
11
+ export { IdbSealedDataStore } from "./data-store.js";
12
+ export { IdbHandoverStore, IdbVaultLedgerStore, IdbWatchRecordStore } from "./ledger-stores.js";
13
+ import type { OrchestrationDeps } from "@nihilium/recovery-sdk";
14
+ import type { IdbOptions } from "./indexeddb.js";
15
+ import { IdbWatchRecordStore } from "./ledger-stores.js";
16
+ /** Every store the orchestration needs, over one database. Add `chains` and it is an `OrchestrationDeps`. */
17
+ export declare function idbStores(options?: IdbOptions & {
18
+ domain?: string;
19
+ }): Omit<OrchestrationDeps, "chains"> & {
20
+ watches: IdbWatchRecordStore;
21
+ };
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACrH,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEhG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGjD,OAAO,EAAyC,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEhG,6GAA6G;AAC7G,wBAAgB,SAAS,CAAC,OAAO,GAAE,UAAU,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,IAAI,CAAC,iBAAiB,EAAE,QAAQ,CAAC,GAAG;IAAE,OAAO,EAAE,mBAAmB,CAAA;CAAE,CAQ9I"}
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `@nihilium/recovery-storage-indexeddb` — the §12 storage contracts and the sdk's ledger stores
3
+ * over IndexedDB, for a browser wallet.
4
+ *
5
+ * Five stores in one database, opened per call. Pass `dbName` to keep a database a wallet already
6
+ * owns; the upgrade creates what is missing and drops nothing.
7
+ */
8
+ export { DB_VERSION, DEFAULT_DB_NAME, STORES, clearStores, deleteDatabase, detach, withStore } from "./indexeddb.js";
9
+ export { IdbSealStore, SealNotFoundError } from "./seal-store.js";
10
+ export { IdbSealedDataStore } from "./data-store.js";
11
+ export { IdbHandoverStore, IdbVaultLedgerStore, IdbWatchRecordStore } from "./ledger-stores.js";
12
+ import { IdbSealStore } from "./seal-store.js";
13
+ import { IdbSealedDataStore } from "./data-store.js";
14
+ import { IdbHandoverStore, IdbVaultLedgerStore, IdbWatchRecordStore } from "./ledger-stores.js";
15
+ /** Every store the orchestration needs, over one database. Add `chains` and it is an `OrchestrationDeps`. */
16
+ export function idbStores(options = {}) {
17
+ return {
18
+ sealStore: new IdbSealStore(options),
19
+ dataStore: new IdbSealedDataStore(options),
20
+ ledger: new IdbVaultLedgerStore(options),
21
+ handovers: new IdbHandoverStore(options),
22
+ watches: new IdbWatchRecordStore(options),
23
+ };
24
+ }
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAErH,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAIhG,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEhG,6GAA6G;AAC7G,MAAM,UAAU,SAAS,CAAC,UAA4C,EAAE;IACpE,OAAO;QACH,SAAS,EAAE,IAAI,YAAY,CAAC,OAAO,CAAC;QACpC,SAAS,EAAE,IAAI,kBAAkB,CAAC,OAAO,CAAC;QAC1C,MAAM,EAAE,IAAI,mBAAmB,CAAC,OAAO,CAAC;QACxC,SAAS,EAAE,IAAI,gBAAgB,CAAC,OAAO,CAAC;QACxC,OAAO,EAAE,IAAI,mBAAmB,CAAC,OAAO,CAAC;KAC5C,CAAC;AACN,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The twenty lines of IndexedDB plumbing every store here sits on.
3
+ *
4
+ * Opens per operation and closes when the transaction completes. That is not tidiness: the
5
+ * conformance suites build a store, use it, and then build a *second* one expecting to see the
6
+ * first one's data — a "survives a reopen" case that a long-lived connection would pass for the
7
+ * wrong reason. Opening per call makes the reopen real every time.
8
+ *
9
+ * **Create-if-absent, drop nothing.** Every store is created only when missing, so a wallet that
10
+ * already runs an earlier layout under its own `dbName` keeps its rows — a recovery in flight
11
+ * survives the upgrade, which matters because the handover store is exactly what holds one.
12
+ *
13
+ * **Assumes:** a browser with IndexedDB, or `fake-indexeddb` under a test runner; nothing here
14
+ * touches `window`.
15
+ */
16
+ /** 6: adds `watches`. Earlier numbers belong to the demo this grew out of; they drop nothing here. */
17
+ export declare const DB_VERSION = 6;
18
+ export declare const DEFAULT_DB_NAME = "nihilium-recovery";
19
+ /** Every store this package keeps. Declared in one place so an upgrade is a diff, not an archaeology. */
20
+ export declare const STORES: {
21
+ /** vaultId → { vaultId, storedAt, blob }. Bearer material: one row is a whole seal. */
22
+ readonly seals: "seals";
23
+ /** Append-only encrypted records, indexed by recordId and uniquely by (recordId, entryId). */
24
+ readonly records: "records";
25
+ /** The ledger of what was sealed — `VaultRecord`. */
26
+ readonly vaults: "vaults";
27
+ /** Recoveries submitted on-chain and waiting out a timelock. Signed intents, never keys. */
28
+ readonly handovers: "handovers";
29
+ /** What this device keeps about each vault's watch. */
30
+ readonly watches: "watches";
31
+ };
32
+ export interface IdbOptions {
33
+ /** Defaults to `nihilium-recovery`. Tests pass a fresh name per case. */
34
+ dbName?: string;
35
+ }
36
+ /**
37
+ * Runs `fn` inside one transaction and resolves when the transaction *completes* — not when the
38
+ * request succeeds. A write that resolved on `onsuccess` would let a caller observe data that the
39
+ * transaction had not yet committed.
40
+ */
41
+ export declare function withStore<T>(options: IdbOptions, storeName: string, mode: IDBTransactionMode, fn: (store: IDBObjectStore, run: <R>(request: IDBRequest<R>) => Promise<R>) => Promise<T> | T): Promise<T>;
42
+ /** Structured-cloned on the way out, so a caller mutating what it got cannot reach the store. */
43
+ export declare function detach<T>(value: T): T;
44
+ /** Empty the named stores and leave the rest, and the database, in place. */
45
+ export declare function clearStores(names: readonly string[], options?: IdbOptions): Promise<void>;
46
+ /**
47
+ * Delete the whole database. Blunt, and the right answer only for a throwaway wallet: seals are
48
+ * bearer material and records are append-only, so "repair the rows" is not something this can
49
+ * honestly offer. A blocked delete — another tab holding the database open — rejects rather than
50
+ * hanging.
51
+ */
52
+ export declare function deleteDatabase(dbName?: string): Promise<void>;
53
+ //# sourceMappingURL=indexeddb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexeddb.d.ts","sourceRoot":"","sources":["../src/indexeddb.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,sGAAsG;AACtG,eAAO,MAAM,UAAU,IAAI,CAAC;AAE5B,eAAO,MAAM,eAAe,sBAAsB,CAAC;AAEnD,yGAAyG;AACzG,eAAO,MAAM,MAAM;IACf,uFAAuF;;IAEvF,8FAA8F;;IAE9F,qDAAqD;;IAErD,4FAA4F;;IAE5F,uDAAuD;;CAEjD,CAAC;AAEX,MAAM,WAAW,UAAU;IACvB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAyCD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC7B,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,kBAAkB,EACxB,EAAE,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAC9F,OAAO,CAAC,CAAC,CAAC,CAmBZ;AAED,iGAAiG;AACjG,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAErC;AAED,6EAA6E;AAC7E,wBAAsB,WAAW,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAInG;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,SAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOtE"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * The twenty lines of IndexedDB plumbing every store here sits on.
3
+ *
4
+ * Opens per operation and closes when the transaction completes. That is not tidiness: the
5
+ * conformance suites build a store, use it, and then build a *second* one expecting to see the
6
+ * first one's data — a "survives a reopen" case that a long-lived connection would pass for the
7
+ * wrong reason. Opening per call makes the reopen real every time.
8
+ *
9
+ * **Create-if-absent, drop nothing.** Every store is created only when missing, so a wallet that
10
+ * already runs an earlier layout under its own `dbName` keeps its rows — a recovery in flight
11
+ * survives the upgrade, which matters because the handover store is exactly what holds one.
12
+ *
13
+ * **Assumes:** a browser with IndexedDB, or `fake-indexeddb` under a test runner; nothing here
14
+ * touches `window`.
15
+ */
16
+ /** 6: adds `watches`. Earlier numbers belong to the demo this grew out of; they drop nothing here. */
17
+ export const DB_VERSION = 6;
18
+ export const DEFAULT_DB_NAME = "nihilium-recovery";
19
+ /** Every store this package keeps. Declared in one place so an upgrade is a diff, not an archaeology. */
20
+ export const STORES = {
21
+ /** vaultId → { vaultId, storedAt, blob }. Bearer material: one row is a whole seal. */
22
+ seals: "seals",
23
+ /** Append-only encrypted records, indexed by recordId and uniquely by (recordId, entryId). */
24
+ records: "records",
25
+ /** The ledger of what was sealed — `VaultRecord`. */
26
+ vaults: "vaults",
27
+ /** Recoveries submitted on-chain and waiting out a timelock. Signed intents, never keys. */
28
+ handovers: "handovers",
29
+ /** What this device keeps about each vault's watch. */
30
+ watches: "watches",
31
+ };
32
+ function open(dbName) {
33
+ return new Promise((resolve, reject) => {
34
+ const request = indexedDB.open(dbName, DB_VERSION);
35
+ request.onupgradeneeded = () => {
36
+ const db = request.result;
37
+ if (!db.objectStoreNames.contains(STORES.seals)) {
38
+ db.createObjectStore(STORES.seals, { keyPath: "vaultId" });
39
+ }
40
+ if (!db.objectStoreNames.contains(STORES.records)) {
41
+ const records = db.createObjectStore(STORES.records, { autoIncrement: true });
42
+ records.createIndex("byRecord", "recordId", { unique: false });
43
+ // Unique, so IndexedDB itself rejects a duplicate entry rather than this package
44
+ // read-modify-writing its way into a race. The rejection becomes core's own
45
+ // `OneWayViolationError` in `data-store.ts`.
46
+ records.createIndex("byEntry", ["recordId", "entryId"], { unique: true });
47
+ }
48
+ if (!db.objectStoreNames.contains(STORES.vaults)) {
49
+ db.createObjectStore(STORES.vaults, { keyPath: "vaultId" });
50
+ }
51
+ if (!db.objectStoreNames.contains(STORES.handovers)) {
52
+ const handovers = db.createObjectStore(STORES.handovers, { keyPath: "id" });
53
+ handovers.createIndex("byVault", "vaultId", { unique: false });
54
+ }
55
+ if (!db.objectStoreNames.contains(STORES.watches)) {
56
+ db.createObjectStore(STORES.watches, { keyPath: "vaultId" });
57
+ }
58
+ };
59
+ request.onsuccess = () => resolve(request.result);
60
+ request.onerror = () => reject(request.error ?? new Error("Could not open IndexedDB"));
61
+ });
62
+ }
63
+ function promisify(request) {
64
+ return new Promise((resolve, reject) => {
65
+ request.onsuccess = () => resolve(request.result);
66
+ request.onerror = () => reject(request.error ?? new Error("IndexedDB request failed"));
67
+ });
68
+ }
69
+ /**
70
+ * Runs `fn` inside one transaction and resolves when the transaction *completes* — not when the
71
+ * request succeeds. A write that resolved on `onsuccess` would let a caller observe data that the
72
+ * transaction had not yet committed.
73
+ */
74
+ export async function withStore(options, storeName, mode, fn) {
75
+ const db = await open(options.dbName ?? DEFAULT_DB_NAME);
76
+ try {
77
+ const tx = db.transaction(storeName, mode);
78
+ const store = tx.objectStore(storeName);
79
+ const settled = new Promise((resolve, reject) => {
80
+ tx.oncomplete = () => resolve();
81
+ tx.onabort = () => reject(tx.error ?? new Error("IndexedDB transaction aborted"));
82
+ tx.onerror = () => reject(tx.error ?? new Error("IndexedDB transaction failed"));
83
+ });
84
+ // A failed request aborts its transaction, so one cause rejects both promises. Marking this
85
+ // one observed keeps the abort from surfacing as a second, unhandled rejection.
86
+ void settled.catch(() => undefined);
87
+ const result = await fn(store, promisify);
88
+ await settled;
89
+ return result;
90
+ }
91
+ finally {
92
+ db.close();
93
+ }
94
+ }
95
+ /** Structured-cloned on the way out, so a caller mutating what it got cannot reach the store. */
96
+ export function detach(value) {
97
+ return structuredClone(value);
98
+ }
99
+ /** Empty the named stores and leave the rest, and the database, in place. */
100
+ export async function clearStores(names, options = {}) {
101
+ for (const name of names) {
102
+ await withStore(options, name, "readwrite", (store, run) => run(store.clear()));
103
+ }
104
+ }
105
+ /**
106
+ * Delete the whole database. Blunt, and the right answer only for a throwaway wallet: seals are
107
+ * bearer material and records are append-only, so "repair the rows" is not something this can
108
+ * honestly offer. A blocked delete — another tab holding the database open — rejects rather than
109
+ * hanging.
110
+ */
111
+ export function deleteDatabase(dbName = DEFAULT_DB_NAME) {
112
+ return new Promise((resolve, reject) => {
113
+ const request = indexedDB.deleteDatabase(dbName);
114
+ request.onsuccess = () => resolve();
115
+ request.onerror = () => reject(request.error ?? new Error(`Could not delete ${dbName}.`));
116
+ request.onblocked = () => reject(new Error(`Another tab still has "${dbName}" open, so it cannot be deleted. Close the other tabs and try again.`));
117
+ });
118
+ }
119
+ //# sourceMappingURL=indexeddb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexeddb.js","sourceRoot":"","sources":["../src/indexeddb.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /** The sdk's three ledger stores over IndexedDB. Each is the interface and nothing more. */
2
+ import type { HandoverRecord, HandoverStore, VaultLedgerStore, VaultRecord, VaultWatchRecord, WatchRecordStore } from "@nihilium/recovery-sdk";
3
+ import { type IdbOptions } from "./indexeddb.js";
4
+ export declare class IdbVaultLedgerStore implements VaultLedgerStore {
5
+ private readonly options;
6
+ constructor(options?: IdbOptions);
7
+ put(record: VaultRecord): Promise<void>;
8
+ get(vaultId: string): Promise<VaultRecord | undefined>;
9
+ list(): Promise<VaultRecord[]>;
10
+ delete(vaultId: string): Promise<void>;
11
+ }
12
+ export declare class IdbHandoverStore implements HandoverStore {
13
+ private readonly options;
14
+ constructor(options?: IdbOptions);
15
+ put(record: HandoverRecord): Promise<void>;
16
+ forVault(vaultId: string): Promise<HandoverRecord[]>;
17
+ list(): Promise<HandoverRecord[]>;
18
+ delete(id: string): Promise<void>;
19
+ }
20
+ export declare class IdbWatchRecordStore implements WatchRecordStore {
21
+ private readonly options;
22
+ constructor(options?: IdbOptions);
23
+ put(record: VaultWatchRecord): Promise<void>;
24
+ get(vaultId: string): Promise<VaultWatchRecord | undefined>;
25
+ delete(vaultId: string): Promise<void>;
26
+ }
27
+ //# sourceMappingURL=ledger-stores.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger-stores.d.ts","sourceRoot":"","sources":["../src/ledger-stores.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,gBAAgB,EAAE,WAAW,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/I,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5E,qBAAa,mBAAoB,YAAW,gBAAgB;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,GAAE,UAAe;IAE/C,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAGvC,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAItD,IAAI,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAI9B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG/C;AAED,qBAAa,gBAAiB,YAAW,aAAa;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,GAAE,UAAe;IAE/C,GAAG,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAG1C,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAIpD,IAAI,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAIjC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG1C;AAED,qBAAa,mBAAoB,YAAW,gBAAgB;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,GAAE,UAAe;IAE/C,GAAG,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAG5C,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAI3D,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG/C"}
@@ -0,0 +1,58 @@
1
+ import { detach, STORES, withStore } from "./indexeddb.js";
2
+ export class IdbVaultLedgerStore {
3
+ options;
4
+ constructor(options = {}) {
5
+ this.options = options;
6
+ }
7
+ async put(record) {
8
+ await withStore(this.options, STORES.vaults, "readwrite", (store, run) => run(store.put(record)));
9
+ }
10
+ async get(vaultId) {
11
+ const row = await withStore(this.options, STORES.vaults, "readonly", (store, run) => run(store.get(vaultId)));
12
+ return row === undefined ? undefined : detach(row);
13
+ }
14
+ async list() {
15
+ const rows = await withStore(this.options, STORES.vaults, "readonly", (store, run) => run(store.getAll()));
16
+ return rows.map((row) => detach(row));
17
+ }
18
+ async delete(vaultId) {
19
+ await withStore(this.options, STORES.vaults, "readwrite", (store, run) => run(store.delete(vaultId)));
20
+ }
21
+ }
22
+ export class IdbHandoverStore {
23
+ options;
24
+ constructor(options = {}) {
25
+ this.options = options;
26
+ }
27
+ async put(record) {
28
+ await withStore(this.options, STORES.handovers, "readwrite", (store, run) => run(store.put(record)));
29
+ }
30
+ async forVault(vaultId) {
31
+ const rows = await withStore(this.options, STORES.handovers, "readonly", (store, run) => run(store.index("byVault").getAll(vaultId)));
32
+ return rows.map((row) => detach(row));
33
+ }
34
+ async list() {
35
+ const rows = await withStore(this.options, STORES.handovers, "readonly", (store, run) => run(store.getAll()));
36
+ return rows.map((row) => detach(row));
37
+ }
38
+ async delete(id) {
39
+ await withStore(this.options, STORES.handovers, "readwrite", (store, run) => run(store.delete(id)));
40
+ }
41
+ }
42
+ export class IdbWatchRecordStore {
43
+ options;
44
+ constructor(options = {}) {
45
+ this.options = options;
46
+ }
47
+ async put(record) {
48
+ await withStore(this.options, STORES.watches, "readwrite", (store, run) => run(store.put(record)));
49
+ }
50
+ async get(vaultId) {
51
+ const row = await withStore(this.options, STORES.watches, "readonly", (store, run) => run(store.get(vaultId)));
52
+ return row === undefined ? undefined : detach(row);
53
+ }
54
+ async delete(vaultId) {
55
+ await withStore(this.options, STORES.watches, "readwrite", (store, run) => run(store.delete(vaultId)));
56
+ }
57
+ }
58
+ //# sourceMappingURL=ledger-stores.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger-stores.js","sourceRoot":"","sources":["../src/ledger-stores.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `SealStore` over IndexedDB — where a browser wallet keeps its bearer material.
3
+ *
4
+ * `getSeal` **throws** on a missing vault rather than resolving `undefined`; core's conformance
5
+ * suite checks that, because a store that answers "nothing here" for a seal a user believes in is
6
+ * worse than one that fails loudly. One seal per `vaultId`; a re-put replaces (seals are mutable,
7
+ * unlike records).
8
+ */
9
+ import type { SealBlob, SealRef, SealStore } from "@nihilium/recovery-core";
10
+ import { type IdbOptions } from "./indexeddb.js";
11
+ export declare class SealNotFoundError extends Error {
12
+ readonly name = "SealNotFoundError";
13
+ constructor(vaultId: string);
14
+ }
15
+ export declare class IdbSealStore implements SealStore {
16
+ private readonly options;
17
+ /**
18
+ * Truthful, and load-bearing: `checkSealPlacement` compares this against the identity factor's
19
+ * domain. "local" would be a lie that happens to pass the check.
20
+ */
21
+ readonly domain: string;
22
+ constructor(options?: IdbOptions & {
23
+ domain?: string;
24
+ });
25
+ putSeal(vaultId: string, blob: SealBlob): Promise<void>;
26
+ getSeal(vaultId: string): Promise<SealBlob>;
27
+ /** A seal that is already gone is the state the caller wanted. Not an error. */
28
+ deleteSeal(vaultId: string): Promise<void>;
29
+ listSeals(): Promise<SealRef[]>;
30
+ }
31
+ //# sourceMappingURL=seal-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seal-store.d.ts","sourceRoot":"","sources":["../src/seal-store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5E,qBAAa,iBAAkB,SAAQ,KAAK;IACxC,SAAkB,IAAI,uBAAuB;gBACjC,OAAO,EAAE,MAAM;CAG9B;AAQD,qBAAa,YAAa,YAAW,SAAS;IAO9B,OAAO,CAAC,QAAQ,CAAC,OAAO;IANpC;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEK,OAAO,GAAE,UAAU,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO;IAIrE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvD,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAMjD,gFAAgF;IAC1E,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1C,SAAS,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;CAIxC"}
@@ -0,0 +1,38 @@
1
+ import { detach, STORES, withStore } from "./indexeddb.js";
2
+ export class SealNotFoundError extends Error {
3
+ name = "SealNotFoundError";
4
+ constructor(vaultId) {
5
+ super(`No seal for vault "${vaultId}" on this device. Import the seal file you downloaded.`);
6
+ }
7
+ }
8
+ export class IdbSealStore {
9
+ options;
10
+ /**
11
+ * Truthful, and load-bearing: `checkSealPlacement` compares this against the identity factor's
12
+ * domain. "local" would be a lie that happens to pass the check.
13
+ */
14
+ domain;
15
+ constructor(options = {}) {
16
+ this.options = options;
17
+ this.domain = options.domain ?? "browser-indexeddb";
18
+ }
19
+ async putSeal(vaultId, blob) {
20
+ const row = { vaultId, storedAt: Math.floor(Date.now() / 1000), blob };
21
+ await withStore(this.options, STORES.seals, "readwrite", (store, run) => run(store.put(row)));
22
+ }
23
+ async getSeal(vaultId) {
24
+ const row = await withStore(this.options, STORES.seals, "readonly", (store, run) => run(store.get(vaultId)));
25
+ if (row === undefined)
26
+ throw new SealNotFoundError(vaultId);
27
+ return detach(row.blob);
28
+ }
29
+ /** A seal that is already gone is the state the caller wanted. Not an error. */
30
+ async deleteSeal(vaultId) {
31
+ await withStore(this.options, STORES.seals, "readwrite", (store, run) => run(store.delete(vaultId)));
32
+ }
33
+ async listSeals() {
34
+ const rows = await withStore(this.options, STORES.seals, "readonly", (store, run) => run(store.getAll()));
35
+ return rows.map((row) => ({ vaultId: row.vaultId, storedAt: row.storedAt }));
36
+ }
37
+ }
38
+ //# sourceMappingURL=seal-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seal-store.js","sourceRoot":"","sources":["../src/seal-store.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAmB,MAAM,gBAAgB,CAAC;AAE5E,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACtB,IAAI,GAAG,mBAAmB,CAAC;IAC7C,YAAY,OAAe;QACvB,KAAK,CAAC,sBAAsB,OAAO,wDAAwD,CAAC,CAAC;IACjG,CAAC;CACJ;AAQD,MAAM,OAAO,YAAY;IAOQ;IAN7B;;;OAGG;IACM,MAAM,CAAS;IAExB,YAA6B,UAA4C,EAAE;QAA9C,YAAO,GAAP,OAAO,CAAuC;QACvE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,mBAAmB,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAe,EAAE,IAAc;QACzC,MAAM,GAAG,GAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC;QAChF,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAClG,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAe;QACzB,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAsB,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAClI,IAAI,GAAG,KAAK,SAAS;YAAE,MAAM,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC5D,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED,gFAAgF;IAChF,KAAK,CAAC,UAAU,CAAC,OAAe;QAC5B,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACzG,CAAC;IAED,KAAK,CAAC,SAAS;QACX,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAY,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACrH,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACjF,CAAC;CACJ"}
package/package.json CHANGED
@@ -1,6 +1,30 @@
1
1
  {
2
2
  "name": "@nihilium/recovery-storage-indexeddb",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.9.0",
4
+ "description": "The §12 storage contracts and the sdk's ledger stores over IndexedDB, for browser wallets.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": ["dist", "README.md"],
16
+ "scripts": {
17
+ "build": "tsc -b",
18
+ "clean": "tsc -b --clean"
19
+ },
20
+ "dependencies": {
21
+ "@nihilium/recovery-core": "^0.9.0",
22
+ "@nihilium/recovery-sdk": "^0.9.0"
23
+ },
24
+ "devDependencies": {
25
+ "fake-indexeddb": "^6.2.5"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ }
30
+ }