@vielzeug/vault 2.0.0 → 2.1.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.
Files changed (61) hide show
  1. package/README.md +42 -6
  2. package/dist/adapter-core.cjs +1 -1
  3. package/dist/adapter-core.cjs.map +1 -1
  4. package/dist/adapter-core.d.ts +1 -0
  5. package/dist/adapter-core.d.ts.map +1 -1
  6. package/dist/adapter-core.js +98 -108
  7. package/dist/adapter-core.js.map +1 -1
  8. package/dist/adapters/indexeddb.cjs +1 -1
  9. package/dist/adapters/indexeddb.cjs.map +1 -1
  10. package/dist/adapters/indexeddb.d.ts.map +1 -1
  11. package/dist/adapters/indexeddb.js +114 -113
  12. package/dist/adapters/indexeddb.js.map +1 -1
  13. package/dist/adapters/memory.cjs +1 -1
  14. package/dist/adapters/memory.js +59 -59
  15. package/dist/adapters/sqlite.cjs +42 -0
  16. package/dist/adapters/sqlite.cjs.map +1 -0
  17. package/dist/adapters/sqlite.d.ts +43 -0
  18. package/dist/adapters/sqlite.d.ts.map +1 -0
  19. package/dist/adapters/sqlite.js +401 -0
  20. package/dist/adapters/sqlite.js.map +1 -0
  21. package/dist/adapters/webstorage.cjs +1 -1
  22. package/dist/adapters/webstorage.js +25 -25
  23. package/dist/index.cjs +1 -1
  24. package/dist/index.d.ts +1 -6
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +4 -8
  27. package/dist/indexeddb.cjs +1 -0
  28. package/dist/indexeddb.d.ts +5 -0
  29. package/dist/indexeddb.d.ts.map +1 -0
  30. package/dist/indexeddb.js +3 -0
  31. package/dist/local-storage.cjs +1 -0
  32. package/dist/local-storage.d.ts +2 -0
  33. package/dist/local-storage.d.ts.map +1 -0
  34. package/dist/local-storage.js +2 -0
  35. package/dist/memory.cjs +1 -0
  36. package/dist/memory.d.ts +2 -0
  37. package/dist/memory.d.ts.map +1 -0
  38. package/dist/memory.js +2 -0
  39. package/dist/session-storage.cjs +1 -0
  40. package/dist/session-storage.d.ts +2 -0
  41. package/dist/session-storage.d.ts.map +1 -0
  42. package/dist/session-storage.js +2 -0
  43. package/dist/sqlite.cjs +1 -0
  44. package/dist/sqlite.d.ts +3 -0
  45. package/dist/sqlite.d.ts.map +1 -0
  46. package/dist/sqlite.js +2 -0
  47. package/dist/types.cjs.map +1 -1
  48. package/dist/types.d.ts +8 -2
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/types.js.map +1 -1
  51. package/dist/vault.cjs +1 -1
  52. package/dist/vault.cjs.map +1 -1
  53. package/dist/vault.iife.js +1 -1
  54. package/dist/vault.iife.js.map +1 -1
  55. package/dist/vault.js +1 -1
  56. package/dist/vault.js.map +1 -1
  57. package/package.json +48 -3
  58. package/dist/streaming.d.ts +0 -2
  59. package/dist/streaming.d.ts.map +0 -1
  60. package/dist/versioned-codec.d.ts +0 -2
  61. package/dist/versioned-codec.d.ts.map +0 -1
package/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # @vielzeug/vault
2
2
 
3
- > Typed browser storage with portable string/number keys, TTL, queries, and snapshot observation.
3
+ > Typed browser and SQLite storage with portable string/number keys, TTL, queries, and snapshot observation.
4
4
 
5
5
  ## Quick start
6
6
 
7
7
  ```ts
8
- import { createLocalStorage, table } from '@vielzeug/vault';
8
+ import { table } from '@vielzeug/vault';
9
+ import { createLocalStorage } from '@vielzeug/vault/local-storage';
9
10
 
10
11
  type User = { id: number; name: string };
11
12
  const store = createLocalStorage({ name: 'app', schema: { users: table<User>('id') } });
@@ -15,14 +16,49 @@ const stop = store.observe('users', (users) => console.log(users));
15
16
  stop();
16
17
  ```
17
18
 
18
- `createMemory`, `createLocalStorage`, and `createSessionStorage` return the portable `VaultStore<S>` API. `createIndexedDB` returns `IndexedDbVaultStore<S>`, adding cursor `iterate()` and atomic `batch()` transactions.
19
+ The root entry provides schemas, shared types, errors, TTL, and pruning. Import one focused adapter only when needed: `/memory`, `/local-storage`, `/session-storage`, `/indexeddb`, or `/sqlite`.
19
20
 
20
- All adapters store a fixed `{ value, expiresAt? }` envelope. Number and string primary keys are tagged internally, so `1` and `'1'` are distinct everywhere.
21
+ `createMemory`, `createLocalStorage`, and `createSessionStorage` return the portable `VaultStore<S>` API. `createIndexedDB` and `createSQLite` add cursor or keyset `iterate()` plus atomic `batch()` transactions.
21
22
 
22
- ## Major migration
23
+ ## SQLite
24
+
25
+ Use the driver-neutral `@vielzeug/vault/sqlite` subpath outside the browser. The caller opens and owns the connection unless `closeOnDispose` is enabled.
26
+
27
+ ```ts
28
+ import { DatabaseSync } from 'node:sqlite';
29
+
30
+ import { table } from '@vielzeug/vault';
31
+ import { createSQLite } from '@vielzeug/vault/sqlite';
32
+
33
+ const store = createSQLite({
34
+ database: new DatabaseSync('app.db'),
35
+ name: 'app',
36
+ schema: { users: table<{ id: number; name: string }>('id') },
37
+ });
38
+ ```
39
+
40
+ Node's `node:sqlite` API remains experimental. Bun's `Database` is compatible directly; configure WAL in application code when needed. Deno has no native SQLite API, but `jsr:@db/sqlite`'s `Database` is compatible directly when its FFI, filesystem, and environment permissions are granted.
41
+
42
+ SQLite values must be JSON-compatible plain objects. SQLite operations execute synchronously beneath Vault's Promise API, so use a worker or isolate for large scans and writes on latency-sensitive paths. During a `batch()` callback, use only `tx.*`; calls on any store sharing that connection reject. Stores observe their own and same-connection Vault writes after commit; direct SQL and other processes are not observed.
43
+
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
+
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
23
59
 
24
60
  - Replace `Adapter` with `VaultStore`; replace `IndexedDbAdapter` with `IndexedDbVaultStore`.
25
- - `batch()` is now IndexedDB-only. Use `createIndexedDB` where atomic work is required.
61
+ - `batch()` is available only on `createIndexedDB` and `createSQLite`. Use one of those adapters where atomic work is required.
26
62
  - Use `observe()` for reactivity. `watch`, `observeMany`, constructor `signals`, and `toReadableStream` were removed.
27
63
  - Custom codecs, `defaultCodec`, and `createVersionedCodec` were removed; migrate existing data to the fixed envelope before upgrading.
28
64
 
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs"),t=require("./internal.cjs"),n=require("./query.cjs"),r=require("./ttl.cjs");var i=typeof performance<`u`?()=>performance.now():()=>Date.now();function a(e,t,n){return n!==void 0&&r.assertTtlMs(n,`put/putAll`),n??e[t].defaultTtl}function o(n,r,i,a,o){let s=t.getRecordKey(n,r,a);if(s!==i)throw new e.VaultError(`${o}: key field "${n[r].key}" must be "${String(i)}" but got "${String(s)}" in table "${r}"`)}function s(e,t,n){return e.getMany?e.getMany(t,n):Promise.all(n.map(n=>e.get(t,n)))}function c(e,n,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(n=>t.getRecordKey(r,e,n)),s=await n.deleteMany(e,o);return s>0&&i(e),s},getIndexRange:n.getByIndexRange?(t,r)=>n.getByIndexRange(e,t,r):void 0,getRange:n.getByKeyRange?t=>n.getByKeyRange(e,t):void 0,indexedFields:s,keyField:o,source:()=>n.getAll(e)}}function l(t,r,i,l,u){let d=l??((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 r.count(e);await r.clear(e),t>0&&i(e)},async count(e){return f(e),r.count(e)},async delete(e,t){f(e);let n=await r.delete(e,t);return n&&i(e),n},async deleteMany(e,t){f(e);let n=await r.deleteMany(e,t);return n>0&&i(e),n},async entries(e){f(e);let n=await r.getAll(e),i=t[e].key;return n.map(e=>[e[i],e])},async get(e,t){return f(e),r.get(e,t)},async getAll(e){return f(e),r.getAll(e)},async getMany(e,t){return f(e),s(r,e,t)},async getOrDefault(e,n,s,c){f(e);let l=await r.get(e,n);if(l!==void 0)return l;let u=s();return o(t,e,n,u,`getOrDefault: defaultFn()`),await r.put(e,d(e,u),a(t,e,c)),i(e),u},async has(e,t){return f(e),r.has(e,t)},async isEmpty(e){return f(e),await r.count(e)===0},async keys(e,n){if(f(e),n){let i=await r.getAll(e),a=t[e].key;return i.filter(n).map(e=>e[a])}if(r.getAllKeys)return r.getAllKeys(e);let i=await r.getAll(e),a=t[e].key;return i.map(e=>e[a])},async put(e,n,o){f(e),await r.put(e,d(e,n),a(t,e,o)),i(e)},async putAll(e,n,o){if(f(e),n.length===0)return;let s=n.map(t=>d(e,t));await r.putAll(e,s,a(t,e,o)),i(e)},query(e){return f(e),n.createQueryBuilder(c(e,r,t,i))},async update(e,n,s,c){f(e);let l=await r.get(e,n);if(!l)return;let u={...l,...s};return o(t,e,n,u,`update`),await r.put(e,d(e,u),a(t,e,c)),i(e),u},async upsert(e,n,s,c){f(e);let l=s(await r.get(e,n));return o(t,e,n,l,`upsert: fn()`),await r.put(e,d(e,l),a(t,e,c)),i(e),l}}}function u(r,a,o){let{logger:s,onMetrics:u,validators:d}=o??{},f=t.createObserverHub(e=>a.getAll(e),s?e=>s.error(e instanceof Error?e:Error(String(e)),`[vault] observer notification failed`):void 0),p=new Map,m=async e=>{if(p.has(e))return p.get(e);let t=await a.count(e);return p.set(e,t),t},h=e=>{p.delete(e),f.notify(e),o?.onMutation?.(e)},g=e=>{p.delete(e),f.notify(e)},_=a.pruneAllExpired?.bind(a),v=a.getRawCount?.bind(a),y=o?.onCrossTabMessage?.(g)??void 0,b=(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})}},x=(e,t,n)=>{if(!u)return n();let r=i();return n().finally(()=>u({duration:i()-r,operation:t,table:e}))};o?.onTransactions?.({notifyMutation:h,validate:b});let S=l(r,a,h,b),C=new AbortController,w=!1,T=()=>{if(w)throw new e.VaultDisposedError},E={async clear(e){T(),await x(e,`clear`,()=>S.clear(e)),p.set(e,0)},async count(e){return T(),x(e,`count`,()=>m(e))},async debug(){T();let e=Object.keys(r);return{tables:await Promise.all(e.map(async e=>{let t=v?await v(e):void 0,n=await a.count(e);return p.set(e,n),{expiredCount:t===void 0?0:Math.max(0,t-n),name:e,recordCount:n}}))}},async delete(e,t){return T(),x(e,`delete`,()=>S.delete(e,t))},async deleteMany(e,t){return T(),x(e,`deleteMany`,()=>S.deleteMany(e,t))},get disposalSignal(){return C.signal},async dispose(){w||(w=!0,C.abort(),y?.(),f.dispose(),await a.dispose?.())},get disposed(){return w},async entries(e){return T(),x(e,`entries`,()=>S.entries(e))},async get(e,t){return T(),x(e,`get`,()=>S.get(e,t))},async getAll(e){return T(),x(e,`getAll`,()=>S.getAll(e))},async getMany(e,t){return T(),x(e,`getMany`,()=>S.getMany(e,t))},async getOrDefault(e,t,n,r){return T(),x(e,`getOrDefault`,()=>S.getOrDefault(e,t,n,r))},async has(e,t){return T(),x(e,`has`,()=>S.has(e,t))},async isEmpty(e){return T(),x(e,`isEmpty`,async()=>await m(e)===0)},async keys(e,t){return T(),x(e,`keys`,()=>S.keys(e,t))},observe(e,t,n){return T(),f.observe(e,t,n)},async pruneExpired(e){T();let t=e??Object.keys(r);if(!e&&_){let e=await _();for(let[t,n]of Object.entries(e))n>0&&p.delete(t);return e}let n=await Promise.all(t.map(async e=>{let t=await a.pruneExpiredInTable(e);return t>0&&p.delete(e),[e,t]}));return Object.fromEntries(n)},async put(e,t,n){T(),await x(e,`put`,()=>S.put(e,t,n))},async putAll(e,t,n){T(),await x(e,`putAll`,()=>S.putAll(e,t,n))},query(e){T();let t=c(e,a,r,h);return u?n.createQueryBuilder({...t,deleteMany:t.deleteMany?n=>x(e,`queryDelete`,()=>t.deleteMany(n)):void 0,source:()=>x(e,`query`,()=>a.getAll(e))}):n.createQueryBuilder(t)},async[Symbol.asyncDispose](){await E.dispose()},async update(e,t,n,r){return T(),x(e,`update`,()=>S.update(e,t,n,r))},async upsert(e,t,n,r){return T(),x(e,`upsert`,()=>S.upsert(e,t,n,r))}};return E}function d(e,t){return Object.assign(e,{batch:t})}exports.buildAdapterOps=u,exports.buildTxContext=l,exports.withIndexedDbTransactions=d;
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.assertTtlMs(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(e instanceof Error?e:Error(String(e)),`[vault] observer notification failed`):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 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\nimport { VaultDisposedError, VaultError, VaultScopeError } from './errors';\nimport { createObserverHub, getRecordKey } from './internal';\nimport { createQueryBuilder, type NativeRange, type QueryContext } from './query';\nimport { assertTtlMs } from './ttl';\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\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 // Per-table live-count cache. Populated lazily on first count() call.\n // Invalidated on every mutation via notifyMutation.\n const countCache = new Map<string, number>();\n\n const getCachedCount = async (table: keyof S & string): Promise<number> => {\n if (countCache.has(table)) return countCache.get(table)!;\n\n const n = await core.count(table);\n\n countCache.set(table, n);\n\n return n;\n };\n\n const notifyMutation = (table: keyof S & string): void => {\n countCache.delete(table); // invalidate on any mutation\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 need to invalidate the count cache and notify observers.\n const notifyExternal = (table: keyof S & string): void => {\n countCache.delete(table);\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 countCache.set(table, 0); // we know count is 0 after clear\n },\n\n async count(table) {\n checkDisposed();\n\n return timed(table, 'count', () => getCachedCount(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 // Warm the count cache so subsequent adapter.count() calls don't re-hit the backend.\n countCache.set(name, live);\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 getCachedCount(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(tables?: readonly (keyof S & string)[]) {\n checkDisposed();\n\n const tableNames = tables ?? (Object.keys(schema) as Array<keyof S & string>);\n\n if (!tables && pruneAll) {\n const result = await pruneAll();\n\n for (const [name, count] of Object.entries(result)) {\n if (count > 0) countCache.delete(name);\n }\n\n return result as { [K in keyof S & string]: number };\n }\n\n const pairs = await Promise.all(\n tableNames.map(async (name) => {\n const pruned = await core.pruneExpiredInTable(name);\n\n if (pruned > 0) countCache.delete(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 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\nimport { VaultDisposedError, VaultError, VaultScopeError } from './errors';\nimport { createObserverHub, getRecordKey } from './internal';\nimport { createQueryBuilder, type NativeRange, type QueryContext } from './query';\nimport { assertTtlMs } from './ttl';\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,4 +1,5 @@
1
1
  import type { AnySchema, BaseAdapterOptions, IndexedDbVaultStore, RecordOf, TransactionContext, VaultStore } from './types';
2
+ export declare function assertBatchTables(tables: readonly string[]): void;
2
3
  export declare function buildTxContext<S extends AnySchema, K extends keyof S & string>(schema: S, core: StorageBackend<S, K>, onMutate: (table: K) => void, validate?: <T extends K>(table: T, value: RecordOf<S, T>) => RecordOf<S, T>, scope?: ReadonlySet<string>): TransactionContext<S, K>;
3
4
  /** Builds the portable surface first; IndexedDB opts into transactions at its factory boundary. */
4
5
  export declare function buildAdapterOps<S extends AnySchema>(schema: S, core: StorageBackend<S>, options?: BaseAdapterOptions<S> & {
@@ -1 +1 @@
1
- {"version":3,"file":"adapter-core.d.ts","sourceRoot":"","sources":["../src/adapter-core.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,kBAAkB,EAElB,mBAAmB,EAGnB,QAAQ,EACR,kBAAkB,EAElB,UAAU,EACX,MAAM,SAAS,CAAC;AAiJjB,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,CAkRf;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"}
1
+ {"version":3,"file":"adapter-core.d.ts","sourceRoot":"","sources":["../src/adapter-core.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,kBAAkB,EAElB,mBAAmB,EAGnB,QAAQ,EACR,kBAAkB,EAElB,UAAU,EACX,MAAM,SAAS,CAAC;AAwEjB,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"}
@@ -1,25 +1,28 @@
1
1
  import { VaultDisposedError as e, VaultError as t, VaultScopeError as n } from "./errors.js";
2
- import { createObserverHub as r, getRecordKey as i } from "./internal.js";
3
- import { createQueryBuilder as a } from "./query.js";
4
- import { assertTtlMs as o } from "./ttl.js";
2
+ import { assertTtlMs as r } from "./ttl.js";
3
+ import { createObserverHub as i, getRecordKey as a } from "./internal.js";
4
+ import { createQueryBuilder as o } from "./query.js";
5
5
  //#region src/adapter-core.ts
6
6
  var s = typeof performance < "u" ? () => performance.now() : () => Date.now();
7
- function c(e, t, n) {
8
- return n !== void 0 && o(n, "put/putAll"), n ?? e[t].defaultTtl;
7
+ function c(e) {
8
+ if (e.length === 0) throw new t("batch: declare at least one table");
9
9
  }
10
- function l(e, n, r, a, o) {
11
- let s = i(e, n, a);
10
+ function l(e, t, n) {
11
+ return n !== void 0 && r(n, "put/putAll"), n ?? e[t].defaultTtl;
12
+ }
13
+ function u(e, n, r, i, o) {
14
+ let s = a(e, n, i);
12
15
  if (s !== r) throw new t(`${o}: key field "${e[n].key}" must be "${String(r)}" but got "${String(s)}" in table "${n}"`);
13
16
  }
14
- function u(e, t, n) {
17
+ function d(e, t, n) {
15
18
  return e.getMany ? e.getMany(t, n) : Promise.all(n.map((n) => e.get(t, n)));
16
19
  }
17
- function d(e, t, n, r) {
18
- let a = n[e], o = a.key, s = a.indexes ? new Set(a.indexes) : void 0;
20
+ function f(e, t, n, r) {
21
+ let i = n[e], o = i.key, s = i.indexes ? new Set(i.indexes) : void 0;
19
22
  return {
20
- deleteMany: async (a) => {
21
- if (a.length === 0) return 0;
22
- let o = a.map((t) => i(n, e, t)), s = await t.deleteMany(e, o);
23
+ deleteMany: async (i) => {
24
+ if (i.length === 0) return 0;
25
+ let o = i.map((t) => a(n, e, t)), s = await t.deleteMany(e, o);
23
26
  return s > 0 && r(e), s;
24
27
  },
25
28
  getIndexRange: t.getByIndexRange ? (n, r) => t.getByIndexRange(e, n, r) : void 0,
@@ -29,58 +32,58 @@ function d(e, t, n, r) {
29
32
  source: () => t.getAll(e)
30
33
  };
31
34
  }
32
- function f(e, t, r, i, o) {
33
- let s = i ?? ((e, t) => t), f = o ? (e) => {
34
- if (!o.has(e)) throw new n(`table "${e}" is not part of this batch scope`);
35
+ function p(e, t, r, i, a) {
36
+ let s = i ?? ((e, t) => t), c = a ? (e) => {
37
+ if (!a.has(e)) throw new n(`table "${e}" is not part of this batch scope`);
35
38
  } : (e) => {};
36
39
  return {
37
40
  async clear(e) {
38
- f(e);
41
+ c(e);
39
42
  let n = await t.count(e);
40
43
  await t.clear(e), n > 0 && r(e);
41
44
  },
42
45
  async count(e) {
43
- return f(e), t.count(e);
46
+ return c(e), t.count(e);
44
47
  },
45
48
  async delete(e, n) {
46
- f(e);
49
+ c(e);
47
50
  let i = await t.delete(e, n);
48
51
  return i && r(e), i;
49
52
  },
50
53
  async deleteMany(e, n) {
51
- f(e);
54
+ c(e);
52
55
  let i = await t.deleteMany(e, n);
53
56
  return i > 0 && r(e), i;
54
57
  },
55
58
  async entries(n) {
56
- f(n);
59
+ c(n);
57
60
  let r = await t.getAll(n), i = e[n].key;
58
61
  return r.map((e) => [e[i], e]);
59
62
  },
60
63
  async get(e, n) {
61
- return f(e), t.get(e, n);
64
+ return c(e), t.get(e, n);
62
65
  },
63
66
  async getAll(e) {
64
- return f(e), t.getAll(e);
67
+ return c(e), t.getAll(e);
65
68
  },
66
69
  async getMany(e, n) {
67
- return f(e), u(t, e, n);
70
+ return c(e), d(t, e, n);
68
71
  },
69
72
  async getOrDefault(n, i, a, o) {
70
- f(n);
71
- let u = await t.get(n, i);
72
- if (u !== void 0) return u;
73
- let d = a();
74
- return l(e, n, i, d, "getOrDefault: defaultFn()"), await t.put(n, s(n, d), c(e, n, o)), r(n), d;
73
+ c(n);
74
+ let d = await t.get(n, i);
75
+ if (d !== void 0) return d;
76
+ let f = a();
77
+ return u(e, n, i, f, "getOrDefault: defaultFn()"), await t.put(n, s(n, f), l(e, n, o)), r(n), f;
75
78
  },
76
79
  async has(e, n) {
77
- return f(e), t.has(e, n);
80
+ return c(e), t.has(e, n);
78
81
  },
79
82
  async isEmpty(e) {
80
- return f(e), await t.count(e) === 0;
83
+ return c(e), await t.count(e) === 0;
81
84
  },
82
85
  async keys(n, r) {
83
- if (f(n), r) {
86
+ if (c(n), r) {
84
87
  let i = await t.getAll(n), a = e[n].key;
85
88
  return i.filter(r).map((e) => e[a]);
86
89
  }
@@ -89,43 +92,39 @@ function f(e, t, r, i, o) {
89
92
  return i.map((e) => e[a]);
90
93
  },
91
94
  async put(n, i, a) {
92
- f(n), await t.put(n, s(n, i), c(e, n, a)), r(n);
95
+ c(n), await t.put(n, s(n, i), l(e, n, a)), r(n);
93
96
  },
94
97
  async putAll(n, i, a) {
95
- if (f(n), i.length === 0) return;
98
+ if (c(n), i.length === 0) return;
96
99
  let o = i.map((e) => s(n, e));
97
- await t.putAll(n, o, c(e, n, a)), r(n);
100
+ await t.putAll(n, o, l(e, n, a)), r(n);
98
101
  },
99
102
  query(n) {
100
- return f(n), a(d(n, t, e, r));
103
+ return c(n), o(f(n, t, e, r));
101
104
  },
102
105
  async update(n, i, a, o) {
103
- f(n);
104
- let u = await t.get(n, i);
105
- if (!u) return;
106
- let d = {
107
- ...u,
106
+ c(n);
107
+ let d = await t.get(n, i);
108
+ if (!d) return;
109
+ let f = {
110
+ ...d,
108
111
  ...a
109
112
  };
110
- return l(e, n, i, d, "update"), await t.put(n, s(n, d), c(e, n, o)), r(n), d;
113
+ return u(e, n, i, f, "update"), await t.put(n, s(n, f), l(e, n, o)), r(n), f;
111
114
  },
112
115
  async upsert(n, i, a, o) {
113
- f(n);
114
- let u = a(await t.get(n, i));
115
- return l(e, n, i, u, "upsert: fn()"), await t.put(n, s(n, u), c(e, n, o)), r(n), u;
116
+ c(n);
117
+ let d = a(await t.get(n, i));
118
+ return u(e, n, i, d, "upsert: fn()"), await t.put(n, s(n, d), l(e, n, o)), r(n), d;
116
119
  }
117
120
  };
118
121
  }
119
- function p(n, i, o) {
120
- let { logger: c, onMetrics: l, validators: u } = o ?? {}, p = r((e) => i.getAll(e), c ? (e) => c.error(e instanceof Error ? e : Error(String(e)), "[vault] observer notification failed") : void 0), m = /* @__PURE__ */ new Map(), h = async (e) => {
121
- if (m.has(e)) return m.get(e);
122
- let t = await i.count(e);
123
- return m.set(e, t), t;
124
- }, g = (e) => {
125
- m.delete(e), p.notify(e), o?.onMutation?.(e);
126
- }, _ = (e) => {
127
- m.delete(e), p.notify(e);
128
- }, v = i.pruneAllExpired?.bind(i), y = i.getRawCount?.bind(i), b = o?.onCrossTabMessage?.(_) ?? void 0, x = (e, n) => {
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)), "[vault] observer notification failed") : void 0), m = (e) => {
124
+ d.notify(e), a?.onMutation?.(e);
125
+ }, h = (e) => {
126
+ d.notify(e);
127
+ }, g = r.pruneAllExpired?.bind(r), _ = r.getRawCount?.bind(r), v = a?.onCrossTabMessage?.(h) ?? void 0, y = (e, n) => {
129
128
  let r = u?.[e];
130
129
  if (!r) return n;
131
130
  try {
@@ -133,7 +132,7 @@ function p(n, i, o) {
133
132
  } catch (n) {
134
133
  throw new t(`validation failed for table "${e}"`, { cause: n });
135
134
  }
136
- }, S = (e, t, n) => {
135
+ }, b = (e, t, n) => {
137
136
  if (!l) return n();
138
137
  let r = s();
139
138
  return n().finally(() => l({
@@ -142,25 +141,25 @@ function p(n, i, o) {
142
141
  table: e
143
142
  }));
144
143
  };
145
- o?.onTransactions?.({
146
- notifyMutation: g,
147
- validate: x
144
+ a?.onTransactions?.({
145
+ notifyMutation: m,
146
+ validate: y
148
147
  });
149
- let C = f(n, i, g, x), w = new AbortController(), T = !1, E = () => {
150
- if (T) throw new e();
151
- }, D = {
148
+ let x = p(n, r, m, y), S = new AbortController(), C = !1, w = () => {
149
+ if (C) throw new e();
150
+ }, T = {
152
151
  async clear(e) {
153
- E(), await S(e, "clear", () => C.clear(e)), m.set(e, 0);
152
+ w(), await b(e, "clear", () => x.clear(e));
154
153
  },
155
154
  async count(e) {
156
- return E(), S(e, "count", () => h(e));
155
+ return w(), b(e, "count", () => r.count(e));
157
156
  },
158
157
  async debug() {
159
- E();
158
+ w();
160
159
  let e = Object.keys(n);
161
160
  return { tables: await Promise.all(e.map(async (e) => {
162
- let t = y ? await y(e) : void 0, n = await i.count(e);
163
- return m.set(e, n), {
161
+ let t = _ ? await _(e) : void 0, n = await r.count(e);
162
+ return {
164
163
  expiredCount: t === void 0 ? 0 : Math.max(0, t - n),
165
164
  name: e,
166
165
  recordCount: n
@@ -168,92 +167,83 @@ function p(n, i, o) {
168
167
  })) };
169
168
  },
170
169
  async delete(e, t) {
171
- return E(), S(e, "delete", () => C.delete(e, t));
170
+ return w(), b(e, "delete", () => x.delete(e, t));
172
171
  },
173
172
  async deleteMany(e, t) {
174
- return E(), S(e, "deleteMany", () => C.deleteMany(e, t));
173
+ return w(), b(e, "deleteMany", () => x.deleteMany(e, t));
175
174
  },
176
175
  get disposalSignal() {
177
- return w.signal;
176
+ return S.signal;
178
177
  },
179
178
  async dispose() {
180
- T || (T = !0, w.abort(), b?.(), p.dispose(), await i.dispose?.());
179
+ C || (C = !0, S.abort(), v?.(), d.dispose(), await r.dispose?.());
181
180
  },
182
181
  get disposed() {
183
- return T;
182
+ return C;
184
183
  },
185
184
  async entries(e) {
186
- return E(), S(e, "entries", () => C.entries(e));
185
+ return w(), b(e, "entries", () => x.entries(e));
187
186
  },
188
187
  async get(e, t) {
189
- return E(), S(e, "get", () => C.get(e, t));
188
+ return w(), b(e, "get", () => x.get(e, t));
190
189
  },
191
190
  async getAll(e) {
192
- return E(), S(e, "getAll", () => C.getAll(e));
191
+ return w(), b(e, "getAll", () => x.getAll(e));
193
192
  },
194
193
  async getMany(e, t) {
195
- return E(), S(e, "getMany", () => C.getMany(e, t));
194
+ return w(), b(e, "getMany", () => x.getMany(e, t));
196
195
  },
197
196
  async getOrDefault(e, t, n, r) {
198
- return E(), S(e, "getOrDefault", () => C.getOrDefault(e, t, n, r));
197
+ return w(), b(e, "getOrDefault", () => x.getOrDefault(e, t, n, r));
199
198
  },
200
199
  async has(e, t) {
201
- return E(), S(e, "has", () => C.has(e, t));
200
+ return w(), b(e, "has", () => x.has(e, t));
202
201
  },
203
202
  async isEmpty(e) {
204
- return E(), S(e, "isEmpty", async () => await h(e) === 0);
203
+ return w(), b(e, "isEmpty", async () => await r.count(e) === 0);
205
204
  },
206
205
  async keys(e, t) {
207
- return E(), S(e, "keys", () => C.keys(e, t));
206
+ return w(), b(e, "keys", () => x.keys(e, t));
208
207
  },
209
208
  observe(e, t, n) {
210
- return E(), p.observe(e, t, n);
211
- },
212
- async pruneExpired(e) {
213
- E();
214
- let t = e ?? Object.keys(n);
215
- if (!e && v) {
216
- let e = await v();
217
- for (let [t, n] of Object.entries(e)) n > 0 && m.delete(t);
218
- return e;
219
- }
220
- let r = await Promise.all(t.map(async (e) => {
221
- let t = await i.pruneExpiredInTable(e);
222
- return t > 0 && m.delete(e), [e, t];
223
- }));
224
- return Object.fromEntries(r);
209
+ return w(), d.observe(e, t, n);
210
+ },
211
+ async pruneExpired() {
212
+ if (w(), g) return await g();
213
+ let e = Object.keys(n), t = await Promise.all(e.map(async (e) => [e, await r.pruneExpiredInTable(e)]));
214
+ return Object.fromEntries(t);
225
215
  },
226
216
  async put(e, t, n) {
227
- E(), await S(e, "put", () => C.put(e, t, n));
217
+ w(), await b(e, "put", () => x.put(e, t, n));
228
218
  },
229
219
  async putAll(e, t, n) {
230
- E(), await S(e, "putAll", () => C.putAll(e, t, n));
220
+ w(), await b(e, "putAll", () => x.putAll(e, t, n));
231
221
  },
232
222
  query(e) {
233
- E();
234
- let t = d(e, i, n, g);
235
- return a(l ? {
223
+ w();
224
+ let t = f(e, r, n, m);
225
+ return o(l ? {
236
226
  ...t,
237
- deleteMany: t.deleteMany ? (n) => S(e, "queryDelete", () => t.deleteMany(n)) : void 0,
238
- source: () => S(e, "query", () => i.getAll(e))
227
+ deleteMany: t.deleteMany ? (n) => b(e, "queryDelete", () => t.deleteMany(n)) : void 0,
228
+ source: () => b(e, "query", () => r.getAll(e))
239
229
  } : t);
240
230
  },
241
231
  async [Symbol.asyncDispose]() {
242
- await D.dispose();
232
+ await T.dispose();
243
233
  },
244
234
  async update(e, t, n, r) {
245
- return E(), S(e, "update", () => C.update(e, t, n, r));
235
+ return w(), b(e, "update", () => x.update(e, t, n, r));
246
236
  },
247
237
  async upsert(e, t, n, r) {
248
- return E(), S(e, "upsert", () => C.upsert(e, t, n, r));
238
+ return w(), b(e, "upsert", () => x.upsert(e, t, n, r));
249
239
  }
250
240
  };
251
- return D;
241
+ return T;
252
242
  }
253
- function m(e, t) {
243
+ function h(e, t) {
254
244
  return Object.assign(e, { batch: t });
255
245
  }
256
246
  //#endregion
257
- export { p as buildAdapterOps, f as buildTxContext, m as withIndexedDbTransactions };
247
+ export { c as assertBatchTables, m as buildAdapterOps, p as buildTxContext, h as withIndexedDbTransactions };
258
248
 
259
249
  //# sourceMappingURL=adapter-core.js.map