@vielzeug/vault 2.2.0 → 2.3.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 +0 -19
- package/dist/_dev.cjs +1 -1
- package/dist/_dev.cjs.map +1 -1
- package/dist/_dev.js +2 -3
- package/dist/_dev.js.map +1 -1
- package/dist/adapter-core.cjs +1 -1
- package/dist/adapter-core.cjs.map +1 -1
- package/dist/adapter-core.d.ts.map +1 -1
- package/dist/adapter-core.js +2 -2
- package/dist/adapter-core.js.map +1 -1
- package/dist/adapters/indexeddb.cjs +1 -1
- package/dist/adapters/indexeddb.cjs.map +1 -1
- package/dist/adapters/indexeddb.d.ts +46 -2
- package/dist/adapters/indexeddb.d.ts.map +1 -1
- package/dist/adapters/indexeddb.js +76 -56
- package/dist/adapters/indexeddb.js.map +1 -1
- package/dist/adapters/sqlite.cjs.map +1 -1
- package/dist/adapters/sqlite.d.ts +2 -2
- package/dist/adapters/sqlite.d.ts.map +1 -1
- package/dist/adapters/sqlite.js.map +1 -1
- package/dist/adapters/webstorage.cjs.map +1 -1
- package/dist/adapters/webstorage.d.ts.map +1 -1
- package/dist/adapters/webstorage.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/indexeddb.cjs +1 -1
- package/dist/indexeddb.d.ts +3 -4
- package/dist/indexeddb.d.ts.map +1 -1
- package/dist/indexeddb.js +1 -2
- package/dist/prune.cjs +1 -1
- package/dist/prune.cjs.map +1 -1
- package/dist/prune.d.ts +8 -19
- package/dist/prune.d.ts.map +1 -1
- package/dist/prune.js +9 -13
- package/dist/prune.js.map +1 -1
- package/dist/query.cjs +1 -1
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.ts +5 -11
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +1 -4
- package/dist/query.js.map +1 -1
- package/dist/sqlite.d.ts +1 -2
- package/dist/sqlite.d.ts.map +1 -1
- package/dist/ttl.cjs +1 -1
- package/dist/ttl.cjs.map +1 -1
- package/dist/ttl.d.ts +8 -12
- package/dist/ttl.d.ts.map +1 -1
- package/dist/ttl.js +5 -5
- package/dist/ttl.js.map +1 -1
- package/dist/types.cjs +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.ts +22 -34
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +14 -19
- package/dist/types.js.map +1 -1
- package/dist/vault.cjs +1 -1
- package/dist/vault.cjs.map +1 -1
- package/dist/vault.iife.js +1 -1
- package/dist/vault.iife.js.map +1 -1
- package/dist/vault.js +1 -1
- package/dist/vault.js.map +1 -1
- package/package.json +1 -1
- package/dist/migration.cjs +0 -2
- package/dist/migration.cjs.map +0 -1
- package/dist/migration.d.ts +0 -37
- package/dist/migration.d.ts.map +0 -1
- package/dist/migration.js +0 -25
- package/dist/migration.js.map +0 -1
package/README.md
CHANGED
|
@@ -43,25 +43,6 @@ SQLite values must be JSON-compatible plain objects. SQLite operations execute s
|
|
|
43
43
|
|
|
44
44
|
Vault stores record values and expiry metadata using each adapter's native layout. Number and string primary keys are tagged internally, so `1` and `'1'` are distinct everywhere.
|
|
45
45
|
|
|
46
|
-
## Vault 3.0 migration
|
|
47
|
-
|
|
48
|
-
Adapter factories no longer come from the root entry. Import shared schema and types from `@vielzeug/vault`, then import a factory from its dedicated subpath:
|
|
49
|
-
|
|
50
|
-
```ts
|
|
51
|
-
import { table } from '@vielzeug/vault';
|
|
52
|
-
import { createIndexedDB } from '@vielzeug/vault/indexeddb';
|
|
53
|
-
import { createLocalStorage } from '@vielzeug/vault/local-storage';
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Import each adapter from its focused subpath so unused storage backends stay out of the bundle.
|
|
57
|
-
|
|
58
|
-
## Vault 2.0 migration
|
|
59
|
-
|
|
60
|
-
- Replace `Adapter` with `VaultStore`; replace `IndexedDbAdapter` with `IndexedDbVaultStore`.
|
|
61
|
-
- `batch()` is available only on `createIndexedDB` and `createSQLite`. Use one of those adapters where atomic work is required.
|
|
62
|
-
- Use `observe()` for reactivity. `watch`, `observeMany`, constructor `signals`, and `toReadableStream` were removed.
|
|
63
|
-
- Custom codecs, `defaultCodec`, and `createVersionedCodec` were removed; migrate existing data to the fixed envelope before upgrading.
|
|
64
|
-
|
|
65
46
|
## Documentation
|
|
66
47
|
|
|
67
48
|
- [Overview](https://vielzeug.dev/vault/)
|
package/dist/_dev.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function e(e
|
|
1
|
+
function e(e,...t){}exports.error=e;
|
|
2
2
|
//# sourceMappingURL=_dev.cjs.map
|
package/dist/_dev.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __VAULT_PROD__?: boolean }).__VAULT_PROD__;\n\n/** @internal
|
|
1
|
+
{"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __VAULT_PROD__?: boolean }).__VAULT_PROD__;\n\n/** @internal */\nexport function error(msg: string, ...args: unknown[]): void {\n if (isDev) console.error(`[@vielzeug/vault] ${msg}`, ...args);\n}\n"],"mappings":"AAGA,SAAgB,EAAM,EAAa,GAAG,EAAuB,CAE7D"}
|
package/dist/_dev.js
CHANGED
package/dist/_dev.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __VAULT_PROD__?: boolean }).__VAULT_PROD__;\n\n/** @internal
|
|
1
|
+
{"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __VAULT_PROD__?: boolean }).__VAULT_PROD__;\n\n/** @internal */\nexport function error(msg: string, ...args: unknown[]): void {\n if (isDev) console.error(`[@vielzeug/vault] ${msg}`, ...args);\n}\n"],"mappings":";AAGA,SAAgB,EAAM,GAAa,GAAG,GAAuB,CAE7D"}
|
package/dist/adapter-core.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./errors.cjs"),t=require("./ttl.cjs"),n=require("./internal.cjs"),r=require("./query.cjs");var i=typeof performance<`u`?()=>performance.now():()=>Date.now();function a(t){if(t.length===0)throw new e.VaultError(`batch: declare at least one table`)}function o(e,n,r){return r!==void 0&&t.
|
|
1
|
+
const e=require("./errors.cjs"),t=require("./ttl.cjs"),n=require("./internal.cjs"),r=require("./query.cjs");var i=typeof performance<`u`?()=>performance.now():()=>Date.now();function a(t){if(t.length===0)throw new e.VaultError(`batch: declare at least one table`)}function o(e,n,r){return r!==void 0&&t.assertPositiveFinite(r,`put/putAll`),r??e[n].defaultTtl}function s(t,r,i,a,o){let s=n.getRecordKey(t,r,a);if(s!==i)throw new e.VaultError(`${o}: key field "${t[r].key}" must be "${String(i)}" but got "${String(s)}" in table "${r}"`)}function c(e,t,n){return e.getMany?e.getMany(t,n):Promise.all(n.map(n=>e.get(t,n)))}function l(e,t,r,i){let a=r[e],o=a.key,s=a.indexes?new Set(a.indexes):void 0;return{deleteMany:async a=>{if(a.length===0)return 0;let o=a.map(t=>n.getRecordKey(r,e,t)),s=await t.deleteMany(e,o);return s>0&&i(e),s},getIndexRange:t.getByIndexRange?(n,r)=>t.getByIndexRange(e,n,r):void 0,getRange:t.getByKeyRange?n=>t.getByKeyRange(e,n):void 0,indexedFields:s,keyField:o,source:()=>t.getAll(e)}}function u(t,n,i,a,u){let d=a??((e,t)=>t),f=u?t=>{if(!u.has(t))throw new e.VaultScopeError(`table "${t}" is not part of this batch scope`)}:e=>{};return{async clear(e){f(e);let t=await n.count(e);await n.clear(e),t>0&&i(e)},async count(e){return f(e),n.count(e)},async delete(e,t){f(e);let r=await n.delete(e,t);return r&&i(e),r},async deleteMany(e,t){f(e);let r=await n.deleteMany(e,t);return r>0&&i(e),r},async entries(e){f(e);let r=await n.getAll(e),i=t[e].key;return r.map(e=>[e[i],e])},async get(e,t){return f(e),n.get(e,t)},async getAll(e){return f(e),n.getAll(e)},async getMany(e,t){return f(e),c(n,e,t)},async getOrDefault(e,r,a,c){f(e);let l=await n.get(e,r);if(l!==void 0)return l;let u=a();return s(t,e,r,u,`getOrDefault: defaultFn()`),await n.put(e,d(e,u),o(t,e,c)),i(e),u},async has(e,t){return f(e),n.has(e,t)},async isEmpty(e){return f(e),await n.count(e)===0},async keys(e,r){if(f(e),r){let i=await n.getAll(e),a=t[e].key;return i.filter(r).map(e=>e[a])}if(n.getAllKeys)return n.getAllKeys(e);let i=await n.getAll(e),a=t[e].key;return i.map(e=>e[a])},async put(e,r,a){f(e),await n.put(e,d(e,r),o(t,e,a)),i(e)},async putAll(e,r,a){if(f(e),r.length===0)return;let s=r.map(t=>d(e,t));await n.putAll(e,s,o(t,e,a)),i(e)},query(e){return f(e),r.createQueryBuilder(l(e,n,t,i))},async update(e,r,a,c){f(e);let l=await n.get(e,r);if(!l)return;let u={...l,...a};return s(t,e,r,u,`update`),await n.put(e,d(e,u),o(t,e,c)),i(e),u},async upsert(e,r,a,c){f(e);let l=a(await n.get(e,r));return s(t,e,r,l,`upsert: fn()`),await n.put(e,d(e,l),o(t,e,c)),i(e),l}}}function d(t,a,o){let{logger:s,onMetrics:c,validators:d}=o??{},f=n.createObserverHub(e=>a.getAll(e),s?e=>s.error(`[vault] observer notification failed`,e instanceof Error?e:Error(String(e))):void 0),p=e=>{f.notify(e),o?.onMutation?.(e)},m=e=>{f.notify(e)},h=a.pruneAllExpired?.bind(a),g=a.getRawCount?.bind(a),_=o?.onCrossTabMessage?.(m)??void 0,v=(t,n)=>{let r=d?.[t];if(!r)return n;try{return r.parse(n)}catch(n){throw new e.VaultError(`validation failed for table "${t}"`,{cause:n})}},y=(e,t,n)=>{if(!c)return n();let r=i();return n().finally(()=>c({duration:i()-r,operation:t,table:e}))};o?.onTransactions?.({notifyMutation:p,validate:v});let b=u(t,a,p,v),x=new AbortController,S=!1,C=()=>{if(S)throw new e.VaultDisposedError},w={async clear(e){C(),await y(e,`clear`,()=>b.clear(e))},async count(e){return C(),y(e,`count`,()=>a.count(e))},async debug(){C();let e=Object.keys(t);return{tables:await Promise.all(e.map(async e=>{let t=g?await g(e):void 0,n=await a.count(e);return{expiredCount:t===void 0?0:Math.max(0,t-n),name:e,recordCount:n}}))}},async delete(e,t){return C(),y(e,`delete`,()=>b.delete(e,t))},async deleteMany(e,t){return C(),y(e,`deleteMany`,()=>b.deleteMany(e,t))},get disposalSignal(){return x.signal},async dispose(){S||(S=!0,x.abort(),_?.(),f.dispose(),await a.dispose?.())},get disposed(){return S},async entries(e){return C(),y(e,`entries`,()=>b.entries(e))},async get(e,t){return C(),y(e,`get`,()=>b.get(e,t))},async getAll(e){return C(),y(e,`getAll`,()=>b.getAll(e))},async getMany(e,t){return C(),y(e,`getMany`,()=>b.getMany(e,t))},async getOrDefault(e,t,n,r){return C(),y(e,`getOrDefault`,()=>b.getOrDefault(e,t,n,r))},async has(e,t){return C(),y(e,`has`,()=>b.has(e,t))},async isEmpty(e){return C(),y(e,`isEmpty`,async()=>await a.count(e)===0)},async keys(e,t){return C(),y(e,`keys`,()=>b.keys(e,t))},observe(e,t,n){return C(),f.observe(e,t,n)},async pruneExpired(){if(C(),h)return await h();let e=Object.keys(t),n=await Promise.all(e.map(async e=>[e,await a.pruneExpiredInTable(e)]));return Object.fromEntries(n)},async put(e,t,n){C(),await y(e,`put`,()=>b.put(e,t,n))},async putAll(e,t,n){C(),await y(e,`putAll`,()=>b.putAll(e,t,n))},query(e){C();let n=l(e,a,t,p);return c?r.createQueryBuilder({...n,deleteMany:n.deleteMany?t=>y(e,`queryDelete`,()=>n.deleteMany(t)):void 0,source:()=>y(e,`query`,()=>a.getAll(e))}):r.createQueryBuilder(n)},async[Symbol.asyncDispose](){await w.dispose()},async update(e,t,n,r){return C(),y(e,`update`,()=>b.update(e,t,n,r))},async upsert(e,t,n,r){return C(),y(e,`upsert`,()=>b.upsert(e,t,n,r))}};return w}function f(e,t){return Object.assign(e,{batch:t})}exports.assertBatchTables=a,exports.buildAdapterOps=d,exports.buildTxContext=u,exports.withIndexedDbTransactions=f;
|
|
2
2
|
//# sourceMappingURL=adapter-core.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter-core.cjs","names":[],"sources":["../src/adapter-core.ts"],"sourcesContent":["import { VaultDisposedError, VaultError, VaultScopeError } from './errors';\nimport { createObserverHub, getRecordKey } from './internal';\nimport { createQueryBuilder, type NativeRange, type QueryContext } from './query';\nimport { assertTtlMs } from './ttl';\nimport type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n IndexedDbVaultStore,\n KeyOf,\n MetricsEvent,\n RecordOf,\n TransactionContext,\n TtlMs,\n VaultStore,\n} from './types';\n\n/* -------------------- Internal backend protocol -------------------- */\n\n/** @internal Full backend protocol implemented by each adapter. A single flat interface — no StorageCore/StorageBackend split. */\nexport type StorageBackend<S extends AnySchema, K extends keyof S & string = keyof S & string> = {\n clear<T extends K>(table: T): Promise<void>;\n /** Returns live (non-expired) record count for the table. */\n count<T extends K>(table: T): Promise<number>;\n delete<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;\n deleteMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<number>;\n dispose?(): Promise<void>;\n get<T extends K>(table: T, key: KeyOf<S, T>): Promise<RecordOf<S, T> | undefined>;\n getAll<T extends K>(table: T): Promise<RecordOf<S, T>[]>;\n /**\n * Optional: fetch all live primary keys without materialising full records.\n * When present, `keys()` uses this instead of `getAll()` + key extraction.\n * For tables with TTL, implementations may still fall back to `getAll()` to\n * filter expired keys accurately.\n */\n getAllKeys?<T extends K>(table: T): Promise<KeyOf<S, T>[]>;\n /**\n * Optional secondary-index range fetch. When present, `query()` activates `getIndexRange` in\n * the QueryContext so that a leading `equals`, `between`, or `startsWith` on an indexed field\n * uses an IDB index instead of a full-table scan.\n */\n getByIndexRange?<T extends K>(table: T, field: string, range: NativeRange): Promise<RecordOf<S, T>[]>;\n /**\n * Optional primary-key range fetch for push-down queries. When present, `query()` activates\n * `getRange` in the QueryContext so that a leading `equals`, `between`, or `startsWith` on the\n * key field avoids a full-table scan. Falls back to `getAll` when absent.\n */\n getByKeyRange?<T extends K>(table: T, range: NativeRange): Promise<RecordOf<S, T>[]>;\n /**\n * Optional: fetch multiple records by key in a single operation. Preserves key order; missing keys yield\n * `undefined`. Falls back to N individual `get` calls when absent.\n */\n getMany?<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<Array<RecordOf<S, T> | undefined>>;\n /** Raw record count including TTL-expired entries. Used only for debug(). */\n getRawCount?<T extends K>(table: T): Promise<number>;\n has<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;\n /**\n * Optional: prune all expired records across all tables in a single atomic operation.\n * When present, `pruneExpired()` delegates here instead of calling `pruneExpiredInTable` N times.\n */\n pruneAllExpired?(): Promise<Record<string, number>>;\n pruneExpiredInTable<T extends K>(table: T): Promise<number>;\n put<T extends K>(table: T, value: RecordOf<S, T>, ttl?: TtlMs): Promise<void>;\n putAll<T extends K>(table: T, values: RecordOf<S, T>[], ttl?: TtlMs): Promise<void>;\n};\n\n/** @internal */\nexport type BatchDeps<S extends AnySchema> = {\n notifyMutation: (table: keyof S & string) => void;\n validate: <K extends keyof S & string>(table: K, value: RecordOf<S, K>) => RecordOf<S, K>;\n};\n\n/** @internal */\nexport type BatchImpl<S extends AnySchema> = <K extends keyof S & string, R>(\n tables: readonly K[],\n fn: (tx: TransactionContext<S, K>) => Promise<R>,\n) => Promise<R>;\n\n/* -------------------- Helpers -------------------- */\n\nconst getTimestamp: () => number = typeof performance !== 'undefined' ? () => performance.now() : () => Date.now();\n\nexport function assertBatchTables(tables: readonly string[]): void {\n if (tables.length === 0) throw new VaultError('batch: declare at least one table');\n}\n\nfunction resolveTtl<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n table: K,\n ttl?: TtlMs,\n): TtlMs | undefined {\n if (ttl !== undefined) assertTtlMs(ttl, 'put/putAll');\n\n return ttl ?? schema[table].defaultTtl;\n}\n\nfunction verifyKey<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n table: K,\n expected: KeyOf<S, K>,\n value: RecordOf<S, K>,\n op: string,\n): void {\n const actual = getRecordKey(schema, table, value);\n\n if (actual !== expected) {\n throw new VaultError(\n `${op}: key field \"${schema[table].key}\" must be \"${String(expected)}\" but got \"${String(actual)}\" in table \"${table}\"`,\n );\n }\n}\n\nfunction getManyWithFallback<S extends AnySchema, K extends keyof S & string>(\n core: Pick<StorageBackend<S, K>, 'get' | 'getMany'>,\n table: K,\n keys: KeyOf<S, K>[],\n): Promise<Array<RecordOf<S, K> | undefined>> {\n if (core.getMany) return core.getMany(table, keys);\n\n return Promise.all(keys.map((k) => core.get(table, k)));\n}\n\nfunction buildQueryCtx<S extends AnySchema, K extends keyof S & string>(\n table: K,\n core: Pick<StorageBackend<S, K>, 'deleteMany' | 'getAll' | 'getByIndexRange' | 'getByKeyRange'>,\n schema: S,\n onMutate: (t: K) => void,\n): QueryContext<RecordOf<S, K>> {\n const entry = schema[table];\n const keyField = entry.key;\n const indexedFields = entry.indexes ? new Set(entry.indexes) : undefined;\n\n const deleteManyFn = async (records: RecordOf<S, K>[]): Promise<number> => {\n if (records.length === 0) return 0;\n\n const keys = records.map((r) => getRecordKey(schema, table, r));\n const deleted = await core.deleteMany(table, keys);\n\n if (deleted > 0) onMutate(table);\n\n return deleted;\n };\n\n return {\n deleteMany: deleteManyFn,\n getIndexRange: core.getByIndexRange\n ? (field, range) => core.getByIndexRange!(table, field, range) as Promise<RecordOf<S, K>[]>\n : undefined,\n getRange: core.getByKeyRange\n ? (range) => core.getByKeyRange!(table, range) as Promise<RecordOf<S, K>[]>\n : undefined,\n indexedFields,\n keyField,\n source: () => core.getAll(table),\n };\n}\n\n/* -------------------- buildTxContext -------------------- */\n\nexport function buildTxContext<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n core: StorageBackend<S, K>,\n onMutate: (table: K) => void,\n validate?: <T extends K>(table: T, value: RecordOf<S, T>) => RecordOf<S, T>,\n scope?: ReadonlySet<string>,\n): TransactionContext<S, K> {\n const applyValidation = validate ?? ((_: K, v: RecordOf<S, K>) => v);\n\n const checkScope = scope\n ? (t: K): void => {\n if (!scope.has(t)) {\n throw new VaultScopeError(`table \"${t}\" is not part of this batch scope`);\n }\n }\n : (_t: K): void => {};\n\n return {\n async clear(table) {\n checkScope(table);\n\n const live = await core.count(table);\n\n await core.clear(table);\n\n if (live > 0) onMutate(table);\n },\n async count(table) {\n checkScope(table);\n\n return core.count(table);\n },\n async delete(table, key) {\n checkScope(table);\n\n const deleted = await core.delete(table, key);\n\n if (deleted) onMutate(table);\n\n return deleted;\n },\n async deleteMany(table, keys) {\n checkScope(table);\n\n const deleted = await core.deleteMany(table, keys);\n\n if (deleted > 0) onMutate(table);\n\n return deleted;\n },\n async entries(table) {\n checkScope(table);\n\n const records = await core.getAll(table);\n const keyField = schema[table].key;\n\n return records.map((r) => [(r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>, r]);\n },\n async get(table, key) {\n checkScope(table);\n\n return core.get(table, key);\n },\n async getAll(table) {\n checkScope(table);\n\n return core.getAll(table);\n },\n async getMany(table, keys) {\n checkScope(table);\n\n return getManyWithFallback(core, table, keys);\n },\n async getOrDefault(table, key, defaultFn, ttl) {\n checkScope(table);\n\n const existing = await core.get(table, key);\n\n if (existing !== undefined) return existing;\n\n const value = defaultFn();\n\n verifyKey(schema, table, key, value, 'getOrDefault: defaultFn()');\n await core.put(table, applyValidation(table, value), resolveTtl(schema, table, ttl));\n onMutate(table);\n\n return value;\n },\n async has(table, key) {\n checkScope(table);\n\n return core.has(table, key);\n },\n async isEmpty(table) {\n checkScope(table);\n\n return (await core.count(table)) === 0;\n },\n async keys(table, filter) {\n checkScope(table);\n\n if (filter) {\n const records = await core.getAll(table);\n const keyField = schema[table].key;\n\n return records.filter(filter).map((r) => (r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>);\n }\n\n // R1: prefer native getAllKeys when the backend supports it (e.g. IDB store.getAllKeys).\n if (core.getAllKeys) {\n return core.getAllKeys(table);\n }\n\n const records = await core.getAll(table);\n const keyField = schema[table].key;\n\n return records.map((r) => (r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>);\n },\n async put(table, value, ttl) {\n checkScope(table);\n\n await core.put(table, applyValidation(table, value), resolveTtl(schema, table, ttl));\n onMutate(table);\n },\n async putAll(table, values, ttl) {\n checkScope(table);\n\n if (values.length === 0) return;\n\n const toWrite = values.map((v) => applyValidation(table, v));\n\n await core.putAll(table, toWrite, resolveTtl(schema, table, ttl));\n onMutate(table);\n },\n query(table) {\n checkScope(table);\n\n return createQueryBuilder(buildQueryCtx(table, core, schema, onMutate));\n },\n async update(table, key, changes, ttl) {\n checkScope(table);\n\n const current = await core.get(table, key);\n\n if (!current) return undefined;\n\n const merged = { ...current, ...changes } as RecordOf<S, typeof table>;\n\n verifyKey(schema, table, key, merged, 'update');\n await core.put(table, applyValidation(table, merged), resolveTtl(schema, table, ttl));\n onMutate(table);\n\n return merged;\n },\n async upsert(table, key, fn, ttl) {\n checkScope(table);\n\n const existing = await core.get(table, key);\n const value = fn(existing);\n\n verifyKey(schema, table, key, value, 'upsert: fn()');\n await core.put(table, applyValidation(table, value), resolveTtl(schema, table, ttl));\n onMutate(table);\n\n return value;\n },\n };\n}\n\n/* -------------------- buildAdapterOps -------------------- */\n\n/** Builds the portable surface first; IndexedDB opts into transactions at its factory boundary. */\nexport function buildAdapterOps<S extends AnySchema>(\n schema: S,\n core: StorageBackend<S>,\n options?: BaseAdapterOptions<S> & {\n onCrossTabMessage?: (notify: (table: keyof S & string) => void) => (() => void) | undefined;\n onMutation?: (table: keyof S & string) => void;\n onTransactions?: (deps: BatchDeps<S>) => void;\n },\n): VaultStore<S> {\n const { logger, onMetrics, validators } = options ?? {};\n\n const observers = createObserverHub<S>(\n (table) => core.getAll(table),\n logger\n ? (err) =>\n logger.error(err instanceof Error ? err : new Error(String(err)), '[vault] observer notification failed')\n : undefined,\n );\n\n const notifyMutation = (table: keyof S & string): void => {\n observers.notify(table);\n options?.onMutation?.(table);\n };\n\n // Cross-tab notifications must NOT call onMutation (which would re-publish to BroadcastChannel,\n // creating an infinite loop). They only notify local observers.\n const notifyExternal = (table: keyof S & string): void => {\n observers.notify(table);\n };\n\n // R6: Resolve optional backend capabilities once at construction — no per-call checks.\n const pruneAll = core.pruneAllExpired?.bind(core);\n const rawCountFn = core.getRawCount?.bind(core);\n\n const disconnectExternal = options?.onCrossTabMessage?.(notifyExternal) ?? undefined;\n\n const validate = <K extends keyof S & string>(table: K, value: RecordOf<S, K>): RecordOf<S, K> => {\n const validator = validators?.[table];\n\n if (!validator) return value;\n\n try {\n return validator.parse(value) as RecordOf<S, K>;\n } catch (err) {\n throw new VaultError(`validation failed for table \"${table}\"`, { cause: err });\n }\n };\n\n /* Timed wrapper — only wraps when onMetrics is present, otherwise zero overhead. */\n const timed = <T>(table: string, op: MetricsEvent['operation'], fn: () => Promise<T>): Promise<T> => {\n if (!onMetrics) return fn();\n\n const start = getTimestamp();\n\n return fn().finally(() => onMetrics({ duration: getTimestamp() - start, operation: op, table }));\n };\n\n options?.onTransactions?.({ notifyMutation, validate });\n\n const txCtx = buildTxContext<S, keyof S & string>(schema, core, notifyMutation, validate);\n\n const disposeController = new AbortController();\n let disposed = false;\n\n const checkDisposed = (): void => {\n if (disposed) throw new VaultDisposedError();\n };\n\n const adapter: VaultStore<S> = {\n async clear(table) {\n checkDisposed();\n await timed(table, 'clear', () => txCtx.clear(table));\n },\n\n async count(table) {\n checkDisposed();\n\n return timed(table, 'count', () => core.count(table));\n },\n\n async debug() {\n checkDisposed();\n\n const tableNames = Object.keys(schema) as Array<keyof S & string>;\n const entries = await Promise.all(\n tableNames.map(async (name) => {\n // getRawCount must be called before core.count() because count() has\n // lazy-eviction side effects (deletes expired entries from the store).\n const raw = rawCountFn ? await rawCountFn(name) : undefined;\n const live = await core.count(name);\n\n const expiredCount = raw !== undefined ? Math.max(0, raw - live) : 0;\n\n return { expiredCount, name, recordCount: live };\n }),\n );\n\n return { tables: entries } as DebugInfo<S>;\n },\n\n async delete(table, key) {\n checkDisposed();\n\n return timed(table, 'delete', () => txCtx.delete(table, key));\n // count cache invalidated by notifyMutation inside txCtx.delete\n },\n\n async deleteMany(table, keys) {\n checkDisposed();\n\n return timed(table, 'deleteMany', () => txCtx.deleteMany(table, keys));\n // count cache invalidated by notifyMutation inside txCtx.deleteMany\n },\n\n get disposalSignal(): AbortSignal {\n return disposeController.signal;\n },\n\n async dispose() {\n if (disposed) return;\n\n disposed = true;\n disposeController.abort();\n disconnectExternal?.();\n observers.dispose();\n await core.dispose?.();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n async entries(table) {\n checkDisposed();\n\n return timed(table, 'entries', () => txCtx.entries(table));\n },\n\n async get(table, key) {\n checkDisposed();\n\n return timed(table, 'get', () => txCtx.get(table, key));\n },\n\n async getAll(table) {\n checkDisposed();\n\n return timed(table, 'getAll', () => txCtx.getAll(table));\n },\n\n async getMany(table, keys) {\n checkDisposed();\n\n return timed(table, 'getMany', () => txCtx.getMany(table, keys));\n },\n\n async getOrDefault(table, key, defaultFn, ttl) {\n checkDisposed();\n\n return timed(table, 'getOrDefault', () => txCtx.getOrDefault(table, key, defaultFn, ttl));\n },\n\n async has(table, key) {\n checkDisposed();\n\n return timed(table, 'has', () => txCtx.has(table, key));\n },\n\n async isEmpty(table) {\n checkDisposed();\n\n return timed(table, 'isEmpty', async () => (await core.count(table)) === 0);\n },\n\n async keys(table, filter) {\n checkDisposed();\n\n return timed(table, 'keys', () => txCtx.keys(table, filter));\n },\n\n observe(table, listener, opts) {\n checkDisposed();\n\n return observers.observe(table, listener, opts);\n },\n\n async pruneExpired() {\n checkDisposed();\n\n if (pruneAll) {\n const result = await pruneAll();\n\n return result as { [K in keyof S & string]: number };\n }\n\n const tableNames = Object.keys(schema) as Array<keyof S & string>;\n const pairs = await Promise.all(\n tableNames.map(async (name) => {\n const pruned = await core.pruneExpiredInTable(name);\n\n return [name, pruned] as const;\n }),\n );\n\n return Object.fromEntries(pairs) as { [K in keyof S & string]: number };\n },\n\n async put(table, value, ttl) {\n checkDisposed();\n await timed(table, 'put', () => txCtx.put(table, value, ttl));\n // count cache invalidated via notifyMutation inside txCtx.put\n },\n\n async putAll(table, values, ttl) {\n checkDisposed();\n await timed(table, 'putAll', () => txCtx.putAll(table, values, ttl));\n // count cache invalidated via notifyMutation inside txCtx.putAll\n },\n\n query(table) {\n checkDisposed();\n\n const ctx = buildQueryCtx(table, core, schema, notifyMutation);\n\n if (onMetrics) {\n return createQueryBuilder({\n ...ctx,\n deleteMany: ctx.deleteMany\n ? (records) => timed(table, 'queryDelete', () => ctx.deleteMany!(records))\n : undefined,\n source: () => timed(table, 'query', () => core.getAll(table)),\n });\n }\n\n return createQueryBuilder(ctx);\n },\n\n async [Symbol.asyncDispose]() {\n await adapter.dispose();\n },\n\n async update(table, key, changes, ttl) {\n checkDisposed();\n\n return timed(table, 'update', () => txCtx.update(table, key, changes, ttl));\n },\n\n async upsert(table, key, fn, ttl) {\n checkDisposed();\n\n return timed(table, 'upsert', () => txCtx.upsert(table, key, fn, ttl));\n },\n };\n\n return adapter;\n}\n\n/** IndexedDB is the only backend that can bind this callback to one native transaction. */\nexport function withIndexedDbTransactions<S extends AnySchema>(\n store: VaultStore<S>,\n batch: BatchImpl<S>,\n): IndexedDbVaultStore<S> {\n return Object.assign(store, { batch }) as IndexedDbVaultStore<S>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"adapter-core.cjs","names":[],"sources":["../src/adapter-core.ts"],"sourcesContent":["import { VaultDisposedError, VaultError, VaultScopeError } from './errors';\nimport { createObserverHub, getRecordKey } from './internal';\nimport { createQueryBuilder, type NativeRange, type QueryContext } from './query';\nimport { assertPositiveFinite } from './ttl';\nimport type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n IndexedDbVaultStore,\n KeyOf,\n MetricsEvent,\n RecordOf,\n TransactionContext,\n VaultStore,\n} from './types';\n\n/* -------------------- Internal backend protocol -------------------- */\n\n/** @internal Full backend protocol implemented by each adapter. A single flat interface — no StorageCore/StorageBackend split. */\nexport type StorageBackend<S extends AnySchema, K extends keyof S & string = keyof S & string> = {\n clear<T extends K>(table: T): Promise<void>;\n /** Returns live (non-expired) record count for the table. */\n count<T extends K>(table: T): Promise<number>;\n delete<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;\n deleteMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<number>;\n dispose?(): Promise<void>;\n get<T extends K>(table: T, key: KeyOf<S, T>): Promise<RecordOf<S, T> | undefined>;\n getAll<T extends K>(table: T): Promise<RecordOf<S, T>[]>;\n /**\n * Optional: fetch all live primary keys without materialising full records.\n * When present, `keys()` uses this instead of `getAll()` + key extraction.\n * For tables with TTL, implementations may still fall back to `getAll()` to\n * filter expired keys accurately.\n */\n getAllKeys?<T extends K>(table: T): Promise<KeyOf<S, T>[]>;\n /**\n * Optional secondary-index range fetch. When present, `query()` activates `getIndexRange` in\n * the QueryContext so that a leading `equals`, `between`, or `startsWith` on an indexed field\n * uses an IDB index instead of a full-table scan.\n */\n getByIndexRange?<T extends K>(table: T, field: string, range: NativeRange): Promise<RecordOf<S, T>[]>;\n /**\n * Optional primary-key range fetch for push-down queries. When present, `query()` activates\n * `getRange` in the QueryContext so that a leading `equals`, `between`, or `startsWith` on the\n * key field avoids a full-table scan. Falls back to `getAll` when absent.\n */\n getByKeyRange?<T extends K>(table: T, range: NativeRange): Promise<RecordOf<S, T>[]>;\n /**\n * Optional: fetch multiple records by key in a single operation. Preserves key order; missing keys yield\n * `undefined`. Falls back to N individual `get` calls when absent.\n */\n getMany?<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<Array<RecordOf<S, T> | undefined>>;\n /** Raw record count including TTL-expired entries. Used only for debug(). */\n getRawCount?<T extends K>(table: T): Promise<number>;\n has<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;\n /**\n * Optional: prune all expired records across all tables in a single atomic operation.\n * When present, `pruneExpired()` delegates here instead of calling `pruneExpiredInTable` N times.\n */\n pruneAllExpired?(): Promise<Record<string, number>>;\n pruneExpiredInTable<T extends K>(table: T): Promise<number>;\n put<T extends K>(table: T, value: RecordOf<S, T>, ttl?: number): Promise<void>;\n putAll<T extends K>(table: T, values: RecordOf<S, T>[], ttl?: number): Promise<void>;\n};\n\n/** @internal */\nexport type BatchDeps<S extends AnySchema> = {\n notifyMutation: (table: keyof S & string) => void;\n validate: <K extends keyof S & string>(table: K, value: RecordOf<S, K>) => RecordOf<S, K>;\n};\n\n/** @internal */\nexport type BatchImpl<S extends AnySchema> = <K extends keyof S & string, R>(\n tables: readonly K[],\n fn: (tx: TransactionContext<S, K>) => Promise<R>,\n) => Promise<R>;\n\n/* -------------------- Helpers -------------------- */\n\nconst getTimestamp: () => number = typeof performance !== 'undefined' ? () => performance.now() : () => Date.now();\n\nexport function assertBatchTables(tables: readonly string[]): void {\n if (tables.length === 0) throw new VaultError('batch: declare at least one table');\n}\n\nfunction resolveTtl<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n table: K,\n ttl?: number,\n): number | undefined {\n if (ttl !== undefined) assertPositiveFinite(ttl, 'put/putAll');\n\n return ttl ?? schema[table].defaultTtl;\n}\n\nfunction verifyKey<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n table: K,\n expected: KeyOf<S, K>,\n value: RecordOf<S, K>,\n op: string,\n): void {\n const actual = getRecordKey(schema, table, value);\n\n if (actual !== expected) {\n throw new VaultError(\n `${op}: key field \"${schema[table].key}\" must be \"${String(expected)}\" but got \"${String(actual)}\" in table \"${table}\"`,\n );\n }\n}\n\nfunction getManyWithFallback<S extends AnySchema, K extends keyof S & string>(\n core: Pick<StorageBackend<S, K>, 'get' | 'getMany'>,\n table: K,\n keys: KeyOf<S, K>[],\n): Promise<Array<RecordOf<S, K> | undefined>> {\n if (core.getMany) return core.getMany(table, keys);\n\n return Promise.all(keys.map((k) => core.get(table, k)));\n}\n\nfunction buildQueryCtx<S extends AnySchema, K extends keyof S & string>(\n table: K,\n core: Pick<StorageBackend<S, K>, 'deleteMany' | 'getAll' | 'getByIndexRange' | 'getByKeyRange'>,\n schema: S,\n onMutate: (t: K) => void,\n): QueryContext<RecordOf<S, K>> {\n const entry = schema[table];\n const keyField = entry.key;\n const indexedFields = entry.indexes ? new Set(entry.indexes) : undefined;\n\n const deleteManyFn = async (records: RecordOf<S, K>[]): Promise<number> => {\n if (records.length === 0) return 0;\n\n const keys = records.map((r) => getRecordKey(schema, table, r));\n const deleted = await core.deleteMany(table, keys);\n\n if (deleted > 0) onMutate(table);\n\n return deleted;\n };\n\n return {\n deleteMany: deleteManyFn,\n getIndexRange: core.getByIndexRange\n ? (field, range) => core.getByIndexRange!(table, field, range) as Promise<RecordOf<S, K>[]>\n : undefined,\n getRange: core.getByKeyRange\n ? (range) => core.getByKeyRange!(table, range) as Promise<RecordOf<S, K>[]>\n : undefined,\n indexedFields,\n keyField,\n source: () => core.getAll(table),\n };\n}\n\n/* -------------------- buildTxContext -------------------- */\n\nexport function buildTxContext<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n core: StorageBackend<S, K>,\n onMutate: (table: K) => void,\n validate?: <T extends K>(table: T, value: RecordOf<S, T>) => RecordOf<S, T>,\n scope?: ReadonlySet<string>,\n): TransactionContext<S, K> {\n const applyValidation = validate ?? ((_: K, v: RecordOf<S, K>) => v);\n\n const checkScope = scope\n ? (t: K): void => {\n if (!scope.has(t)) {\n throw new VaultScopeError(`table \"${t}\" is not part of this batch scope`);\n }\n }\n : (_t: K): void => {};\n\n return {\n async clear(table) {\n checkScope(table);\n\n const live = await core.count(table);\n\n await core.clear(table);\n\n if (live > 0) onMutate(table);\n },\n async count(table) {\n checkScope(table);\n\n return core.count(table);\n },\n async delete(table, key) {\n checkScope(table);\n\n const deleted = await core.delete(table, key);\n\n if (deleted) onMutate(table);\n\n return deleted;\n },\n async deleteMany(table, keys) {\n checkScope(table);\n\n const deleted = await core.deleteMany(table, keys);\n\n if (deleted > 0) onMutate(table);\n\n return deleted;\n },\n async entries(table) {\n checkScope(table);\n\n const records = await core.getAll(table);\n const keyField = schema[table].key;\n\n return records.map((r) => [(r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>, r]);\n },\n async get(table, key) {\n checkScope(table);\n\n return core.get(table, key);\n },\n async getAll(table) {\n checkScope(table);\n\n return core.getAll(table);\n },\n async getMany(table, keys) {\n checkScope(table);\n\n return getManyWithFallback(core, table, keys);\n },\n async getOrDefault(table, key, defaultFn, ttl) {\n checkScope(table);\n\n const existing = await core.get(table, key);\n\n if (existing !== undefined) return existing;\n\n const value = defaultFn();\n\n verifyKey(schema, table, key, value, 'getOrDefault: defaultFn()');\n await core.put(table, applyValidation(table, value), resolveTtl(schema, table, ttl));\n onMutate(table);\n\n return value;\n },\n async has(table, key) {\n checkScope(table);\n\n return core.has(table, key);\n },\n async isEmpty(table) {\n checkScope(table);\n\n return (await core.count(table)) === 0;\n },\n async keys(table, filter) {\n checkScope(table);\n\n if (filter) {\n const records = await core.getAll(table);\n const keyField = schema[table].key;\n\n return records.filter(filter).map((r) => (r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>);\n }\n\n // R1: prefer native getAllKeys when the backend supports it (e.g. IDB store.getAllKeys).\n if (core.getAllKeys) {\n return core.getAllKeys(table);\n }\n\n const records = await core.getAll(table);\n const keyField = schema[table].key;\n\n return records.map((r) => (r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>);\n },\n async put(table, value, ttl) {\n checkScope(table);\n\n await core.put(table, applyValidation(table, value), resolveTtl(schema, table, ttl));\n onMutate(table);\n },\n async putAll(table, values, ttl) {\n checkScope(table);\n\n if (values.length === 0) return;\n\n const toWrite = values.map((v) => applyValidation(table, v));\n\n await core.putAll(table, toWrite, resolveTtl(schema, table, ttl));\n onMutate(table);\n },\n query(table) {\n checkScope(table);\n\n return createQueryBuilder(buildQueryCtx(table, core, schema, onMutate));\n },\n async update(table, key, changes, ttl) {\n checkScope(table);\n\n const current = await core.get(table, key);\n\n if (!current) return undefined;\n\n const merged = { ...current, ...changes } as RecordOf<S, typeof table>;\n\n verifyKey(schema, table, key, merged, 'update');\n await core.put(table, applyValidation(table, merged), resolveTtl(schema, table, ttl));\n onMutate(table);\n\n return merged;\n },\n async upsert(table, key, fn, ttl) {\n checkScope(table);\n\n const existing = await core.get(table, key);\n const value = fn(existing);\n\n verifyKey(schema, table, key, value, 'upsert: fn()');\n await core.put(table, applyValidation(table, value), resolveTtl(schema, table, ttl));\n onMutate(table);\n\n return value;\n },\n };\n}\n\n/* -------------------- buildAdapterOps -------------------- */\n\n/** Builds the portable surface first; IndexedDB opts into transactions at its factory boundary. */\nexport function buildAdapterOps<S extends AnySchema>(\n schema: S,\n core: StorageBackend<S>,\n options?: BaseAdapterOptions<S> & {\n onCrossTabMessage?: (notify: (table: keyof S & string) => void) => (() => void) | undefined;\n onMutation?: (table: keyof S & string) => void;\n onTransactions?: (deps: BatchDeps<S>) => void;\n },\n): VaultStore<S> {\n const { logger, onMetrics, validators } = options ?? {};\n\n const observers = createObserverHub<S>(\n (table) => core.getAll(table),\n logger\n ? (err) =>\n logger.error('[vault] observer notification failed', err instanceof Error ? err : new Error(String(err)))\n : undefined,\n );\n\n const notifyMutation = (table: keyof S & string): void => {\n observers.notify(table);\n options?.onMutation?.(table);\n };\n\n // Cross-tab notifications must NOT call onMutation (which would re-publish to BroadcastChannel,\n // creating an infinite loop). They only notify local observers.\n const notifyExternal = (table: keyof S & string): void => {\n observers.notify(table);\n };\n\n // R6: Resolve optional backend capabilities once at construction — no per-call checks.\n const pruneAll = core.pruneAllExpired?.bind(core);\n const rawCountFn = core.getRawCount?.bind(core);\n\n const disconnectExternal = options?.onCrossTabMessage?.(notifyExternal) ?? undefined;\n\n const validate = <K extends keyof S & string>(table: K, value: RecordOf<S, K>): RecordOf<S, K> => {\n const validator = validators?.[table];\n\n if (!validator) return value;\n\n try {\n return validator.parse(value) as RecordOf<S, K>;\n } catch (err) {\n throw new VaultError(`validation failed for table \"${table}\"`, { cause: err });\n }\n };\n\n /* Timed wrapper — only wraps when onMetrics is present, otherwise zero overhead. */\n const timed = <T>(table: string, op: MetricsEvent['operation'], fn: () => Promise<T>): Promise<T> => {\n if (!onMetrics) return fn();\n\n const start = getTimestamp();\n\n return fn().finally(() => onMetrics({ duration: getTimestamp() - start, operation: op, table }));\n };\n\n options?.onTransactions?.({ notifyMutation, validate });\n\n const txCtx = buildTxContext<S, keyof S & string>(schema, core, notifyMutation, validate);\n\n const disposeController = new AbortController();\n let disposed = false;\n\n const checkDisposed = (): void => {\n if (disposed) throw new VaultDisposedError();\n };\n\n const adapter: VaultStore<S> = {\n async clear(table) {\n checkDisposed();\n await timed(table, 'clear', () => txCtx.clear(table));\n },\n\n async count(table) {\n checkDisposed();\n\n return timed(table, 'count', () => core.count(table));\n },\n\n async debug() {\n checkDisposed();\n\n const tableNames = Object.keys(schema) as Array<keyof S & string>;\n const entries = await Promise.all(\n tableNames.map(async (name) => {\n // getRawCount must be called before core.count() because count() has\n // lazy-eviction side effects (deletes expired entries from the store).\n const raw = rawCountFn ? await rawCountFn(name) : undefined;\n const live = await core.count(name);\n\n const expiredCount = raw !== undefined ? Math.max(0, raw - live) : 0;\n\n return { expiredCount, name, recordCount: live };\n }),\n );\n\n return { tables: entries } as DebugInfo<S>;\n },\n\n async delete(table, key) {\n checkDisposed();\n\n return timed(table, 'delete', () => txCtx.delete(table, key));\n // count cache invalidated by notifyMutation inside txCtx.delete\n },\n\n async deleteMany(table, keys) {\n checkDisposed();\n\n return timed(table, 'deleteMany', () => txCtx.deleteMany(table, keys));\n // count cache invalidated by notifyMutation inside txCtx.deleteMany\n },\n\n get disposalSignal(): AbortSignal {\n return disposeController.signal;\n },\n\n async dispose() {\n if (disposed) return;\n\n disposed = true;\n disposeController.abort();\n disconnectExternal?.();\n observers.dispose();\n await core.dispose?.();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n async entries(table) {\n checkDisposed();\n\n return timed(table, 'entries', () => txCtx.entries(table));\n },\n\n async get(table, key) {\n checkDisposed();\n\n return timed(table, 'get', () => txCtx.get(table, key));\n },\n\n async getAll(table) {\n checkDisposed();\n\n return timed(table, 'getAll', () => txCtx.getAll(table));\n },\n\n async getMany(table, keys) {\n checkDisposed();\n\n return timed(table, 'getMany', () => txCtx.getMany(table, keys));\n },\n\n async getOrDefault(table, key, defaultFn, ttl) {\n checkDisposed();\n\n return timed(table, 'getOrDefault', () => txCtx.getOrDefault(table, key, defaultFn, ttl));\n },\n\n async has(table, key) {\n checkDisposed();\n\n return timed(table, 'has', () => txCtx.has(table, key));\n },\n\n async isEmpty(table) {\n checkDisposed();\n\n return timed(table, 'isEmpty', async () => (await core.count(table)) === 0);\n },\n\n async keys(table, filter) {\n checkDisposed();\n\n return timed(table, 'keys', () => txCtx.keys(table, filter));\n },\n\n observe(table, listener, opts) {\n checkDisposed();\n\n return observers.observe(table, listener, opts);\n },\n\n async pruneExpired() {\n checkDisposed();\n\n if (pruneAll) {\n const result = await pruneAll();\n\n return result as { [K in keyof S & string]: number };\n }\n\n const tableNames = Object.keys(schema) as Array<keyof S & string>;\n const pairs = await Promise.all(\n tableNames.map(async (name) => {\n const pruned = await core.pruneExpiredInTable(name);\n\n return [name, pruned] as const;\n }),\n );\n\n return Object.fromEntries(pairs) as { [K in keyof S & string]: number };\n },\n\n async put(table, value, ttl) {\n checkDisposed();\n await timed(table, 'put', () => txCtx.put(table, value, ttl));\n // count cache invalidated via notifyMutation inside txCtx.put\n },\n\n async putAll(table, values, ttl) {\n checkDisposed();\n await timed(table, 'putAll', () => txCtx.putAll(table, values, ttl));\n // count cache invalidated via notifyMutation inside txCtx.putAll\n },\n\n query(table) {\n checkDisposed();\n\n const ctx = buildQueryCtx(table, core, schema, notifyMutation);\n\n if (onMetrics) {\n return createQueryBuilder({\n ...ctx,\n deleteMany: ctx.deleteMany\n ? (records) => timed(table, 'queryDelete', () => ctx.deleteMany!(records))\n : undefined,\n source: () => timed(table, 'query', () => core.getAll(table)),\n });\n }\n\n return createQueryBuilder(ctx);\n },\n\n async [Symbol.asyncDispose]() {\n await adapter.dispose();\n },\n\n async update(table, key, changes, ttl) {\n checkDisposed();\n\n return timed(table, 'update', () => txCtx.update(table, key, changes, ttl));\n },\n\n async upsert(table, key, fn, ttl) {\n checkDisposed();\n\n return timed(table, 'upsert', () => txCtx.upsert(table, key, fn, ttl));\n },\n };\n\n return adapter;\n}\n\n/** IndexedDB is the only backend that can bind this callback to one native transaction. */\nexport function withIndexedDbTransactions<S extends AnySchema>(\n store: VaultStore<S>,\n batch: BatchImpl<S>,\n): IndexedDbVaultStore<S> {\n return Object.assign(store, { batch }) as IndexedDbVaultStore<S>;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter-core.d.ts","sourceRoot":"","sources":["../src/adapter-core.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,SAAS,EACT,kBAAkB,EAElB,mBAAmB,EAGnB,QAAQ,EACR,kBAAkB,
|
|
1
|
+
{"version":3,"file":"adapter-core.d.ts","sourceRoot":"","sources":["../src/adapter-core.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,SAAS,EACT,kBAAkB,EAElB,mBAAmB,EAGnB,QAAQ,EACR,kBAAkB,EAClB,UAAU,EACX,MAAM,SAAS,CAAC;AAmEjB,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAEjE;AA2ED,wBAAgB,cAAc,CAAC,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAC5E,MAAM,EAAE,CAAC,EACT,IAAI,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,EAC5B,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,EAC3E,KAAK,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAC1B,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,CAiK1B;AAID,mGAAmG;AACnG,wBAAgB,eAAe,CAAC,CAAC,SAAS,SAAS,EACjD,MAAM,EAAE,CAAC,EACT,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EACvB,OAAO,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG;IAChC,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IAC5F,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,MAAM,KAAK,IAAI,CAAC;IAC/C,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;CAC/C,GACA,UAAU,CAAC,CAAC,CAAC,CAuPf;AAED,2FAA2F;AAC3F,wBAAgB,yBAAyB,CAAC,CAAC,SAAS,SAAS,EAC3D,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EACpB,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAClB,mBAAmB,CAAC,CAAC,CAAC,CAExB"}
|
package/dist/adapter-core.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VaultDisposedError as e, VaultError as t, VaultScopeError as n } from "./errors.js";
|
|
2
|
-
import {
|
|
2
|
+
import { assertPositiveFinite as r } from "./ttl.js";
|
|
3
3
|
import { createObserverHub as i, getRecordKey as a } from "./internal.js";
|
|
4
4
|
import { createQueryBuilder as o } from "./query.js";
|
|
5
5
|
//#region src/adapter-core.ts
|
|
@@ -120,7 +120,7 @@ function p(e, t, r, i, a) {
|
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
122
|
function m(n, r, a) {
|
|
123
|
-
let { logger: c, onMetrics: l, validators: u } = a ?? {}, d = i((e) => r.getAll(e), c ? (e) => c.error(e instanceof Error ? e : Error(String(e))
|
|
123
|
+
let { logger: c, onMetrics: l, validators: u } = a ?? {}, d = i((e) => r.getAll(e), c ? (e) => c.error("[vault] observer notification failed", e instanceof Error ? e : Error(String(e))) : void 0), m = (e) => {
|
|
124
124
|
d.notify(e), a?.onMutation?.(e);
|
|
125
125
|
}, h = (e) => {
|
|
126
126
|
d.notify(e);
|
package/dist/adapter-core.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter-core.js","names":[],"sources":["../src/adapter-core.ts"],"sourcesContent":["import { VaultDisposedError, VaultError, VaultScopeError } from './errors';\nimport { createObserverHub, getRecordKey } from './internal';\nimport { createQueryBuilder, type NativeRange, type QueryContext } from './query';\nimport { assertTtlMs } from './ttl';\nimport type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n IndexedDbVaultStore,\n KeyOf,\n MetricsEvent,\n RecordOf,\n TransactionContext,\n TtlMs,\n VaultStore,\n} from './types';\n\n/* -------------------- Internal backend protocol -------------------- */\n\n/** @internal Full backend protocol implemented by each adapter. A single flat interface — no StorageCore/StorageBackend split. */\nexport type StorageBackend<S extends AnySchema, K extends keyof S & string = keyof S & string> = {\n clear<T extends K>(table: T): Promise<void>;\n /** Returns live (non-expired) record count for the table. */\n count<T extends K>(table: T): Promise<number>;\n delete<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;\n deleteMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<number>;\n dispose?(): Promise<void>;\n get<T extends K>(table: T, key: KeyOf<S, T>): Promise<RecordOf<S, T> | undefined>;\n getAll<T extends K>(table: T): Promise<RecordOf<S, T>[]>;\n /**\n * Optional: fetch all live primary keys without materialising full records.\n * When present, `keys()` uses this instead of `getAll()` + key extraction.\n * For tables with TTL, implementations may still fall back to `getAll()` to\n * filter expired keys accurately.\n */\n getAllKeys?<T extends K>(table: T): Promise<KeyOf<S, T>[]>;\n /**\n * Optional secondary-index range fetch. When present, `query()` activates `getIndexRange` in\n * the QueryContext so that a leading `equals`, `between`, or `startsWith` on an indexed field\n * uses an IDB index instead of a full-table scan.\n */\n getByIndexRange?<T extends K>(table: T, field: string, range: NativeRange): Promise<RecordOf<S, T>[]>;\n /**\n * Optional primary-key range fetch for push-down queries. When present, `query()` activates\n * `getRange` in the QueryContext so that a leading `equals`, `between`, or `startsWith` on the\n * key field avoids a full-table scan. Falls back to `getAll` when absent.\n */\n getByKeyRange?<T extends K>(table: T, range: NativeRange): Promise<RecordOf<S, T>[]>;\n /**\n * Optional: fetch multiple records by key in a single operation. Preserves key order; missing keys yield\n * `undefined`. Falls back to N individual `get` calls when absent.\n */\n getMany?<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<Array<RecordOf<S, T> | undefined>>;\n /** Raw record count including TTL-expired entries. Used only for debug(). */\n getRawCount?<T extends K>(table: T): Promise<number>;\n has<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;\n /**\n * Optional: prune all expired records across all tables in a single atomic operation.\n * When present, `pruneExpired()` delegates here instead of calling `pruneExpiredInTable` N times.\n */\n pruneAllExpired?(): Promise<Record<string, number>>;\n pruneExpiredInTable<T extends K>(table: T): Promise<number>;\n put<T extends K>(table: T, value: RecordOf<S, T>, ttl?: TtlMs): Promise<void>;\n putAll<T extends K>(table: T, values: RecordOf<S, T>[], ttl?: TtlMs): Promise<void>;\n};\n\n/** @internal */\nexport type BatchDeps<S extends AnySchema> = {\n notifyMutation: (table: keyof S & string) => void;\n validate: <K extends keyof S & string>(table: K, value: RecordOf<S, K>) => RecordOf<S, K>;\n};\n\n/** @internal */\nexport type BatchImpl<S extends AnySchema> = <K extends keyof S & string, R>(\n tables: readonly K[],\n fn: (tx: TransactionContext<S, K>) => Promise<R>,\n) => Promise<R>;\n\n/* -------------------- Helpers -------------------- */\n\nconst getTimestamp: () => number = typeof performance !== 'undefined' ? () => performance.now() : () => Date.now();\n\nexport function assertBatchTables(tables: readonly string[]): void {\n if (tables.length === 0) throw new VaultError('batch: declare at least one table');\n}\n\nfunction resolveTtl<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n table: K,\n ttl?: TtlMs,\n): TtlMs | undefined {\n if (ttl !== undefined) assertTtlMs(ttl, 'put/putAll');\n\n return ttl ?? schema[table].defaultTtl;\n}\n\nfunction verifyKey<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n table: K,\n expected: KeyOf<S, K>,\n value: RecordOf<S, K>,\n op: string,\n): void {\n const actual = getRecordKey(schema, table, value);\n\n if (actual !== expected) {\n throw new VaultError(\n `${op}: key field \"${schema[table].key}\" must be \"${String(expected)}\" but got \"${String(actual)}\" in table \"${table}\"`,\n );\n }\n}\n\nfunction getManyWithFallback<S extends AnySchema, K extends keyof S & string>(\n core: Pick<StorageBackend<S, K>, 'get' | 'getMany'>,\n table: K,\n keys: KeyOf<S, K>[],\n): Promise<Array<RecordOf<S, K> | undefined>> {\n if (core.getMany) return core.getMany(table, keys);\n\n return Promise.all(keys.map((k) => core.get(table, k)));\n}\n\nfunction buildQueryCtx<S extends AnySchema, K extends keyof S & string>(\n table: K,\n core: Pick<StorageBackend<S, K>, 'deleteMany' | 'getAll' | 'getByIndexRange' | 'getByKeyRange'>,\n schema: S,\n onMutate: (t: K) => void,\n): QueryContext<RecordOf<S, K>> {\n const entry = schema[table];\n const keyField = entry.key;\n const indexedFields = entry.indexes ? new Set(entry.indexes) : undefined;\n\n const deleteManyFn = async (records: RecordOf<S, K>[]): Promise<number> => {\n if (records.length === 0) return 0;\n\n const keys = records.map((r) => getRecordKey(schema, table, r));\n const deleted = await core.deleteMany(table, keys);\n\n if (deleted > 0) onMutate(table);\n\n return deleted;\n };\n\n return {\n deleteMany: deleteManyFn,\n getIndexRange: core.getByIndexRange\n ? (field, range) => core.getByIndexRange!(table, field, range) as Promise<RecordOf<S, K>[]>\n : undefined,\n getRange: core.getByKeyRange\n ? (range) => core.getByKeyRange!(table, range) as Promise<RecordOf<S, K>[]>\n : undefined,\n indexedFields,\n keyField,\n source: () => core.getAll(table),\n };\n}\n\n/* -------------------- buildTxContext -------------------- */\n\nexport function buildTxContext<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n core: StorageBackend<S, K>,\n onMutate: (table: K) => void,\n validate?: <T extends K>(table: T, value: RecordOf<S, T>) => RecordOf<S, T>,\n scope?: ReadonlySet<string>,\n): TransactionContext<S, K> {\n const applyValidation = validate ?? ((_: K, v: RecordOf<S, K>) => v);\n\n const checkScope = scope\n ? (t: K): void => {\n if (!scope.has(t)) {\n throw new VaultScopeError(`table \"${t}\" is not part of this batch scope`);\n }\n }\n : (_t: K): void => {};\n\n return {\n async clear(table) {\n checkScope(table);\n\n const live = await core.count(table);\n\n await core.clear(table);\n\n if (live > 0) onMutate(table);\n },\n async count(table) {\n checkScope(table);\n\n return core.count(table);\n },\n async delete(table, key) {\n checkScope(table);\n\n const deleted = await core.delete(table, key);\n\n if (deleted) onMutate(table);\n\n return deleted;\n },\n async deleteMany(table, keys) {\n checkScope(table);\n\n const deleted = await core.deleteMany(table, keys);\n\n if (deleted > 0) onMutate(table);\n\n return deleted;\n },\n async entries(table) {\n checkScope(table);\n\n const records = await core.getAll(table);\n const keyField = schema[table].key;\n\n return records.map((r) => [(r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>, r]);\n },\n async get(table, key) {\n checkScope(table);\n\n return core.get(table, key);\n },\n async getAll(table) {\n checkScope(table);\n\n return core.getAll(table);\n },\n async getMany(table, keys) {\n checkScope(table);\n\n return getManyWithFallback(core, table, keys);\n },\n async getOrDefault(table, key, defaultFn, ttl) {\n checkScope(table);\n\n const existing = await core.get(table, key);\n\n if (existing !== undefined) return existing;\n\n const value = defaultFn();\n\n verifyKey(schema, table, key, value, 'getOrDefault: defaultFn()');\n await core.put(table, applyValidation(table, value), resolveTtl(schema, table, ttl));\n onMutate(table);\n\n return value;\n },\n async has(table, key) {\n checkScope(table);\n\n return core.has(table, key);\n },\n async isEmpty(table) {\n checkScope(table);\n\n return (await core.count(table)) === 0;\n },\n async keys(table, filter) {\n checkScope(table);\n\n if (filter) {\n const records = await core.getAll(table);\n const keyField = schema[table].key;\n\n return records.filter(filter).map((r) => (r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>);\n }\n\n // R1: prefer native getAllKeys when the backend supports it (e.g. IDB store.getAllKeys).\n if (core.getAllKeys) {\n return core.getAllKeys(table);\n }\n\n const records = await core.getAll(table);\n const keyField = schema[table].key;\n\n return records.map((r) => (r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>);\n },\n async put(table, value, ttl) {\n checkScope(table);\n\n await core.put(table, applyValidation(table, value), resolveTtl(schema, table, ttl));\n onMutate(table);\n },\n async putAll(table, values, ttl) {\n checkScope(table);\n\n if (values.length === 0) return;\n\n const toWrite = values.map((v) => applyValidation(table, v));\n\n await core.putAll(table, toWrite, resolveTtl(schema, table, ttl));\n onMutate(table);\n },\n query(table) {\n checkScope(table);\n\n return createQueryBuilder(buildQueryCtx(table, core, schema, onMutate));\n },\n async update(table, key, changes, ttl) {\n checkScope(table);\n\n const current = await core.get(table, key);\n\n if (!current) return undefined;\n\n const merged = { ...current, ...changes } as RecordOf<S, typeof table>;\n\n verifyKey(schema, table, key, merged, 'update');\n await core.put(table, applyValidation(table, merged), resolveTtl(schema, table, ttl));\n onMutate(table);\n\n return merged;\n },\n async upsert(table, key, fn, ttl) {\n checkScope(table);\n\n const existing = await core.get(table, key);\n const value = fn(existing);\n\n verifyKey(schema, table, key, value, 'upsert: fn()');\n await core.put(table, applyValidation(table, value), resolveTtl(schema, table, ttl));\n onMutate(table);\n\n return value;\n },\n };\n}\n\n/* -------------------- buildAdapterOps -------------------- */\n\n/** Builds the portable surface first; IndexedDB opts into transactions at its factory boundary. */\nexport function buildAdapterOps<S extends AnySchema>(\n schema: S,\n core: StorageBackend<S>,\n options?: BaseAdapterOptions<S> & {\n onCrossTabMessage?: (notify: (table: keyof S & string) => void) => (() => void) | undefined;\n onMutation?: (table: keyof S & string) => void;\n onTransactions?: (deps: BatchDeps<S>) => void;\n },\n): VaultStore<S> {\n const { logger, onMetrics, validators } = options ?? {};\n\n const observers = createObserverHub<S>(\n (table) => core.getAll(table),\n logger\n ? (err) =>\n logger.error(err instanceof Error ? err : new Error(String(err)), '[vault] observer notification failed')\n : undefined,\n );\n\n const notifyMutation = (table: keyof S & string): void => {\n observers.notify(table);\n options?.onMutation?.(table);\n };\n\n // Cross-tab notifications must NOT call onMutation (which would re-publish to BroadcastChannel,\n // creating an infinite loop). They only notify local observers.\n const notifyExternal = (table: keyof S & string): void => {\n observers.notify(table);\n };\n\n // R6: Resolve optional backend capabilities once at construction — no per-call checks.\n const pruneAll = core.pruneAllExpired?.bind(core);\n const rawCountFn = core.getRawCount?.bind(core);\n\n const disconnectExternal = options?.onCrossTabMessage?.(notifyExternal) ?? undefined;\n\n const validate = <K extends keyof S & string>(table: K, value: RecordOf<S, K>): RecordOf<S, K> => {\n const validator = validators?.[table];\n\n if (!validator) return value;\n\n try {\n return validator.parse(value) as RecordOf<S, K>;\n } catch (err) {\n throw new VaultError(`validation failed for table \"${table}\"`, { cause: err });\n }\n };\n\n /* Timed wrapper — only wraps when onMetrics is present, otherwise zero overhead. */\n const timed = <T>(table: string, op: MetricsEvent['operation'], fn: () => Promise<T>): Promise<T> => {\n if (!onMetrics) return fn();\n\n const start = getTimestamp();\n\n return fn().finally(() => onMetrics({ duration: getTimestamp() - start, operation: op, table }));\n };\n\n options?.onTransactions?.({ notifyMutation, validate });\n\n const txCtx = buildTxContext<S, keyof S & string>(schema, core, notifyMutation, validate);\n\n const disposeController = new AbortController();\n let disposed = false;\n\n const checkDisposed = (): void => {\n if (disposed) throw new VaultDisposedError();\n };\n\n const adapter: VaultStore<S> = {\n async clear(table) {\n checkDisposed();\n await timed(table, 'clear', () => txCtx.clear(table));\n },\n\n async count(table) {\n checkDisposed();\n\n return timed(table, 'count', () => core.count(table));\n },\n\n async debug() {\n checkDisposed();\n\n const tableNames = Object.keys(schema) as Array<keyof S & string>;\n const entries = await Promise.all(\n tableNames.map(async (name) => {\n // getRawCount must be called before core.count() because count() has\n // lazy-eviction side effects (deletes expired entries from the store).\n const raw = rawCountFn ? await rawCountFn(name) : undefined;\n const live = await core.count(name);\n\n const expiredCount = raw !== undefined ? Math.max(0, raw - live) : 0;\n\n return { expiredCount, name, recordCount: live };\n }),\n );\n\n return { tables: entries } as DebugInfo<S>;\n },\n\n async delete(table, key) {\n checkDisposed();\n\n return timed(table, 'delete', () => txCtx.delete(table, key));\n // count cache invalidated by notifyMutation inside txCtx.delete\n },\n\n async deleteMany(table, keys) {\n checkDisposed();\n\n return timed(table, 'deleteMany', () => txCtx.deleteMany(table, keys));\n // count cache invalidated by notifyMutation inside txCtx.deleteMany\n },\n\n get disposalSignal(): AbortSignal {\n return disposeController.signal;\n },\n\n async dispose() {\n if (disposed) return;\n\n disposed = true;\n disposeController.abort();\n disconnectExternal?.();\n observers.dispose();\n await core.dispose?.();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n async entries(table) {\n checkDisposed();\n\n return timed(table, 'entries', () => txCtx.entries(table));\n },\n\n async get(table, key) {\n checkDisposed();\n\n return timed(table, 'get', () => txCtx.get(table, key));\n },\n\n async getAll(table) {\n checkDisposed();\n\n return timed(table, 'getAll', () => txCtx.getAll(table));\n },\n\n async getMany(table, keys) {\n checkDisposed();\n\n return timed(table, 'getMany', () => txCtx.getMany(table, keys));\n },\n\n async getOrDefault(table, key, defaultFn, ttl) {\n checkDisposed();\n\n return timed(table, 'getOrDefault', () => txCtx.getOrDefault(table, key, defaultFn, ttl));\n },\n\n async has(table, key) {\n checkDisposed();\n\n return timed(table, 'has', () => txCtx.has(table, key));\n },\n\n async isEmpty(table) {\n checkDisposed();\n\n return timed(table, 'isEmpty', async () => (await core.count(table)) === 0);\n },\n\n async keys(table, filter) {\n checkDisposed();\n\n return timed(table, 'keys', () => txCtx.keys(table, filter));\n },\n\n observe(table, listener, opts) {\n checkDisposed();\n\n return observers.observe(table, listener, opts);\n },\n\n async pruneExpired() {\n checkDisposed();\n\n if (pruneAll) {\n const result = await pruneAll();\n\n return result as { [K in keyof S & string]: number };\n }\n\n const tableNames = Object.keys(schema) as Array<keyof S & string>;\n const pairs = await Promise.all(\n tableNames.map(async (name) => {\n const pruned = await core.pruneExpiredInTable(name);\n\n return [name, pruned] as const;\n }),\n );\n\n return Object.fromEntries(pairs) as { [K in keyof S & string]: number };\n },\n\n async put(table, value, ttl) {\n checkDisposed();\n await timed(table, 'put', () => txCtx.put(table, value, ttl));\n // count cache invalidated via notifyMutation inside txCtx.put\n },\n\n async putAll(table, values, ttl) {\n checkDisposed();\n await timed(table, 'putAll', () => txCtx.putAll(table, values, ttl));\n // count cache invalidated via notifyMutation inside txCtx.putAll\n },\n\n query(table) {\n checkDisposed();\n\n const ctx = buildQueryCtx(table, core, schema, notifyMutation);\n\n if (onMetrics) {\n return createQueryBuilder({\n ...ctx,\n deleteMany: ctx.deleteMany\n ? (records) => timed(table, 'queryDelete', () => ctx.deleteMany!(records))\n : undefined,\n source: () => timed(table, 'query', () => core.getAll(table)),\n });\n }\n\n return createQueryBuilder(ctx);\n },\n\n async [Symbol.asyncDispose]() {\n await adapter.dispose();\n },\n\n async update(table, key, changes, ttl) {\n checkDisposed();\n\n return timed(table, 'update', () => txCtx.update(table, key, changes, ttl));\n },\n\n async upsert(table, key, fn, ttl) {\n checkDisposed();\n\n return timed(table, 'upsert', () => txCtx.upsert(table, key, fn, ttl));\n },\n };\n\n return adapter;\n}\n\n/** IndexedDB is the only backend that can bind this callback to one native transaction. */\nexport function withIndexedDbTransactions<S extends AnySchema>(\n store: VaultStore<S>,\n batch: BatchImpl<S>,\n): IndexedDbVaultStore<S> {\n return Object.assign(store, { batch }) as IndexedDbVaultStore<S>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"adapter-core.js","names":[],"sources":["../src/adapter-core.ts"],"sourcesContent":["import { VaultDisposedError, VaultError, VaultScopeError } from './errors';\nimport { createObserverHub, getRecordKey } from './internal';\nimport { createQueryBuilder, type NativeRange, type QueryContext } from './query';\nimport { assertPositiveFinite } from './ttl';\nimport type {\n AnySchema,\n BaseAdapterOptions,\n DebugInfo,\n IndexedDbVaultStore,\n KeyOf,\n MetricsEvent,\n RecordOf,\n TransactionContext,\n VaultStore,\n} from './types';\n\n/* -------------------- Internal backend protocol -------------------- */\n\n/** @internal Full backend protocol implemented by each adapter. A single flat interface — no StorageCore/StorageBackend split. */\nexport type StorageBackend<S extends AnySchema, K extends keyof S & string = keyof S & string> = {\n clear<T extends K>(table: T): Promise<void>;\n /** Returns live (non-expired) record count for the table. */\n count<T extends K>(table: T): Promise<number>;\n delete<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;\n deleteMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<number>;\n dispose?(): Promise<void>;\n get<T extends K>(table: T, key: KeyOf<S, T>): Promise<RecordOf<S, T> | undefined>;\n getAll<T extends K>(table: T): Promise<RecordOf<S, T>[]>;\n /**\n * Optional: fetch all live primary keys without materialising full records.\n * When present, `keys()` uses this instead of `getAll()` + key extraction.\n * For tables with TTL, implementations may still fall back to `getAll()` to\n * filter expired keys accurately.\n */\n getAllKeys?<T extends K>(table: T): Promise<KeyOf<S, T>[]>;\n /**\n * Optional secondary-index range fetch. When present, `query()` activates `getIndexRange` in\n * the QueryContext so that a leading `equals`, `between`, or `startsWith` on an indexed field\n * uses an IDB index instead of a full-table scan.\n */\n getByIndexRange?<T extends K>(table: T, field: string, range: NativeRange): Promise<RecordOf<S, T>[]>;\n /**\n * Optional primary-key range fetch for push-down queries. When present, `query()` activates\n * `getRange` in the QueryContext so that a leading `equals`, `between`, or `startsWith` on the\n * key field avoids a full-table scan. Falls back to `getAll` when absent.\n */\n getByKeyRange?<T extends K>(table: T, range: NativeRange): Promise<RecordOf<S, T>[]>;\n /**\n * Optional: fetch multiple records by key in a single operation. Preserves key order; missing keys yield\n * `undefined`. Falls back to N individual `get` calls when absent.\n */\n getMany?<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<Array<RecordOf<S, T> | undefined>>;\n /** Raw record count including TTL-expired entries. Used only for debug(). */\n getRawCount?<T extends K>(table: T): Promise<number>;\n has<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;\n /**\n * Optional: prune all expired records across all tables in a single atomic operation.\n * When present, `pruneExpired()` delegates here instead of calling `pruneExpiredInTable` N times.\n */\n pruneAllExpired?(): Promise<Record<string, number>>;\n pruneExpiredInTable<T extends K>(table: T): Promise<number>;\n put<T extends K>(table: T, value: RecordOf<S, T>, ttl?: number): Promise<void>;\n putAll<T extends K>(table: T, values: RecordOf<S, T>[], ttl?: number): Promise<void>;\n};\n\n/** @internal */\nexport type BatchDeps<S extends AnySchema> = {\n notifyMutation: (table: keyof S & string) => void;\n validate: <K extends keyof S & string>(table: K, value: RecordOf<S, K>) => RecordOf<S, K>;\n};\n\n/** @internal */\nexport type BatchImpl<S extends AnySchema> = <K extends keyof S & string, R>(\n tables: readonly K[],\n fn: (tx: TransactionContext<S, K>) => Promise<R>,\n) => Promise<R>;\n\n/* -------------------- Helpers -------------------- */\n\nconst getTimestamp: () => number = typeof performance !== 'undefined' ? () => performance.now() : () => Date.now();\n\nexport function assertBatchTables(tables: readonly string[]): void {\n if (tables.length === 0) throw new VaultError('batch: declare at least one table');\n}\n\nfunction resolveTtl<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n table: K,\n ttl?: number,\n): number | undefined {\n if (ttl !== undefined) assertPositiveFinite(ttl, 'put/putAll');\n\n return ttl ?? schema[table].defaultTtl;\n}\n\nfunction verifyKey<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n table: K,\n expected: KeyOf<S, K>,\n value: RecordOf<S, K>,\n op: string,\n): void {\n const actual = getRecordKey(schema, table, value);\n\n if (actual !== expected) {\n throw new VaultError(\n `${op}: key field \"${schema[table].key}\" must be \"${String(expected)}\" but got \"${String(actual)}\" in table \"${table}\"`,\n );\n }\n}\n\nfunction getManyWithFallback<S extends AnySchema, K extends keyof S & string>(\n core: Pick<StorageBackend<S, K>, 'get' | 'getMany'>,\n table: K,\n keys: KeyOf<S, K>[],\n): Promise<Array<RecordOf<S, K> | undefined>> {\n if (core.getMany) return core.getMany(table, keys);\n\n return Promise.all(keys.map((k) => core.get(table, k)));\n}\n\nfunction buildQueryCtx<S extends AnySchema, K extends keyof S & string>(\n table: K,\n core: Pick<StorageBackend<S, K>, 'deleteMany' | 'getAll' | 'getByIndexRange' | 'getByKeyRange'>,\n schema: S,\n onMutate: (t: K) => void,\n): QueryContext<RecordOf<S, K>> {\n const entry = schema[table];\n const keyField = entry.key;\n const indexedFields = entry.indexes ? new Set(entry.indexes) : undefined;\n\n const deleteManyFn = async (records: RecordOf<S, K>[]): Promise<number> => {\n if (records.length === 0) return 0;\n\n const keys = records.map((r) => getRecordKey(schema, table, r));\n const deleted = await core.deleteMany(table, keys);\n\n if (deleted > 0) onMutate(table);\n\n return deleted;\n };\n\n return {\n deleteMany: deleteManyFn,\n getIndexRange: core.getByIndexRange\n ? (field, range) => core.getByIndexRange!(table, field, range) as Promise<RecordOf<S, K>[]>\n : undefined,\n getRange: core.getByKeyRange\n ? (range) => core.getByKeyRange!(table, range) as Promise<RecordOf<S, K>[]>\n : undefined,\n indexedFields,\n keyField,\n source: () => core.getAll(table),\n };\n}\n\n/* -------------------- buildTxContext -------------------- */\n\nexport function buildTxContext<S extends AnySchema, K extends keyof S & string>(\n schema: S,\n core: StorageBackend<S, K>,\n onMutate: (table: K) => void,\n validate?: <T extends K>(table: T, value: RecordOf<S, T>) => RecordOf<S, T>,\n scope?: ReadonlySet<string>,\n): TransactionContext<S, K> {\n const applyValidation = validate ?? ((_: K, v: RecordOf<S, K>) => v);\n\n const checkScope = scope\n ? (t: K): void => {\n if (!scope.has(t)) {\n throw new VaultScopeError(`table \"${t}\" is not part of this batch scope`);\n }\n }\n : (_t: K): void => {};\n\n return {\n async clear(table) {\n checkScope(table);\n\n const live = await core.count(table);\n\n await core.clear(table);\n\n if (live > 0) onMutate(table);\n },\n async count(table) {\n checkScope(table);\n\n return core.count(table);\n },\n async delete(table, key) {\n checkScope(table);\n\n const deleted = await core.delete(table, key);\n\n if (deleted) onMutate(table);\n\n return deleted;\n },\n async deleteMany(table, keys) {\n checkScope(table);\n\n const deleted = await core.deleteMany(table, keys);\n\n if (deleted > 0) onMutate(table);\n\n return deleted;\n },\n async entries(table) {\n checkScope(table);\n\n const records = await core.getAll(table);\n const keyField = schema[table].key;\n\n return records.map((r) => [(r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>, r]);\n },\n async get(table, key) {\n checkScope(table);\n\n return core.get(table, key);\n },\n async getAll(table) {\n checkScope(table);\n\n return core.getAll(table);\n },\n async getMany(table, keys) {\n checkScope(table);\n\n return getManyWithFallback(core, table, keys);\n },\n async getOrDefault(table, key, defaultFn, ttl) {\n checkScope(table);\n\n const existing = await core.get(table, key);\n\n if (existing !== undefined) return existing;\n\n const value = defaultFn();\n\n verifyKey(schema, table, key, value, 'getOrDefault: defaultFn()');\n await core.put(table, applyValidation(table, value), resolveTtl(schema, table, ttl));\n onMutate(table);\n\n return value;\n },\n async has(table, key) {\n checkScope(table);\n\n return core.has(table, key);\n },\n async isEmpty(table) {\n checkScope(table);\n\n return (await core.count(table)) === 0;\n },\n async keys(table, filter) {\n checkScope(table);\n\n if (filter) {\n const records = await core.getAll(table);\n const keyField = schema[table].key;\n\n return records.filter(filter).map((r) => (r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>);\n }\n\n // R1: prefer native getAllKeys when the backend supports it (e.g. IDB store.getAllKeys).\n if (core.getAllKeys) {\n return core.getAllKeys(table);\n }\n\n const records = await core.getAll(table);\n const keyField = schema[table].key;\n\n return records.map((r) => (r as Record<string, unknown>)[keyField] as KeyOf<S, typeof table>);\n },\n async put(table, value, ttl) {\n checkScope(table);\n\n await core.put(table, applyValidation(table, value), resolveTtl(schema, table, ttl));\n onMutate(table);\n },\n async putAll(table, values, ttl) {\n checkScope(table);\n\n if (values.length === 0) return;\n\n const toWrite = values.map((v) => applyValidation(table, v));\n\n await core.putAll(table, toWrite, resolveTtl(schema, table, ttl));\n onMutate(table);\n },\n query(table) {\n checkScope(table);\n\n return createQueryBuilder(buildQueryCtx(table, core, schema, onMutate));\n },\n async update(table, key, changes, ttl) {\n checkScope(table);\n\n const current = await core.get(table, key);\n\n if (!current) return undefined;\n\n const merged = { ...current, ...changes } as RecordOf<S, typeof table>;\n\n verifyKey(schema, table, key, merged, 'update');\n await core.put(table, applyValidation(table, merged), resolveTtl(schema, table, ttl));\n onMutate(table);\n\n return merged;\n },\n async upsert(table, key, fn, ttl) {\n checkScope(table);\n\n const existing = await core.get(table, key);\n const value = fn(existing);\n\n verifyKey(schema, table, key, value, 'upsert: fn()');\n await core.put(table, applyValidation(table, value), resolveTtl(schema, table, ttl));\n onMutate(table);\n\n return value;\n },\n };\n}\n\n/* -------------------- buildAdapterOps -------------------- */\n\n/** Builds the portable surface first; IndexedDB opts into transactions at its factory boundary. */\nexport function buildAdapterOps<S extends AnySchema>(\n schema: S,\n core: StorageBackend<S>,\n options?: BaseAdapterOptions<S> & {\n onCrossTabMessage?: (notify: (table: keyof S & string) => void) => (() => void) | undefined;\n onMutation?: (table: keyof S & string) => void;\n onTransactions?: (deps: BatchDeps<S>) => void;\n },\n): VaultStore<S> {\n const { logger, onMetrics, validators } = options ?? {};\n\n const observers = createObserverHub<S>(\n (table) => core.getAll(table),\n logger\n ? (err) =>\n logger.error('[vault] observer notification failed', err instanceof Error ? err : new Error(String(err)))\n : undefined,\n );\n\n const notifyMutation = (table: keyof S & string): void => {\n observers.notify(table);\n options?.onMutation?.(table);\n };\n\n // Cross-tab notifications must NOT call onMutation (which would re-publish to BroadcastChannel,\n // creating an infinite loop). They only notify local observers.\n const notifyExternal = (table: keyof S & string): void => {\n observers.notify(table);\n };\n\n // R6: Resolve optional backend capabilities once at construction — no per-call checks.\n const pruneAll = core.pruneAllExpired?.bind(core);\n const rawCountFn = core.getRawCount?.bind(core);\n\n const disconnectExternal = options?.onCrossTabMessage?.(notifyExternal) ?? undefined;\n\n const validate = <K extends keyof S & string>(table: K, value: RecordOf<S, K>): RecordOf<S, K> => {\n const validator = validators?.[table];\n\n if (!validator) return value;\n\n try {\n return validator.parse(value) as RecordOf<S, K>;\n } catch (err) {\n throw new VaultError(`validation failed for table \"${table}\"`, { cause: err });\n }\n };\n\n /* Timed wrapper — only wraps when onMetrics is present, otherwise zero overhead. */\n const timed = <T>(table: string, op: MetricsEvent['operation'], fn: () => Promise<T>): Promise<T> => {\n if (!onMetrics) return fn();\n\n const start = getTimestamp();\n\n return fn().finally(() => onMetrics({ duration: getTimestamp() - start, operation: op, table }));\n };\n\n options?.onTransactions?.({ notifyMutation, validate });\n\n const txCtx = buildTxContext<S, keyof S & string>(schema, core, notifyMutation, validate);\n\n const disposeController = new AbortController();\n let disposed = false;\n\n const checkDisposed = (): void => {\n if (disposed) throw new VaultDisposedError();\n };\n\n const adapter: VaultStore<S> = {\n async clear(table) {\n checkDisposed();\n await timed(table, 'clear', () => txCtx.clear(table));\n },\n\n async count(table) {\n checkDisposed();\n\n return timed(table, 'count', () => core.count(table));\n },\n\n async debug() {\n checkDisposed();\n\n const tableNames = Object.keys(schema) as Array<keyof S & string>;\n const entries = await Promise.all(\n tableNames.map(async (name) => {\n // getRawCount must be called before core.count() because count() has\n // lazy-eviction side effects (deletes expired entries from the store).\n const raw = rawCountFn ? await rawCountFn(name) : undefined;\n const live = await core.count(name);\n\n const expiredCount = raw !== undefined ? Math.max(0, raw - live) : 0;\n\n return { expiredCount, name, recordCount: live };\n }),\n );\n\n return { tables: entries } as DebugInfo<S>;\n },\n\n async delete(table, key) {\n checkDisposed();\n\n return timed(table, 'delete', () => txCtx.delete(table, key));\n // count cache invalidated by notifyMutation inside txCtx.delete\n },\n\n async deleteMany(table, keys) {\n checkDisposed();\n\n return timed(table, 'deleteMany', () => txCtx.deleteMany(table, keys));\n // count cache invalidated by notifyMutation inside txCtx.deleteMany\n },\n\n get disposalSignal(): AbortSignal {\n return disposeController.signal;\n },\n\n async dispose() {\n if (disposed) return;\n\n disposed = true;\n disposeController.abort();\n disconnectExternal?.();\n observers.dispose();\n await core.dispose?.();\n },\n\n get disposed(): boolean {\n return disposed;\n },\n\n async entries(table) {\n checkDisposed();\n\n return timed(table, 'entries', () => txCtx.entries(table));\n },\n\n async get(table, key) {\n checkDisposed();\n\n return timed(table, 'get', () => txCtx.get(table, key));\n },\n\n async getAll(table) {\n checkDisposed();\n\n return timed(table, 'getAll', () => txCtx.getAll(table));\n },\n\n async getMany(table, keys) {\n checkDisposed();\n\n return timed(table, 'getMany', () => txCtx.getMany(table, keys));\n },\n\n async getOrDefault(table, key, defaultFn, ttl) {\n checkDisposed();\n\n return timed(table, 'getOrDefault', () => txCtx.getOrDefault(table, key, defaultFn, ttl));\n },\n\n async has(table, key) {\n checkDisposed();\n\n return timed(table, 'has', () => txCtx.has(table, key));\n },\n\n async isEmpty(table) {\n checkDisposed();\n\n return timed(table, 'isEmpty', async () => (await core.count(table)) === 0);\n },\n\n async keys(table, filter) {\n checkDisposed();\n\n return timed(table, 'keys', () => txCtx.keys(table, filter));\n },\n\n observe(table, listener, opts) {\n checkDisposed();\n\n return observers.observe(table, listener, opts);\n },\n\n async pruneExpired() {\n checkDisposed();\n\n if (pruneAll) {\n const result = await pruneAll();\n\n return result as { [K in keyof S & string]: number };\n }\n\n const tableNames = Object.keys(schema) as Array<keyof S & string>;\n const pairs = await Promise.all(\n tableNames.map(async (name) => {\n const pruned = await core.pruneExpiredInTable(name);\n\n return [name, pruned] as const;\n }),\n );\n\n return Object.fromEntries(pairs) as { [K in keyof S & string]: number };\n },\n\n async put(table, value, ttl) {\n checkDisposed();\n await timed(table, 'put', () => txCtx.put(table, value, ttl));\n // count cache invalidated via notifyMutation inside txCtx.put\n },\n\n async putAll(table, values, ttl) {\n checkDisposed();\n await timed(table, 'putAll', () => txCtx.putAll(table, values, ttl));\n // count cache invalidated via notifyMutation inside txCtx.putAll\n },\n\n query(table) {\n checkDisposed();\n\n const ctx = buildQueryCtx(table, core, schema, notifyMutation);\n\n if (onMetrics) {\n return createQueryBuilder({\n ...ctx,\n deleteMany: ctx.deleteMany\n ? (records) => timed(table, 'queryDelete', () => ctx.deleteMany!(records))\n : undefined,\n source: () => timed(table, 'query', () => core.getAll(table)),\n });\n }\n\n return createQueryBuilder(ctx);\n },\n\n async [Symbol.asyncDispose]() {\n await adapter.dispose();\n },\n\n async update(table, key, changes, ttl) {\n checkDisposed();\n\n return timed(table, 'update', () => txCtx.update(table, key, changes, ttl));\n },\n\n async upsert(table, key, fn, ttl) {\n checkDisposed();\n\n return timed(table, 'upsert', () => txCtx.upsert(table, key, fn, ttl));\n },\n };\n\n return adapter;\n}\n\n/** IndexedDB is the only backend that can bind this callback to one native transaction. */\nexport function withIndexedDbTransactions<S extends AnySchema>(\n store: VaultStore<S>,\n batch: BatchImpl<S>,\n): IndexedDbVaultStore<S> {\n return Object.assign(store, { batch }) as IndexedDbVaultStore<S>;\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../errors.cjs"),t=require("../ttl.cjs"),n=require("../internal.cjs"),r=require("../adapter-core.cjs");function i(t){return new Promise((n,r)=>{t.onsuccess=()=>n(t.result),t.onerror=()=>r(t.error??new e.VaultError(`IndexedDB request failed`))})}function
|
|
1
|
+
const e=require("../errors.cjs"),t=require("../ttl.cjs"),n=require("../internal.cjs"),r=require("../adapter-core.cjs");function i(e){return({db:t,tx:n})=>{for(let r of e)switch(r.type){case`addIndex`:{let e=n.objectStore(r.table);e.indexNames.contains(r.field)||e.createIndex(r.field,`value.${r.field}`);break}case`addTable`:t.objectStoreNames.contains(r.name)||t.createObjectStore(r.name);break;case`removeIndex`:{let e=n.objectStore(r.table);e.indexNames.contains(r.field)&&e.deleteIndex(r.field);break}case`removeTable`:t.objectStoreNames.contains(r.name)&&t.deleteObjectStore(r.name)}}}function a(t){return new Promise((n,r)=>{t.onsuccess=()=>n(t.result),t.onerror=()=>r(t.error??new e.VaultError(`IndexedDB request failed`))})}function o(t,n,r){let i=r instanceof Error&&r.message?`: ${r.message}`:``;return new e.VaultError(`${n} on "${t}"${i}`,{cause:r})}function s(e,t,n){return new Promise((r,i)=>{let a,s;Promise.resolve().then(n).then(e=>{a=e}).catch(t=>{s=t;try{e.abort()}catch{}});let c=(e,n=`transaction failed`)=>{s instanceof Error?i(s):i(o(t,n,s??e))};e.oncomplete=()=>{if(s){c(void 0);return}r(a)},e.onerror=()=>i(o(t,`transaction error`,e.error)),e.onabort=()=>c(e.error,`transaction aborted`)})}async function c(e,t){let n=await a(e.getAll()),r=[];for(let e of n){let n=t(e);n!==void 0&&r.push(n)}return r}async function l(e,t,n,r){let i=e.index(t),o=n.type===`eq`?IDBKeyRange.only(n.value):n.type===`between`?IDBKeyRange.bound(n.lower,n.upper):IDBKeyRange.bound(n.prefix,`${n.prefix}\uffff`),s=await a(i.getAll(o)),c=[];for(let e of s){let t=r(e);t!==void 0&&c.push(t)}return c}async function u(e,t,n){let r=await a(e.get(t));if(r!=null)return n(r)}async function d(e,t,n){return await u(e,t,n)!==void 0}async function f(e,t,n){let r=await d(e,t,n);return await a(e.delete(t)),r}async function p(e,t,n){return t.length===0?0:(await Promise.all(t.map(t=>f(e,t,n)))).filter(Boolean).length}async function m(e,t,n,r,i){await a(e.put(r(n,i),t))}function h(n){return new Promise((r,i)=>{let a=0,o=n.openCursor();o.onerror=()=>i(o.error??new e.VaultError(`IndexedDB cursor failed during prune`)),o.onsuccess=()=>{let e=o.result;if(!e){r(a);return}let n=t.parseStored(e.value);(!n||t.isExpired(n.expiresAt))&&(e.delete(),a+=1),e.continue()}})}function g(t,n){return{[Symbol.asyncIterator](){let r=t.openCursor(),i={type:`idle`},a=e=>{if(i.type===`waiting`){let{resolve:t}=i;i=e.done?{type:`done`}:{type:`idle`},t(e)}else i={result:e,type:`buffered`}};return r.onerror=()=>{let t=r.error??new e.VaultError(`IndexedDB cursor iteration failed`);if(i.type===`waiting`){let{reject:e}=i;i={type:`done`},e(t)}else i={error:t,type:`error`}},r.onsuccess=()=>{let e=r.result;if(!e){a({done:!0,value:void 0});return}let t=n(e.value);e.continue(),t!==void 0&&a({done:!1,value:t})},{next(){if(i.type===`error`){let{error:e}=i;return i={type:`done`},Promise.reject(e)}if(i.type===`buffered`){let{result:e}=i;return i=e.done?{type:`done`}:{type:`idle`},Promise.resolve(e)}return i.type===`done`?Promise.resolve({done:!0,value:void 0}):new Promise((e,t)=>{i={reject:t,resolve:e,type:`waiting`}})},return(e){return i.type===`waiting`&&i.resolve({done:!0,value:void 0}),i={type:`done`},Promise.resolve({done:!0,value:e})},throw(e){return i.type===`waiting`&&i.reject(e),i={type:`done`},Promise.reject(e)}}}}}function _(e,t,r,i){let o=e=>t.objectStore(e);return{clear:async e=>{await a(o(e).clear())},count:async e=>(await a(o(e).getAll())).filter(e=>r(e)!==void 0).length,delete:(e,t)=>f(o(e),n.encodeVaultKey(t),r),deleteMany:(e,t)=>p(o(e),t.map(n.encodeVaultKey),r),get:(e,t)=>u(o(e),n.encodeVaultKey(t),r),getAll:e=>c(o(e),r),getByIndexRange:(e,t,n)=>l(o(e),t,n,r),getMany:(e,t)=>Promise.all(t.map(t=>u(o(e),n.encodeVaultKey(t),r))),has:(e,t)=>d(o(e),n.encodeVaultKey(t),r),pruneExpiredInTable:e=>h(o(e)),put(t,r,a){return m(o(t),n.encodeVaultKey(n.getRecordKey(e,t,r)),r,i,a)},putAll(t,r,a){return Promise.all(r.map(r=>m(o(t),n.encodeVaultKey(n.getRecordKey(e,t,r)),r,i,a))).then(()=>void 0)}}}function v(i){let{logger:o,migrate:v,name:y,onMetrics:b,schema:x,validators:S,version:C=1}=i;if(!Number.isInteger(C)||C<1)throw new e.VaultError(`createIndexedDB: version must be a positive integer, got ${String(C)}`);let w=e=>{let n=t.parseStored(e);return!n||t.isExpired(n.expiresAt)?void 0:n.value},T=(e,t)=>t===void 0?{value:e}:{expiresAt:Date.now()+t,value:e},E=typeof BroadcastChannel<`u`?new BroadcastChannel(`vault:${y}`):void 0;!E&&o&&o.error(`[vault] BroadcastChannel unavailable — cross-tab sync disabled for "${y}"`);let D=null,O=null,k=!1,A=(e,t)=>{for(let[n,r]of Object.entries(x)){let i;i=e.objectStoreNames.contains(n)?t.objectStore(n):e.createObjectStore(n);let a=r.indexes??[];for(let e of a)i.indexNames.contains(e)||i.createIndex(e,`value.${e}`)}},j=async()=>(O||=new Promise((t,n)=>{let r=indexedDB.open(y,C);r.onupgradeneeded=t=>{let i=r.result,a=r.transaction;if(v)try{v({db:i,newVersion:t.newVersion??null,oldVersion:t.oldVersion,tx:a})}catch(t){try{a.abort()}catch{}n(new e.VaultMigrationError(`migration failed for "${y}"`,{cause:t}));return}A(i,a)},r.onsuccess=()=>{if(k){r.result.close(),t();return}let e=r.result;e.onversionchange=()=>{e.close(),D=null,O=null},D=e,t()},r.onerror=()=>{O=null,n(new e.VaultError(`failed to open "${y}"`,{cause:r.error}))}}),O),M=async(t,n,r)=>{if(k||(D||await j(),!D))throw new e.VaultDisposedError(`"${y}" is disposed`);let i=String(t),a=D.transaction(i,n);return s(a,`${y}/${i}`,()=>r(a.objectStore(i)))},N=async()=>{if(k||(D||await j(),!D))throw new e.VaultDisposedError(`"${y}" is disposed`);return D},P=e=>{E?.postMessage({table:String(e)})},F={clear:e=>M(e,`readwrite`,e=>a(e.clear()).then(()=>void 0)),count:e=>M(e,`readonly`,async e=>(await a(e.getAll())).filter(e=>w(e)!==void 0).length),delete:(e,t)=>M(e,`readwrite`,e=>f(e,n.encodeVaultKey(t),w)),deleteMany:(e,t)=>t.length===0?Promise.resolve(0):M(e,`readwrite`,e=>p(e,t.map(n.encodeVaultKey),w)),async dispose(){k=!0,E?.close(),O&&await O.catch(()=>{}),D?.close(),D=null,O=null},get:(e,t)=>M(e,`readonly`,e=>u(e,n.encodeVaultKey(t),w)),getAll:e=>M(e,`readonly`,e=>c(e,w)),getAllKeys:e=>M(e,`readonly`,async t=>{let n=await c(t,w),r=x[e].key;return n.map(e=>e[r])}),getByIndexRange:(e,t,n)=>M(e,`readonly`,e=>l(e,t,n,w)),getMany:(e,t)=>t.length===0?Promise.resolve([]):M(e,`readonly`,e=>Promise.all(t.map(t=>u(e,n.encodeVaultKey(t),w)))),getRawCount:e=>M(e,`readonly`,e=>a(e.count())),has:(e,t)=>M(e,`readonly`,e=>d(e,n.encodeVaultKey(t),w)),async pruneAllExpired(){let e=await N(),t=Object.keys(x),n=e.transaction(t,`readwrite`),r=await s(n,`${y}/pruneAll`,()=>Promise.all(t.map(async e=>[e,await h(n.objectStore(e))])));return Object.fromEntries(r)},pruneExpiredInTable:e=>M(e,`readwrite`,e=>h(e)),put(e,t,r){let i=n.encodeVaultKey(n.getRecordKey(x,e,t));return M(e,`readwrite`,e=>m(e,i,t,T,r))},putAll(e,t,r){return M(e,`readwrite`,i=>Promise.all(t.map(t=>m(i,n.encodeVaultKey(n.getRecordKey(x,e,t)),t,T,r))).then(()=>void 0))}},I=async(e,t,n,i)=>{r.assertBatchTables(e);let a=(await N()).transaction([...e],`readwrite`),o=new Set,c=_(x,a,w,T),l=new Set(e),u=r.buildTxContext(x,c,e=>o.add(e),i,l),d=await s(a,y,()=>t(u));for(let e of o)n(e);return d},L,R={...r.buildAdapterOps(x,F,{logger:o,onCrossTabMessage(e){if(E)return E.onmessage=t=>{let n=t.data?.table;!n||!Object.hasOwn(x,n)||e(n)},()=>{E.onmessage=null}},onMetrics:b,onMutation:P,onTransactions:e=>{L=(t,n)=>I(t,n,e.notifyMutation,e.validate)},schema:x,validators:S}),iterate(t){if(k)throw new e.VaultDisposedError(`"${y}" is disposed`);let n=async()=>{if(D||await j(),!D||k)throw new e.VaultDisposedError(`"${y}" is disposed`);return g(D.transaction(String(t),`readonly`).objectStore(String(t)),w)},r,i=()=>n().then(e=>(r=e[Symbol.asyncIterator](),r));return{[Symbol.asyncIterator](){return{next(){return r?r.next():i().then(e=>e.next())},return(e){return r?r.return?.(e)??Promise.resolve({done:!0,value:e}):Promise.resolve({done:!0,value:e})},throw(e){return r?r.throw?.(e)??Promise.reject(e):Promise.reject(e)}}}}}};if(!L)throw new e.VaultError(`IndexedDB transaction capability was not initialized`);return r.withIndexedDbTransactions(R,L)}exports.createIndexedDB=v,exports.defineMigration=i;
|
|
2
2
|
//# sourceMappingURL=indexeddb.cjs.map
|