@vielzeug/vault 2.3.1 → 2.5.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/dist/adapter-core.cjs +1 -1
- package/dist/adapter-core.cjs.map +1 -1
- package/dist/adapter-core.d.ts +2 -2
- package/dist/adapter-core.d.ts.map +1 -1
- package/dist/adapter-core.js +66 -116
- 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 +3 -3
- package/dist/adapters/indexeddb.d.ts.map +1 -1
- package/dist/adapters/indexeddb.js +93 -102
- package/dist/adapters/indexeddb.js.map +1 -1
- package/dist/adapters/memory.cjs +1 -1
- package/dist/adapters/memory.cjs.map +1 -1
- package/dist/adapters/memory.d.ts +1 -3
- package/dist/adapters/memory.d.ts.map +1 -1
- package/dist/adapters/memory.js +24 -105
- package/dist/adapters/memory.js.map +1 -1
- package/dist/adapters/sqlite.cjs +16 -4
- package/dist/adapters/sqlite.cjs.map +1 -1
- package/dist/adapters/sqlite.d.ts +2 -5
- package/dist/adapters/sqlite.d.ts.map +1 -1
- package/dist/adapters/sqlite.js +122 -90
- package/dist/adapters/sqlite.js.map +1 -1
- package/dist/adapters/webstorage.cjs +1 -1
- package/dist/adapters/webstorage.cjs.map +1 -1
- package/dist/adapters/webstorage.d.ts.map +1 -1
- package/dist/adapters/webstorage.js +59 -66
- package/dist/adapters/webstorage.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/indexeddb.d.ts +1 -1
- package/dist/indexeddb.d.ts.map +1 -1
- package/dist/internal.cjs +1 -1
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +18 -23
- package/dist/internal.js.map +1 -1
- package/dist/query.cjs +1 -1
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.ts +11 -79
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +14 -71
- package/dist/query.js.map +1 -1
- package/dist/sqlite.d.ts +1 -1
- package/dist/sqlite.d.ts.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.ts +1 -31
- package/dist/types.d.ts.map +1 -1
- 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 +2 -1
- package/dist/_dev.cjs +0 -2
- package/dist/_dev.cjs.map +0 -1
- package/dist/_dev.d.ts +0 -2
- package/dist/_dev.d.ts.map +0 -1
- package/dist/_dev.js +0 -6
- package/dist/_dev.js.map +0 -1
- package/dist/prune.cjs +0 -2
- package/dist/prune.cjs.map +0 -1
- package/dist/prune.d.ts +0 -21
- package/dist/prune.d.ts.map +0 -1
- package/dist/prune.js +0 -16
- package/dist/prune.js.map +0 -1
package/dist/indexeddb.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type {
|
|
1
|
+
export type { MigrationContext, MigrationFn, MigrationStep } from './adapters/indexeddb';
|
|
2
2
|
export { createIndexedDB, defineMigration } from './adapters/indexeddb';
|
|
3
3
|
export type { TransactionContext } from './types';
|
|
4
4
|
//# sourceMappingURL=indexeddb.d.ts.map
|
package/dist/indexeddb.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexeddb.d.ts","sourceRoot":"","sources":["../src/indexeddb.ts"],"names":[],"mappings":"AAAA,YAAY,
|
|
1
|
+
{"version":3,"file":"indexeddb.d.ts","sourceRoot":"","sources":["../src/indexeddb.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACxE,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/internal.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./errors.cjs");
|
|
1
|
+
const e=require("./errors.cjs");var t=`\0`;function n(t){if(typeof t==`number`){if(!Number.isFinite(t))throw new e.VaultError(`primary key must be a finite number, received ${String(t)}`);return`n:${String(t)}`}return`s:${encodeURIComponent(t)}`}function r(e){return`${encodeURIComponent(e)}${t}`}function i(e,r,i){return`${encodeURIComponent(e)}${t}${encodeURIComponent(r)}${t}${n(i)}`}function a(e,n){return`${encodeURIComponent(e)}${t}${encodeURIComponent(n)}${t}`}function o(e,n){if(!n)return;let i=r(e);if(!n.startsWith(i))return;let a=n.slice(i.length),o=a.indexOf(t);if(o!==-1)try{return decodeURIComponent(a.slice(0,o))}catch{return}}function s(t){let n=new Map,r=!1,i=e=>{if(r)return;let i=n.get(e);!i||i.size===0||t(e).then(t=>{if(r)return;let i=n.get(e);if(!(!i||i.size===0))for(let e of i)try{e(t)}catch{}}).catch(()=>{})};return{dispose:()=>{r=!0,n.clear()},notify:i,observe:(t,a,{immediate:o=!0,signal:s}={})=>{if(r)throw new e.VaultDisposedError(`observer hub is disposed`);if(s?.aborted)return()=>{};let c=a,l=n.get(t)??new Set;n.set(t,l),l.add(c),o&&i(t);let u=()=>{let e=n.get(t);e&&(e.delete(c),e.size===0&&n.delete(t))};return s?.addEventListener(`abort`,u,{once:!0}),u}}}function c(t,n,r){let i=String(t[n].key),a=r[i];if(typeof a!=`number`&&typeof a!=`string`)throw new e.VaultError(`record in table "${String(n)}" must have a string or finite-number key at "${i}"`);if(typeof a==`number`&&!Number.isFinite(a))throw new e.VaultError(`record in table "${String(n)}" must have a finite-number key at "${i}"`);return a}exports.createObserverHub=s,exports.decodeStorageTableFromKey=o,exports.encodeDbPrefix=r,exports.encodeStorageKey=i,exports.encodeStorageTablePrefix=a,exports.encodeVaultKey=n,exports.getRecordKey=c;
|
|
2
2
|
//# sourceMappingURL=internal.cjs.map
|
package/dist/internal.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal.cjs","names":[],"sources":["../src/internal.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"internal.cjs","names":[],"sources":["../src/internal.ts"],"sourcesContent":["import { VaultDisposedError, VaultError } from './errors';\nimport type { AnySchema, KeyOf, RecordOf, VaultKey } from './types';\n\ntype ObserverListener<T> = (records: T[]) => void;\n\nconst SEP = '\\x00';\n\n/**\n * Type-tagged keys make `1` and `'1'` distinct in string-keyed adapters while\n * keeping the same portable primary-key contract as IndexedDB.\n */\nexport function encodeVaultKey(key: VaultKey): string {\n if (typeof key === 'number') {\n if (!Number.isFinite(key)) throw new VaultError(`primary key must be a finite number, received ${String(key)}`);\n\n return `n:${String(key)}`;\n }\n\n return `s:${encodeURIComponent(key)}`;\n}\n\nexport function encodeDbPrefix(dbName: string): string {\n return `${encodeURIComponent(dbName)}${SEP}`;\n}\n\nexport function encodeStorageKey(dbName: string, table: string, key: VaultKey): string {\n return `${encodeURIComponent(dbName)}${SEP}${encodeURIComponent(table)}${SEP}${encodeVaultKey(key)}`;\n}\n\nexport function encodeStorageTablePrefix(dbName: string, table: string): string {\n return `${encodeURIComponent(dbName)}${SEP}${encodeURIComponent(table)}${SEP}`;\n}\n\nexport function decodeStorageTableFromKey(dbName: string, storageKey: string | null): string | undefined {\n if (!storageKey) return undefined;\n\n const prefix = encodeDbPrefix(dbName);\n\n if (!storageKey.startsWith(prefix)) return undefined;\n\n const tail = storageKey.slice(prefix.length);\n const end = tail.indexOf(SEP);\n\n if (end === -1) return undefined;\n\n try {\n return decodeURIComponent(tail.slice(0, end));\n } catch {\n return undefined;\n }\n}\n\n/** `observe()` is Vault's single reactivity primitive: a snapshot now, then on mutations. */\nexport function createObserverHub<S extends AnySchema>(\n getAll: <K extends keyof S & string>(table: K) => Promise<RecordOf<S, K>[]>,\n) {\n const observers = new Map<string, Set<ObserverListener<unknown>>>();\n let disposed = false;\n\n const notify = <K extends keyof S & string>(table: K): void => {\n if (disposed) return;\n\n const listeners = observers.get(table);\n\n if (!listeners || listeners.size === 0) return;\n\n void getAll(table)\n .then((records) => {\n if (disposed) return;\n\n const current = observers.get(table);\n\n if (!current || current.size === 0) return;\n\n for (const listener of current) {\n try {\n listener(records as unknown[]);\n } catch {\n /* observer listener errors are intentionally swallowed */\n }\n }\n })\n .catch(() => {\n /* observer source errors are intentionally swallowed */\n });\n };\n\n const observe = <K extends keyof S & string>(\n table: K,\n listener: (records: RecordOf<S, K>[]) => void,\n { immediate = true, signal }: { immediate?: boolean; signal?: AbortSignal } = {},\n ): (() => void) => {\n if (disposed) throw new VaultDisposedError('observer hub is disposed');\n\n if (signal?.aborted) return () => {};\n\n const wrapped = listener as ObserverListener<unknown>;\n const listeners = observers.get(table) ?? new Set<ObserverListener<unknown>>();\n\n observers.set(table, listeners);\n listeners.add(wrapped);\n\n if (immediate) notify(table);\n\n const stop = (): void => {\n const current = observers.get(table);\n\n if (!current) return;\n\n current.delete(wrapped);\n\n if (current.size === 0) observers.delete(table);\n };\n\n signal?.addEventListener('abort', stop, { once: true });\n\n return stop;\n };\n\n return {\n dispose: () => {\n disposed = true;\n observers.clear();\n },\n notify,\n observe,\n };\n}\n\nexport function getRecordKey<S extends AnySchema, K extends keyof S>(\n schema: S,\n table: K,\n value: RecordOf<S, K>,\n): KeyOf<S, K> {\n const keyField = String(schema[table].key);\n const keyValue = (value as Record<string, unknown>)[keyField];\n\n if (typeof keyValue !== 'number' && typeof keyValue !== 'string') {\n throw new VaultError(`record in table \"${String(table)}\" must have a string or finite-number key at \"${keyField}\"`);\n }\n\n if (typeof keyValue === 'number' && !Number.isFinite(keyValue)) {\n throw new VaultError(`record in table \"${String(table)}\" must have a finite-number key at \"${keyField}\"`);\n }\n\n return keyValue as KeyOf<S, K>;\n}\n"],"mappings":"gCAKA,IAAM,EAAM,KAMZ,SAAgB,EAAe,EAAuB,CACpD,GAAI,OAAO,GAAQ,SAAU,CAC3B,GAAI,CAAC,OAAO,SAAS,CAAG,EAAG,MAAM,IAAI,EAAA,WAAW,iDAAiD,OAAO,CAAG,GAAG,EAE9G,MAAO,KAAK,OAAO,CAAG,GACxB,CAEA,MAAO,KAAK,mBAAmB,CAAG,GACpC,CAEA,SAAgB,EAAe,EAAwB,CACrD,MAAO,GAAG,mBAAmB,CAAM,IAAI,GACzC,CAEA,SAAgB,EAAiB,EAAgB,EAAe,EAAuB,CACrF,MAAO,GAAG,mBAAmB,CAAM,IAAI,IAAM,mBAAmB,CAAK,IAAI,IAAM,EAAe,CAAG,GACnG,CAEA,SAAgB,EAAyB,EAAgB,EAAuB,CAC9E,MAAO,GAAG,mBAAmB,CAAM,IAAI,IAAM,mBAAmB,CAAK,IAAI,GAC3E,CAEA,SAAgB,EAA0B,EAAgB,EAA+C,CACvG,GAAI,CAAC,EAAY,OAEjB,IAAM,EAAS,EAAe,CAAM,EAEpC,GAAI,CAAC,EAAW,WAAW,CAAM,EAAG,OAEpC,IAAM,EAAO,EAAW,MAAM,EAAO,MAAM,EACrC,EAAM,EAAK,QAAQ,CAAG,EAExB,OAAQ,GAEZ,GAAI,CACF,OAAO,mBAAmB,EAAK,MAAM,EAAG,CAAG,CAAC,CAC9C,MAAQ,CACN,MACF,CACF,CAGA,SAAgB,EACd,EACA,CACA,IAAM,EAAY,IAAI,IAClB,EAAW,GAET,EAAsC,GAAmB,CAC7D,GAAI,EAAU,OAEd,IAAM,EAAY,EAAU,IAAI,CAAK,EAEjC,CAAC,GAAa,EAAU,OAAS,GAErC,EAAY,CAAK,CAAC,CACf,KAAM,GAAY,CACjB,GAAI,EAAU,OAEd,IAAM,EAAU,EAAU,IAAI,CAAK,EAE/B,MAAC,GAAW,EAAQ,OAAS,GAEjC,IAAK,IAAM,KAAY,EACrB,GAAI,CACF,EAAS,CAAoB,CAC/B,MAAQ,CAER,CAEJ,CAAC,CAAC,CACD,UAAY,CAEb,CAAC,CACL,EAkCA,MAAO,CACL,YAAe,CACb,EAAW,GACX,EAAU,MAAM,CAClB,EACA,SACA,SArCA,EACA,EACA,CAAE,YAAY,GAAM,UAA0D,CAAC,IAC9D,CACjB,GAAI,EAAU,MAAM,IAAI,EAAA,mBAAmB,0BAA0B,EAErE,GAAI,GAAQ,QAAS,UAAa,CAAC,EAEnC,IAAM,EAAU,EACV,EAAY,EAAU,IAAI,CAAK,GAAK,IAAI,IAE9C,EAAU,IAAI,EAAO,CAAS,EAC9B,EAAU,IAAI,CAAO,EAEjB,GAAW,EAAO,CAAK,EAE3B,IAAM,MAAmB,CACvB,IAAM,EAAU,EAAU,IAAI,CAAK,EAE9B,IAEL,EAAQ,OAAO,CAAO,EAElB,EAAQ,OAAS,GAAG,EAAU,OAAO,CAAK,EAChD,EAIA,OAFA,GAAQ,iBAAiB,QAAS,EAAM,CAAE,KAAM,EAAK,CAAC,EAE/C,CACT,CASA,CACF,CAEA,SAAgB,EACd,EACA,EACA,EACa,CACb,IAAM,EAAW,OAAO,EAAO,EAAM,CAAC,GAAG,EACnC,EAAY,EAAkC,GAEpD,GAAI,OAAO,GAAa,UAAY,OAAO,GAAa,SACtD,MAAM,IAAI,EAAA,WAAW,oBAAoB,OAAO,CAAK,EAAE,gDAAgD,EAAS,EAAE,EAGpH,GAAI,OAAO,GAAa,UAAY,CAAC,OAAO,SAAS,CAAQ,EAC3D,MAAM,IAAI,EAAA,WAAW,oBAAoB,OAAO,CAAK,EAAE,sCAAsC,EAAS,EAAE,EAG1G,OAAO,CACT"}
|
package/dist/internal.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export declare function encodeStorageKey(dbName: string, table: string, key: Vau
|
|
|
9
9
|
export declare function encodeStorageTablePrefix(dbName: string, table: string): string;
|
|
10
10
|
export declare function decodeStorageTableFromKey(dbName: string, storageKey: string | null): string | undefined;
|
|
11
11
|
/** `observe()` is Vault's single reactivity primitive: a snapshot now, then on mutations. */
|
|
12
|
-
export declare function createObserverHub<S extends AnySchema>(getAll: <K extends keyof S & string>(table: K) => Promise<RecordOf<S, K>[]
|
|
12
|
+
export declare function createObserverHub<S extends AnySchema>(getAll: <K extends keyof S & string>(table: K) => Promise<RecordOf<S, K>[]>): {
|
|
13
13
|
dispose: () => void;
|
|
14
14
|
notify: <K extends keyof S & string>(table: K) => void;
|
|
15
15
|
observe: <K extends keyof S & string>(table: K, listener: (records: RecordOf<S, K>[]) => void, { immediate, signal }?: {
|
package/dist/internal.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAMpE;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,CAQpD;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,MAAM,CAErF;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAiBvG;AAED,6FAA6F;AAC7F,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,SAAS,EACnD,MAAM,EAAE,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;;aAK3D,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC,KAAG,IAAI;cA4B1C,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,SAClC,CAAC,YACE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,IAAI,0BACf;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,KAC1E,CAAC,MAAM,IAAI,CAAC;EAoChB;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,MAAM,CAAC,EACjE,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,CAAC,EACR,KAAK,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,GACpB,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAab"}
|
package/dist/internal.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { VaultDisposedError as e, VaultError as t } from "./errors.js";
|
|
2
|
-
import "./_dev.js";
|
|
3
2
|
//#region src/internal.ts
|
|
4
3
|
var n = "\0";
|
|
5
4
|
function r(e) {
|
|
@@ -29,35 +28,31 @@ function s(e, t) {
|
|
|
29
28
|
return;
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
|
-
function c(t
|
|
33
|
-
let
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (i)
|
|
40
|
-
let n = r.get(e);
|
|
41
|
-
if (!(!n || n.size === 0)) for (let e of n) try {
|
|
31
|
+
function c(t) {
|
|
32
|
+
let n = /* @__PURE__ */ new Map(), r = !1, i = (e) => {
|
|
33
|
+
if (r) return;
|
|
34
|
+
let i = n.get(e);
|
|
35
|
+
!i || i.size === 0 || t(e).then((t) => {
|
|
36
|
+
if (r) return;
|
|
37
|
+
let i = n.get(e);
|
|
38
|
+
if (!(!i || i.size === 0)) for (let e of i) try {
|
|
42
39
|
e(t);
|
|
43
|
-
} catch
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
}).catch(a);
|
|
40
|
+
} catch {}
|
|
41
|
+
}).catch(() => {});
|
|
47
42
|
};
|
|
48
43
|
return {
|
|
49
44
|
dispose: () => {
|
|
50
|
-
|
|
45
|
+
r = !0, n.clear();
|
|
51
46
|
},
|
|
52
|
-
notify:
|
|
53
|
-
observe: (t,
|
|
54
|
-
if (
|
|
47
|
+
notify: i,
|
|
48
|
+
observe: (t, a, { immediate: o = !0, signal: s } = {}) => {
|
|
49
|
+
if (r) throw new e("observer hub is disposed");
|
|
55
50
|
if (s?.aborted) return () => {};
|
|
56
|
-
let c =
|
|
57
|
-
|
|
51
|
+
let c = a, l = n.get(t) ?? /* @__PURE__ */ new Set();
|
|
52
|
+
n.set(t, l), l.add(c), o && i(t);
|
|
58
53
|
let u = () => {
|
|
59
|
-
let e =
|
|
60
|
-
e && (e.delete(c), e.size === 0 &&
|
|
54
|
+
let e = n.get(t);
|
|
55
|
+
e && (e.delete(c), e.size === 0 && n.delete(t));
|
|
61
56
|
};
|
|
62
57
|
return s?.addEventListener("abort", u, { once: !0 }), u;
|
|
63
58
|
}
|
package/dist/internal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal.js","names":[],"sources":["../src/internal.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"internal.js","names":[],"sources":["../src/internal.ts"],"sourcesContent":["import { VaultDisposedError, VaultError } from './errors';\nimport type { AnySchema, KeyOf, RecordOf, VaultKey } from './types';\n\ntype ObserverListener<T> = (records: T[]) => void;\n\nconst SEP = '\\x00';\n\n/**\n * Type-tagged keys make `1` and `'1'` distinct in string-keyed adapters while\n * keeping the same portable primary-key contract as IndexedDB.\n */\nexport function encodeVaultKey(key: VaultKey): string {\n if (typeof key === 'number') {\n if (!Number.isFinite(key)) throw new VaultError(`primary key must be a finite number, received ${String(key)}`);\n\n return `n:${String(key)}`;\n }\n\n return `s:${encodeURIComponent(key)}`;\n}\n\nexport function encodeDbPrefix(dbName: string): string {\n return `${encodeURIComponent(dbName)}${SEP}`;\n}\n\nexport function encodeStorageKey(dbName: string, table: string, key: VaultKey): string {\n return `${encodeURIComponent(dbName)}${SEP}${encodeURIComponent(table)}${SEP}${encodeVaultKey(key)}`;\n}\n\nexport function encodeStorageTablePrefix(dbName: string, table: string): string {\n return `${encodeURIComponent(dbName)}${SEP}${encodeURIComponent(table)}${SEP}`;\n}\n\nexport function decodeStorageTableFromKey(dbName: string, storageKey: string | null): string | undefined {\n if (!storageKey) return undefined;\n\n const prefix = encodeDbPrefix(dbName);\n\n if (!storageKey.startsWith(prefix)) return undefined;\n\n const tail = storageKey.slice(prefix.length);\n const end = tail.indexOf(SEP);\n\n if (end === -1) return undefined;\n\n try {\n return decodeURIComponent(tail.slice(0, end));\n } catch {\n return undefined;\n }\n}\n\n/** `observe()` is Vault's single reactivity primitive: a snapshot now, then on mutations. */\nexport function createObserverHub<S extends AnySchema>(\n getAll: <K extends keyof S & string>(table: K) => Promise<RecordOf<S, K>[]>,\n) {\n const observers = new Map<string, Set<ObserverListener<unknown>>>();\n let disposed = false;\n\n const notify = <K extends keyof S & string>(table: K): void => {\n if (disposed) return;\n\n const listeners = observers.get(table);\n\n if (!listeners || listeners.size === 0) return;\n\n void getAll(table)\n .then((records) => {\n if (disposed) return;\n\n const current = observers.get(table);\n\n if (!current || current.size === 0) return;\n\n for (const listener of current) {\n try {\n listener(records as unknown[]);\n } catch {\n /* observer listener errors are intentionally swallowed */\n }\n }\n })\n .catch(() => {\n /* observer source errors are intentionally swallowed */\n });\n };\n\n const observe = <K extends keyof S & string>(\n table: K,\n listener: (records: RecordOf<S, K>[]) => void,\n { immediate = true, signal }: { immediate?: boolean; signal?: AbortSignal } = {},\n ): (() => void) => {\n if (disposed) throw new VaultDisposedError('observer hub is disposed');\n\n if (signal?.aborted) return () => {};\n\n const wrapped = listener as ObserverListener<unknown>;\n const listeners = observers.get(table) ?? new Set<ObserverListener<unknown>>();\n\n observers.set(table, listeners);\n listeners.add(wrapped);\n\n if (immediate) notify(table);\n\n const stop = (): void => {\n const current = observers.get(table);\n\n if (!current) return;\n\n current.delete(wrapped);\n\n if (current.size === 0) observers.delete(table);\n };\n\n signal?.addEventListener('abort', stop, { once: true });\n\n return stop;\n };\n\n return {\n dispose: () => {\n disposed = true;\n observers.clear();\n },\n notify,\n observe,\n };\n}\n\nexport function getRecordKey<S extends AnySchema, K extends keyof S>(\n schema: S,\n table: K,\n value: RecordOf<S, K>,\n): KeyOf<S, K> {\n const keyField = String(schema[table].key);\n const keyValue = (value as Record<string, unknown>)[keyField];\n\n if (typeof keyValue !== 'number' && typeof keyValue !== 'string') {\n throw new VaultError(`record in table \"${String(table)}\" must have a string or finite-number key at \"${keyField}\"`);\n }\n\n if (typeof keyValue === 'number' && !Number.isFinite(keyValue)) {\n throw new VaultError(`record in table \"${String(table)}\" must have a finite-number key at \"${keyField}\"`);\n }\n\n return keyValue as KeyOf<S, K>;\n}\n"],"mappings":";;AAKA,IAAM,IAAM;AAMZ,SAAgB,EAAe,GAAuB;CACpD,IAAI,OAAO,KAAQ,UAAU;EAC3B,IAAI,CAAC,OAAO,SAAS,CAAG,GAAG,MAAM,IAAI,EAAW,iDAAiD,OAAO,CAAG,GAAG;EAE9G,OAAO,KAAK,OAAO,CAAG;CACxB;CAEA,OAAO,KAAK,mBAAmB,CAAG;AACpC;AAEA,SAAgB,EAAe,GAAwB;CACrD,OAAO,GAAG,mBAAmB,CAAM,IAAI;AACzC;AAEA,SAAgB,EAAiB,GAAgB,GAAe,GAAuB;CACrF,OAAO,GAAG,mBAAmB,CAAM,IAAI,IAAM,mBAAmB,CAAK,IAAI,IAAM,EAAe,CAAG;AACnG;AAEA,SAAgB,EAAyB,GAAgB,GAAuB;CAC9E,OAAO,GAAG,mBAAmB,CAAM,IAAI,IAAM,mBAAmB,CAAK,IAAI;AAC3E;AAEA,SAAgB,EAA0B,GAAgB,GAA+C;CACvG,IAAI,CAAC,GAAY;CAEjB,IAAM,IAAS,EAAe,CAAM;CAEpC,IAAI,CAAC,EAAW,WAAW,CAAM,GAAG;CAEpC,IAAM,IAAO,EAAW,MAAM,EAAO,MAAM,GACrC,IAAM,EAAK,QAAQ,CAAG;CAExB,UAAQ,IAEZ,IAAI;EACF,OAAO,mBAAmB,EAAK,MAAM,GAAG,CAAG,CAAC;CAC9C,QAAQ;EACN;CACF;AACF;AAGA,SAAgB,EACd,GACA;CACA,IAAM,oBAAY,IAAI,IAA4C,GAC9D,IAAW,IAET,KAAsC,MAAmB;EAC7D,IAAI,GAAU;EAEd,IAAM,IAAY,EAAU,IAAI,CAAK;EAEjC,CAAC,KAAa,EAAU,SAAS,KAErC,EAAY,CAAK,CAAC,CACf,MAAM,MAAY;GACjB,IAAI,GAAU;GAEd,IAAM,IAAU,EAAU,IAAI,CAAK;GAE/B,OAAC,KAAW,EAAQ,SAAS,IAEjC,KAAK,IAAM,KAAY,GACrB,IAAI;IACF,EAAS,CAAoB;GAC/B,QAAQ,CAER;EAEJ,CAAC,CAAC,CACD,YAAY,CAEb,CAAC;CACL;CAkCA,OAAO;EACL,eAAe;GAEb,AADA,IAAW,IACX,EAAU,MAAM;EAClB;EACA;EACA,UArCA,GACA,GACA,EAAE,eAAY,IAAM,cAA0D,CAAC,MAC9D;GACjB,IAAI,GAAU,MAAM,IAAI,EAAmB,0BAA0B;GAErE,IAAI,GAAQ,SAAS,aAAa,CAAC;GAEnC,IAAM,IAAU,GACV,IAAY,EAAU,IAAI,CAAK,qBAAK,IAAI,IAA+B;GAK7E,AAHA,EAAU,IAAI,GAAO,CAAS,GAC9B,EAAU,IAAI,CAAO,GAEjB,KAAW,EAAO,CAAK;GAE3B,IAAM,UAAmB;IACvB,IAAM,IAAU,EAAU,IAAI,CAAK;IAE9B,MAEL,EAAQ,OAAO,CAAO,GAElB,EAAQ,SAAS,KAAG,EAAU,OAAO,CAAK;GAChD;GAIA,OAFA,GAAQ,iBAAiB,SAAS,GAAM,EAAE,MAAM,GAAK,CAAC,GAE/C;EACT;CASA;AACF;AAEA,SAAgB,EACd,GACA,GACA,GACa;CACb,IAAM,IAAW,OAAO,EAAO,EAAM,CAAC,GAAG,GACnC,IAAY,EAAkC;CAEpD,IAAI,OAAO,KAAa,YAAY,OAAO,KAAa,UACtD,MAAM,IAAI,EAAW,oBAAoB,OAAO,CAAK,EAAE,gDAAgD,EAAS,EAAE;CAGpH,IAAI,OAAO,KAAa,YAAY,CAAC,OAAO,SAAS,CAAQ,GAC3D,MAAM,IAAI,EAAW,oBAAoB,OAAO,CAAK,EAAE,sCAAsC,EAAS,EAAE;CAG1G,OAAO;AACT"}
|
package/dist/query.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./errors.cjs");async function t(e,t){let n=await e.source();for(let e of t)n=e.apply(n);return n}function n(t,n){if(!Number.isInteger(t)||t<0)throw new e.VaultError(`${n} must be a non-negative integer`);return t}function r(
|
|
1
|
+
const e=require("./errors.cjs");async function t(e,t){let n=await e.source();for(let e of t)n=e.apply(n);return n}function n(t,n){if(!Number.isInteger(t)||t<0)throw new e.VaultError(`${n} must be a non-negative integer`);return t}function r(i,a=[]){let o=e=>r(i,[...a,e]);return{count(){return t(i,a.filter(e=>!e.isNonFilter)).then(e=>e.length)},async delete(){if(!i.deleteMany)throw new e.VaultError(`query.delete is not available for this adapter context`);let n=await t(i,a);return i.deleteMany(n)},equals(e,t){return o({apply:n=>n.filter(n=>n[e]===t)})},filter(e){return o({apply:t=>t.filter(e)})},first(){return a.length===0?i.source().then(e=>e[0]):t(i,a).then(e=>e[0])},limit(e){let t=n(e,`query.limit`);return o({apply:e=>e.slice(0,t),isNonFilter:!0})},offset(e){let t=n(e,`query.offset`);return o({apply:e=>e.slice(t),isNonFilter:!0})},orderBy(e,t=`asc`){return o({apply:n=>{let r=t===`asc`?1:-1;return[...n].sort((t,n)=>{let i=t[e],a=n[e];return i===a?0:i>a?r:-r})},isNonFilter:!0})},toArray(){return t(i,a)}}}exports.createQueryBuilder=r;
|
|
2
2
|
//# sourceMappingURL=query.cjs.map
|
package/dist/query.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.cjs","names":[],"sources":["../src/query.ts"],"sourcesContent":["import { VaultError } from './errors';\n\ntype Predicate<T> = (value: T, index: number, array: T[]) => boolean;\n/**\n * `isNonFilter`: when true this op is excluded from `count()` — it does not restrict\n * *which* records match (limit, offset, orderBy), only how results are presented.\n */\ntype QueryOp<T> = { apply: (data: T[]) => T[]; isNonFilter?: boolean };\ntype ComparableFieldKeys<T extends object> = {\n [K in keyof T]-?: Extract<NonNullable<T[K]>, number | string> extends never ? never : K;\n}[keyof T];\n\n/**\n * A primary-key range hint that can be pushed down to native storage backends (e.g. IndexedDB).\n * When `QueryContext.getRange` and `keyField` are present, a matching first filter op replaces\n * the full-table `source()` scan with a targeted range fetch.\n */\nexport type NativeRange =\n | { type: 'eq'; value: unknown }\n | { lower: unknown; type: 'between'; upper: unknown }\n | { prefix: string; type: 'starts' };\n\nexport type QueryContext<T extends object> = {\n deleteMany?: (records: T[]) => Promise<number>;\n /**\n * When present alongside `indexedFields`, replaces `source()` for secondary-index filter ops.\n * Only activated when the first op is `equals`, `between`, or `startsWith` on a field that\n * has an index registered in the schema. IndexedDB uses `IDBIndex.getAll(range)` under the hood.\n */\n getIndexRange?: (field: string, range: NativeRange) => Promise<T[]>;\n /**\n * When present alongside `keyField`, replaces `source()` for primary-key filter ops.\n * Only activated when the first op is an `equals`, `between`, or case-sensitive `startsWith`\n * on `keyField`. All remaining ops still run in-memory against the range result.\n */\n getRange?: (range: NativeRange) => Promise<T[]>;\n /** Fields that have secondary indexes — used to detect when a filter op can use `getIndexRange`. */\n indexedFields?: ReadonlySet<string>;\n /** Primary key field name — used to detect when a filter op can be pushed to `getRange`. */\n keyField?: string;\n source: () => Promise<T[]>;\n};\n\n/* -------------------- Public interfaces -------------------- */\n\n/**\n * Shared query methods. `T` is the base record type; `N` is the progressively-narrowed type\n * accumulated by `equals()` calls.\n */\ntype ChainedQuery<T extends object, N extends T, Self extends ChainedQuery<T, N, Self>> = {\n /**\n * Filter records where `field` is between `lower` and `upper` (inclusive).\n * Preserves any type narrowing already accumulated by prior `equals()` calls.\n */\n between<K extends ComparableFieldKeys<T>>(\n field: K,\n lower: Extract<NonNullable<T[K]>, number | string>,\n upper: Extract<NonNullable<T[K]>, number | string>,\n ): QueryBuilder<T, N>;\n /**\n * Returns the number of records matching the applied filter predicates.\n * Presentation-only ops (`limit`, `offset`, `orderBy`) are intentionally ignored — this\n * always counts the full filtered set, making paginated total-count queries possible\n * without a second query.\n */\n count(): Promise<number>;\n /**\n * Filter records where `field` exactly equals `value`.\n * The return type is narrowed to `QueryBuilder<T & Record<K, V>>` so that subsequent\n * `.toArray()`, `.first()`, etc. reflect the equality constraint in their result type.\n *\n * ```ts\n * // result: (User & { role: 'admin' })[]\n * const admins = await db.query('users').equals('role', 'admin').toArray();\n * ```\n */\n equals<K extends keyof T & string, V extends T[K]>(field: K, value: V): QueryBuilder<T & Record<K, V>>;\n /**\n * Returns `true` if at least one record matches all applied filter operations.\n * Equivalent to `(await query.first()) !== undefined` but makes the intent explicit.\n * Presentation-only ops (`limit`, `offset`, `orderBy`) are respected before checking.\n */\n exists(): Promise<boolean>;\n filter(fn: Predicate<N>): Self;\n first(): Promise<N | undefined>;\n limit(n: number): Self;\n offset(n: number): Self;\n orderBy<K extends keyof T>(field: K, direction?: 'asc' | 'desc'): Self;\n /**\n * Filter records where `field` starts with `prefix`.\n * Preserves any type narrowing already accumulated by prior `equals()` calls.\n *\n * **Index push-down** is only active when `ignoreCase` is `false` (default) and this\n * is the first operation in the chain. With `ignoreCase: true`, a full-table scan is\n * always performed regardless of whether the field has an index.\n */\n startsWith<K extends keyof T>(field: K, prefix: string, options?: { ignoreCase?: boolean }): QueryBuilder<T, N>;\n toArray(): Promise<N[]>;\n};\n\n/** Extends the shared query API with `delete()`. Available on stores and IndexedDB transaction callbacks. */\nexport interface QueryBuilder<T extends object, N extends T = T> extends ChainedQuery<T, N, QueryBuilder<T, N>> {\n delete(): Promise<number>;\n}\n\n/* -------------------- Helpers -------------------- */\n\nasync function applyOps<T extends object>(ctx: QueryContext<T>, ops: readonly QueryOp<T>[]): Promise<T[]> {\n let data = await ctx.source();\n\n for (const op of ops) {\n data = op.apply(data);\n }\n\n return data;\n}\n\nfunction assertNonNegativeInteger(value: number, name: string): number {\n if (!Number.isInteger(value) || value < 0) {\n throw new VaultError(`${name} must be a non-negative integer`);\n }\n\n return value;\n}\n\n/* -------------------- Push-down helpers -------------------- */\n\n/** Build a new QueryContext with source replaced by a range/index fetch. Drops range hints since push-down is done. */\nfunction pushDownContext<T extends object>(ctx: QueryContext<T>, newSource: () => Promise<T[]>): QueryContext<T> {\n return { deleteMany: ctx.deleteMany, source: newSource };\n}\n\n/* -------------------- Factory -------------------- */\n\nexport function createQueryBuilder<T extends object, N extends T = T>(\n ctx: QueryContext<T>,\n ops: readonly QueryOp<T>[] = [],\n): QueryBuilder<T, N> {\n const append = (op: QueryOp<T>): QueryBuilder<T, N> => createQueryBuilder<T, N>(ctx, [...ops, op]);\n\n return {\n between(field, lower, upper) {\n const fieldStr = String(field);\n\n if (ops.length === 0) {\n if (ctx.getRange && ctx.keyField === fieldStr) {\n return createQueryBuilder(\n pushDownContext(ctx, () => ctx.getRange!({ lower, type: 'between', upper })),\n ops,\n );\n }\n\n if (ctx.getIndexRange && ctx.indexedFields?.has(fieldStr)) {\n return createQueryBuilder(\n pushDownContext(ctx, () => ctx.getIndexRange!(fieldStr, { lower, type: 'between', upper })),\n ops,\n );\n }\n }\n\n return append({\n apply: (data) =>\n data.filter((r) => {\n const v = r[field] as number | string;\n\n return v >= lower && v <= upper;\n }),\n }) as unknown as QueryBuilder<T, N>;\n },\n count(): Promise<number> {\n const filterOps = ops.filter((op) => !op.isNonFilter);\n\n return applyOps(ctx, filterOps).then((r) => r.length);\n },\n async delete(): Promise<number> {\n if (!ctx.deleteMany) {\n throw new VaultError('query.delete is not available for this adapter context');\n }\n\n const records = await applyOps(ctx, ops);\n\n return ctx.deleteMany(records);\n },\n equals<K extends keyof T & string, V extends T[K]>(field: K, value: V): QueryBuilder<T & Record<K, V>> {\n const fieldStr = String(field);\n\n if (ops.length === 0) {\n if (ctx.getRange && ctx.keyField === fieldStr) {\n return createQueryBuilder<T & Record<K, V>>(\n pushDownContext(\n ctx as unknown as QueryContext<T & Record<K, V>>,\n () => ctx.getRange!({ type: 'eq', value }) as Promise<(T & Record<K, V>)[]>,\n ),\n ) as QueryBuilder<T & Record<K, V>>;\n }\n\n if (ctx.getIndexRange && ctx.indexedFields?.has(fieldStr)) {\n return createQueryBuilder<T & Record<K, V>>(\n pushDownContext(\n ctx as unknown as QueryContext<T & Record<K, V>>,\n () => ctx.getIndexRange!(fieldStr, { type: 'eq', value }) as Promise<(T & Record<K, V>)[]>,\n ),\n ) as QueryBuilder<T & Record<K, V>>;\n }\n }\n\n return createQueryBuilder<T & Record<K, V>>(ctx as unknown as QueryContext<T & Record<K, V>>, [\n ...(ops as unknown as QueryOp<T & Record<K, V>>[]),\n { apply: (data) => data.filter((r) => r[field] === value) as (T & Record<K, V>)[] },\n ]);\n },\n async exists(): Promise<boolean> {\n if (ops.length === 0) return ctx.source().then((r) => r.length > 0);\n\n return applyOps(ctx, ops).then((r) => r.length > 0);\n },\n filter(fn) {\n return append({ apply: (data) => data.filter(fn as Predicate<T>) });\n },\n first(): Promise<N | undefined> {\n if (ops.length === 0) return ctx.source().then((r) => r[0] as N | undefined);\n\n return applyOps(ctx, ops).then((r) => r[0] as N | undefined);\n },\n limit(n) {\n const safeN = assertNonNegativeInteger(n, 'query.limit');\n\n return append({ apply: (data) => data.slice(0, safeN), isNonFilter: true });\n },\n offset(n) {\n const safeN = assertNonNegativeInteger(n, 'query.offset');\n\n return append({ apply: (data) => data.slice(safeN), isNonFilter: true });\n },\n orderBy(field, direction = 'asc') {\n return append({\n apply: (data) => {\n const sign = direction === 'asc' ? 1 : -1;\n\n return [...data].sort((a, b) => {\n const av = a[field] as number | string;\n const bv = b[field] as number | string;\n\n if (av === bv) return 0;\n\n return av > bv ? sign : -sign;\n });\n },\n isNonFilter: true,\n });\n },\n startsWith(field, prefix, { ignoreCase = false } = {}) {\n const fieldStr = String(field);\n\n if (!ignoreCase && prefix.length > 0 && ops.length === 0) {\n if (ctx.getRange && ctx.keyField === fieldStr) {\n return createQueryBuilder(\n pushDownContext(ctx, () => ctx.getRange!({ prefix, type: 'starts' })),\n ops,\n );\n }\n\n if (ctx.getIndexRange && ctx.indexedFields?.has(fieldStr)) {\n return createQueryBuilder(\n pushDownContext(ctx, () => ctx.getIndexRange!(fieldStr, { prefix, type: 'starts' })),\n ops,\n );\n }\n }\n\n const needle = ignoreCase ? prefix.toLowerCase() : prefix;\n\n return append({\n apply: (data) =>\n data.filter((r) => {\n const v = r[field];\n\n if (typeof v !== 'string') return false;\n\n const haystack = ignoreCase ? v.toLowerCase() : v;\n\n return haystack.startsWith(needle);\n }),\n }) as unknown as QueryBuilder<T, N>;\n },\n toArray(): Promise<N[]> {\n return applyOps(ctx, ops) as Promise<N[]>;\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"query.cjs","names":[],"sources":["../src/query.ts"],"sourcesContent":["import { VaultError } from './errors';\n\ntype Predicate<T> = (value: T, index: number, array: T[]) => boolean;\n/**\n * `isNonFilter`: when true this op is excluded from `count()` — it does not restrict\n * *which* records match (limit, offset, orderBy), only how results are presented.\n */\ntype QueryOp<T> = { apply: (data: T[]) => T[]; isNonFilter?: boolean };\n\nexport type QueryContext<T extends object> = {\n deleteMany?: (records: T[]) => Promise<number>;\n source: () => Promise<T[]>;\n};\n\n/* -------------------- Public interfaces -------------------- */\n\n/**\n * Fluent query builder. `T` is the record type.\n */\nexport interface QueryBuilder<T extends object> {\n /**\n * Returns the number of records matching the applied filter predicates.\n * Presentation-only ops (`limit`, `offset`, `orderBy`) are intentionally ignored — this\n * always counts the full filtered set, making paginated total-count queries possible\n * without a second query.\n */\n count(): Promise<number>;\n delete(): Promise<number>;\n /**\n * Filter records where `field` exactly equals `value`.\n */\n equals<K extends keyof T & string, V extends T[K]>(field: K, value: V): QueryBuilder<T>;\n filter(fn: Predicate<T>): QueryBuilder<T>;\n first(): Promise<T | undefined>;\n limit(n: number): QueryBuilder<T>;\n offset(n: number): QueryBuilder<T>;\n orderBy<K extends keyof T>(field: K, direction?: 'asc' | 'desc'): QueryBuilder<T>;\n toArray(): Promise<T[]>;\n}\n\n/* -------------------- Helpers -------------------- */\n\nasync function applyOps<T extends object>(ctx: QueryContext<T>, ops: readonly QueryOp<T>[]): Promise<T[]> {\n let data = await ctx.source();\n\n for (const op of ops) {\n data = op.apply(data);\n }\n\n return data;\n}\n\nfunction assertNonNegativeInteger(value: number, name: string): number {\n if (!Number.isInteger(value) || value < 0) {\n throw new VaultError(`${name} must be a non-negative integer`);\n }\n\n return value;\n}\n\n/* -------------------- Factory -------------------- */\n\nexport function createQueryBuilder<T extends object>(\n ctx: QueryContext<T>,\n ops: readonly QueryOp<T>[] = [],\n): QueryBuilder<T> {\n const append = (op: QueryOp<T>): QueryBuilder<T> => createQueryBuilder<T>(ctx, [...ops, op]);\n\n return {\n count(): Promise<number> {\n const filterOps = ops.filter((op) => !op.isNonFilter);\n\n return applyOps(ctx, filterOps).then((r) => r.length);\n },\n async delete(): Promise<number> {\n if (!ctx.deleteMany) {\n throw new VaultError('query.delete is not available for this adapter context');\n }\n\n const records = await applyOps(ctx, ops);\n\n return ctx.deleteMany(records);\n },\n equals<K extends keyof T & string, V extends T[K]>(field: K, value: V): QueryBuilder<T> {\n return append({ apply: (data) => data.filter((r) => r[field] === value) });\n },\n filter(fn) {\n return append({ apply: (data) => data.filter(fn) });\n },\n first(): Promise<T | undefined> {\n if (ops.length === 0) return ctx.source().then((r) => r[0]);\n\n return applyOps(ctx, ops).then((r) => r[0]);\n },\n limit(n) {\n const safeN = assertNonNegativeInteger(n, 'query.limit');\n\n return append({ apply: (data) => data.slice(0, safeN), isNonFilter: true });\n },\n offset(n) {\n const safeN = assertNonNegativeInteger(n, 'query.offset');\n\n return append({ apply: (data) => data.slice(safeN), isNonFilter: true });\n },\n orderBy(field, direction = 'asc') {\n return append({\n apply: (data) => {\n const sign = direction === 'asc' ? 1 : -1;\n\n return [...data].sort((a, b) => {\n const av = a[field] as number | string;\n const bv = b[field] as number | string;\n\n if (av === bv) return 0;\n\n return av > bv ? sign : -sign;\n });\n },\n isNonFilter: true,\n });\n },\n toArray(): Promise<T[]> {\n return applyOps(ctx, ops);\n },\n };\n}\n"],"mappings":"gCA0CA,eAAe,EAA2B,EAAsB,EAA0C,CACxG,IAAI,EAAO,MAAM,EAAI,OAAO,EAE5B,IAAK,IAAM,KAAM,EACf,EAAO,EAAG,MAAM,CAAI,EAGtB,OAAO,CACT,CAEA,SAAS,EAAyB,EAAe,EAAsB,CACrE,GAAI,CAAC,OAAO,UAAU,CAAK,GAAK,EAAQ,EACtC,MAAM,IAAI,EAAA,WAAW,GAAG,EAAK,gCAAgC,EAG/D,OAAO,CACT,CAIA,SAAgB,EACd,EACA,EAA6B,CAAC,EACb,CACjB,IAAM,EAAU,GAAoC,EAAsB,EAAK,CAAC,GAAG,EAAK,CAAE,CAAC,EAE3F,MAAO,CACL,OAAyB,CAGvB,OAAO,EAAS,EAFE,EAAI,OAAQ,GAAO,CAAC,EAAG,WAEpB,CAAS,CAAC,CAAC,KAAM,GAAM,EAAE,MAAM,CACtD,EACA,MAAM,QAA0B,CAC9B,GAAI,CAAC,EAAI,WACP,MAAM,IAAI,EAAA,WAAW,wDAAwD,EAG/E,IAAM,EAAU,MAAM,EAAS,EAAK,CAAG,EAEvC,OAAO,EAAI,WAAW,CAAO,CAC/B,EACA,OAAmD,EAAU,EAA2B,CACtF,OAAO,EAAO,CAAE,MAAQ,GAAS,EAAK,OAAQ,GAAM,EAAE,KAAW,CAAK,CAAE,CAAC,CAC3E,EACA,OAAO,EAAI,CACT,OAAO,EAAO,CAAE,MAAQ,GAAS,EAAK,OAAO,CAAE,CAAE,CAAC,CACpD,EACA,OAAgC,CAG9B,OAFI,EAAI,SAAW,EAAU,EAAI,OAAO,CAAC,CAAC,KAAM,GAAM,EAAE,EAAE,EAEnD,EAAS,EAAK,CAAG,CAAC,CAAC,KAAM,GAAM,EAAE,EAAE,CAC5C,EACA,MAAM,EAAG,CACP,IAAM,EAAQ,EAAyB,EAAG,aAAa,EAEvD,OAAO,EAAO,CAAE,MAAQ,GAAS,EAAK,MAAM,EAAG,CAAK,EAAG,YAAa,EAAK,CAAC,CAC5E,EACA,OAAO,EAAG,CACR,IAAM,EAAQ,EAAyB,EAAG,cAAc,EAExD,OAAO,EAAO,CAAE,MAAQ,GAAS,EAAK,MAAM,CAAK,EAAG,YAAa,EAAK,CAAC,CACzE,EACA,QAAQ,EAAO,EAAY,MAAO,CAChC,OAAO,EAAO,CACZ,MAAQ,GAAS,CACf,IAAM,EAAO,IAAc,MAAQ,EAAI,GAEvC,MAAO,CAAC,GAAG,CAAI,CAAC,CAAC,MAAM,EAAG,IAAM,CAC9B,IAAM,EAAK,EAAE,GACP,EAAK,EAAE,GAIb,OAFI,IAAO,EAAW,EAEf,EAAK,EAAK,EAAO,CAAC,CAC3B,CAAC,CACH,EACA,YAAa,EACf,CAAC,CACH,EACA,SAAwB,CACtB,OAAO,EAAS,EAAK,CAAG,CAC1B,CACF,CACF"}
|
package/dist/query.d.ts
CHANGED
|
@@ -7,55 +7,14 @@ type QueryOp<T> = {
|
|
|
7
7
|
apply: (data: T[]) => T[];
|
|
8
8
|
isNonFilter?: boolean;
|
|
9
9
|
};
|
|
10
|
-
type ComparableFieldKeys<T extends object> = {
|
|
11
|
-
[K in keyof T]-?: Extract<NonNullable<T[K]>, number | string> extends never ? never : K;
|
|
12
|
-
}[keyof T];
|
|
13
|
-
/**
|
|
14
|
-
* A primary-key range hint that can be pushed down to native storage backends (e.g. IndexedDB).
|
|
15
|
-
* When `QueryContext.getRange` and `keyField` are present, a matching first filter op replaces
|
|
16
|
-
* the full-table `source()` scan with a targeted range fetch.
|
|
17
|
-
*/
|
|
18
|
-
export type NativeRange = {
|
|
19
|
-
type: 'eq';
|
|
20
|
-
value: unknown;
|
|
21
|
-
} | {
|
|
22
|
-
lower: unknown;
|
|
23
|
-
type: 'between';
|
|
24
|
-
upper: unknown;
|
|
25
|
-
} | {
|
|
26
|
-
prefix: string;
|
|
27
|
-
type: 'starts';
|
|
28
|
-
};
|
|
29
10
|
export type QueryContext<T extends object> = {
|
|
30
11
|
deleteMany?: (records: T[]) => Promise<number>;
|
|
31
|
-
/**
|
|
32
|
-
* When present alongside `indexedFields`, replaces `source()` for secondary-index filter ops.
|
|
33
|
-
* Only activated when the first op is `equals`, `between`, or `startsWith` on a field that
|
|
34
|
-
* has an index registered in the schema. IndexedDB uses `IDBIndex.getAll(range)` under the hood.
|
|
35
|
-
*/
|
|
36
|
-
getIndexRange?: (field: string, range: NativeRange) => Promise<T[]>;
|
|
37
|
-
/**
|
|
38
|
-
* When present alongside `keyField`, replaces `source()` for primary-key filter ops.
|
|
39
|
-
* Only activated when the first op is an `equals`, `between`, or case-sensitive `startsWith`
|
|
40
|
-
* on `keyField`. All remaining ops still run in-memory against the range result.
|
|
41
|
-
*/
|
|
42
|
-
getRange?: (range: NativeRange) => Promise<T[]>;
|
|
43
|
-
/** Fields that have secondary indexes — used to detect when a filter op can use `getIndexRange`. */
|
|
44
|
-
indexedFields?: ReadonlySet<string>;
|
|
45
|
-
/** Primary key field name — used to detect when a filter op can be pushed to `getRange`. */
|
|
46
|
-
keyField?: string;
|
|
47
12
|
source: () => Promise<T[]>;
|
|
48
13
|
};
|
|
49
14
|
/**
|
|
50
|
-
*
|
|
51
|
-
* accumulated by `equals()` calls.
|
|
15
|
+
* Fluent query builder. `T` is the record type.
|
|
52
16
|
*/
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Filter records where `field` is between `lower` and `upper` (inclusive).
|
|
56
|
-
* Preserves any type narrowing already accumulated by prior `equals()` calls.
|
|
57
|
-
*/
|
|
58
|
-
between<K extends ComparableFieldKeys<T>>(field: K, lower: Extract<NonNullable<T[K]>, number | string>, upper: Extract<NonNullable<T[K]>, number | string>): QueryBuilder<T, N>;
|
|
17
|
+
export interface QueryBuilder<T extends object> {
|
|
59
18
|
/**
|
|
60
19
|
* Returns the number of records matching the applied filter predicates.
|
|
61
20
|
* Presentation-only ops (`limit`, `offset`, `orderBy`) are intentionally ignored — this
|
|
@@ -63,45 +22,18 @@ type ChainedQuery<T extends object, N extends T, Self extends ChainedQuery<T, N,
|
|
|
63
22
|
* without a second query.
|
|
64
23
|
*/
|
|
65
24
|
count(): Promise<number>;
|
|
25
|
+
delete(): Promise<number>;
|
|
66
26
|
/**
|
|
67
27
|
* Filter records where `field` exactly equals `value`.
|
|
68
|
-
* The return type is narrowed to `QueryBuilder<T & Record<K, V>>` so that subsequent
|
|
69
|
-
* `.toArray()`, `.first()`, etc. reflect the equality constraint in their result type.
|
|
70
|
-
*
|
|
71
|
-
* ```ts
|
|
72
|
-
* // result: (User & { role: 'admin' })[]
|
|
73
|
-
* const admins = await db.query('users').equals('role', 'admin').toArray();
|
|
74
|
-
* ```
|
|
75
|
-
*/
|
|
76
|
-
equals<K extends keyof T & string, V extends T[K]>(field: K, value: V): QueryBuilder<T & Record<K, V>>;
|
|
77
|
-
/**
|
|
78
|
-
* Returns `true` if at least one record matches all applied filter operations.
|
|
79
|
-
* Equivalent to `(await query.first()) !== undefined` but makes the intent explicit.
|
|
80
|
-
* Presentation-only ops (`limit`, `offset`, `orderBy`) are respected before checking.
|
|
81
28
|
*/
|
|
82
|
-
|
|
83
|
-
filter(fn: Predicate<
|
|
84
|
-
first(): Promise<
|
|
85
|
-
limit(n: number):
|
|
86
|
-
offset(n: number):
|
|
87
|
-
orderBy<K extends keyof T>(field: K, direction?: 'asc' | 'desc'):
|
|
88
|
-
|
|
89
|
-
* Filter records where `field` starts with `prefix`.
|
|
90
|
-
* Preserves any type narrowing already accumulated by prior `equals()` calls.
|
|
91
|
-
*
|
|
92
|
-
* **Index push-down** is only active when `ignoreCase` is `false` (default) and this
|
|
93
|
-
* is the first operation in the chain. With `ignoreCase: true`, a full-table scan is
|
|
94
|
-
* always performed regardless of whether the field has an index.
|
|
95
|
-
*/
|
|
96
|
-
startsWith<K extends keyof T>(field: K, prefix: string, options?: {
|
|
97
|
-
ignoreCase?: boolean;
|
|
98
|
-
}): QueryBuilder<T, N>;
|
|
99
|
-
toArray(): Promise<N[]>;
|
|
100
|
-
};
|
|
101
|
-
/** Extends the shared query API with `delete()`. Available on stores and IndexedDB transaction callbacks. */
|
|
102
|
-
export interface QueryBuilder<T extends object, N extends T = T> extends ChainedQuery<T, N, QueryBuilder<T, N>> {
|
|
103
|
-
delete(): Promise<number>;
|
|
29
|
+
equals<K extends keyof T & string, V extends T[K]>(field: K, value: V): QueryBuilder<T>;
|
|
30
|
+
filter(fn: Predicate<T>): QueryBuilder<T>;
|
|
31
|
+
first(): Promise<T | undefined>;
|
|
32
|
+
limit(n: number): QueryBuilder<T>;
|
|
33
|
+
offset(n: number): QueryBuilder<T>;
|
|
34
|
+
orderBy<K extends keyof T>(field: K, direction?: 'asc' | 'desc'): QueryBuilder<T>;
|
|
35
|
+
toArray(): Promise<T[]>;
|
|
104
36
|
}
|
|
105
|
-
export declare function createQueryBuilder<T extends object
|
|
37
|
+
export declare function createQueryBuilder<T extends object>(ctx: QueryContext<T>, ops?: readonly QueryOp<T>[]): QueryBuilder<T>;
|
|
106
38
|
export {};
|
|
107
39
|
//# sourceMappingURL=query.d.ts.map
|
package/dist/query.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAEA,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC;AACrE;;;GAGG;AACH,KAAK,OAAO,CAAC,CAAC,IAAI;IAAE,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAEA,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC;AACrE;;;GAGG;AACH,KAAK,OAAO,CAAC,CAAC,IAAI;IAAE,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEvE,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,IAAI;IAC3C,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;CAC5B,CAAC;AAIF;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,MAAM;IAC5C;;;;;OAKG;IACH,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACzB,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1B;;OAEG;IACH,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;IACxF,MAAM,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1C,KAAK,IAAI,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAChC,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;IAClC,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;IACnC,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;IAClF,OAAO,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;CACzB;AAwBD,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,EACjD,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,EACpB,GAAG,GAAE,SAAS,OAAO,CAAC,CAAC,CAAC,EAAO,GAC9B,YAAY,CAAC,CAAC,CAAC,CA4DjB"}
|
package/dist/query.js
CHANGED
|
@@ -9,81 +9,42 @@ function n(t, n) {
|
|
|
9
9
|
if (!Number.isInteger(t) || t < 0) throw new e(`${n} must be a non-negative integer`);
|
|
10
10
|
return t;
|
|
11
11
|
}
|
|
12
|
-
function r(
|
|
12
|
+
function r(i, a = []) {
|
|
13
|
+
let o = (e) => r(i, [...a, e]);
|
|
13
14
|
return {
|
|
14
|
-
deleteMany: e.deleteMany,
|
|
15
|
-
source: t
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
function i(a, o = []) {
|
|
19
|
-
let s = (e) => i(a, [...o, e]);
|
|
20
|
-
return {
|
|
21
|
-
between(e, t, n) {
|
|
22
|
-
let c = String(e);
|
|
23
|
-
if (o.length === 0) {
|
|
24
|
-
if (a.getRange && a.keyField === c) return i(r(a, () => a.getRange({
|
|
25
|
-
lower: t,
|
|
26
|
-
type: "between",
|
|
27
|
-
upper: n
|
|
28
|
-
})), o);
|
|
29
|
-
if (a.getIndexRange && a.indexedFields?.has(c)) return i(r(a, () => a.getIndexRange(c, {
|
|
30
|
-
lower: t,
|
|
31
|
-
type: "between",
|
|
32
|
-
upper: n
|
|
33
|
-
})), o);
|
|
34
|
-
}
|
|
35
|
-
return s({ apply: (r) => r.filter((r) => {
|
|
36
|
-
let i = r[e];
|
|
37
|
-
return i >= t && i <= n;
|
|
38
|
-
}) });
|
|
39
|
-
},
|
|
40
15
|
count() {
|
|
41
|
-
return t(
|
|
16
|
+
return t(i, a.filter((e) => !e.isNonFilter)).then((e) => e.length);
|
|
42
17
|
},
|
|
43
18
|
async delete() {
|
|
44
|
-
if (!
|
|
45
|
-
let n = await t(
|
|
46
|
-
return
|
|
19
|
+
if (!i.deleteMany) throw new e("query.delete is not available for this adapter context");
|
|
20
|
+
let n = await t(i, a);
|
|
21
|
+
return i.deleteMany(n);
|
|
47
22
|
},
|
|
48
23
|
equals(e, t) {
|
|
49
|
-
|
|
50
|
-
if (o.length === 0) {
|
|
51
|
-
if (a.getRange && a.keyField === n) return i(r(a, () => a.getRange({
|
|
52
|
-
type: "eq",
|
|
53
|
-
value: t
|
|
54
|
-
})));
|
|
55
|
-
if (a.getIndexRange && a.indexedFields?.has(n)) return i(r(a, () => a.getIndexRange(n, {
|
|
56
|
-
type: "eq",
|
|
57
|
-
value: t
|
|
58
|
-
})));
|
|
59
|
-
}
|
|
60
|
-
return i(a, [...o, { apply: (n) => n.filter((n) => n[e] === t) }]);
|
|
61
|
-
},
|
|
62
|
-
async exists() {
|
|
63
|
-
return o.length === 0 ? a.source().then((e) => e.length > 0) : t(a, o).then((e) => e.length > 0);
|
|
24
|
+
return o({ apply: (n) => n.filter((n) => n[e] === t) });
|
|
64
25
|
},
|
|
65
26
|
filter(e) {
|
|
66
|
-
return
|
|
27
|
+
return o({ apply: (t) => t.filter(e) });
|
|
67
28
|
},
|
|
68
29
|
first() {
|
|
69
|
-
return
|
|
30
|
+
return a.length === 0 ? i.source().then((e) => e[0]) : t(i, a).then((e) => e[0]);
|
|
70
31
|
},
|
|
71
32
|
limit(e) {
|
|
72
33
|
let t = n(e, "query.limit");
|
|
73
|
-
return
|
|
34
|
+
return o({
|
|
74
35
|
apply: (e) => e.slice(0, t),
|
|
75
36
|
isNonFilter: !0
|
|
76
37
|
});
|
|
77
38
|
},
|
|
78
39
|
offset(e) {
|
|
79
40
|
let t = n(e, "query.offset");
|
|
80
|
-
return
|
|
41
|
+
return o({
|
|
81
42
|
apply: (e) => e.slice(t),
|
|
82
43
|
isNonFilter: !0
|
|
83
44
|
});
|
|
84
45
|
},
|
|
85
46
|
orderBy(e, t = "asc") {
|
|
86
|
-
return
|
|
47
|
+
return o({
|
|
87
48
|
apply: (n) => {
|
|
88
49
|
let r = t === "asc" ? 1 : -1;
|
|
89
50
|
return [...n].sort((t, n) => {
|
|
@@ -94,30 +55,12 @@ function i(a, o = []) {
|
|
|
94
55
|
isNonFilter: !0
|
|
95
56
|
});
|
|
96
57
|
},
|
|
97
|
-
startsWith(e, t, { ignoreCase: n = !1 } = {}) {
|
|
98
|
-
let c = String(e);
|
|
99
|
-
if (!n && t.length > 0 && o.length === 0) {
|
|
100
|
-
if (a.getRange && a.keyField === c) return i(r(a, () => a.getRange({
|
|
101
|
-
prefix: t,
|
|
102
|
-
type: "starts"
|
|
103
|
-
})), o);
|
|
104
|
-
if (a.getIndexRange && a.indexedFields?.has(c)) return i(r(a, () => a.getIndexRange(c, {
|
|
105
|
-
prefix: t,
|
|
106
|
-
type: "starts"
|
|
107
|
-
})), o);
|
|
108
|
-
}
|
|
109
|
-
let l = n ? t.toLowerCase() : t;
|
|
110
|
-
return s({ apply: (t) => t.filter((t) => {
|
|
111
|
-
let r = t[e];
|
|
112
|
-
return typeof r == "string" && (n ? r.toLowerCase() : r).startsWith(l);
|
|
113
|
-
}) });
|
|
114
|
-
},
|
|
115
58
|
toArray() {
|
|
116
|
-
return t(
|
|
59
|
+
return t(i, a);
|
|
117
60
|
}
|
|
118
61
|
};
|
|
119
62
|
}
|
|
120
63
|
//#endregion
|
|
121
|
-
export {
|
|
64
|
+
export { r as createQueryBuilder };
|
|
122
65
|
|
|
123
66
|
//# sourceMappingURL=query.js.map
|
package/dist/query.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.js","names":[],"sources":["../src/query.ts"],"sourcesContent":["import { VaultError } from './errors';\n\ntype Predicate<T> = (value: T, index: number, array: T[]) => boolean;\n/**\n * `isNonFilter`: when true this op is excluded from `count()` — it does not restrict\n * *which* records match (limit, offset, orderBy), only how results are presented.\n */\ntype QueryOp<T> = { apply: (data: T[]) => T[]; isNonFilter?: boolean };\ntype ComparableFieldKeys<T extends object> = {\n [K in keyof T]-?: Extract<NonNullable<T[K]>, number | string> extends never ? never : K;\n}[keyof T];\n\n/**\n * A primary-key range hint that can be pushed down to native storage backends (e.g. IndexedDB).\n * When `QueryContext.getRange` and `keyField` are present, a matching first filter op replaces\n * the full-table `source()` scan with a targeted range fetch.\n */\nexport type NativeRange =\n | { type: 'eq'; value: unknown }\n | { lower: unknown; type: 'between'; upper: unknown }\n | { prefix: string; type: 'starts' };\n\nexport type QueryContext<T extends object> = {\n deleteMany?: (records: T[]) => Promise<number>;\n /**\n * When present alongside `indexedFields`, replaces `source()` for secondary-index filter ops.\n * Only activated when the first op is `equals`, `between`, or `startsWith` on a field that\n * has an index registered in the schema. IndexedDB uses `IDBIndex.getAll(range)` under the hood.\n */\n getIndexRange?: (field: string, range: NativeRange) => Promise<T[]>;\n /**\n * When present alongside `keyField`, replaces `source()` for primary-key filter ops.\n * Only activated when the first op is an `equals`, `between`, or case-sensitive `startsWith`\n * on `keyField`. All remaining ops still run in-memory against the range result.\n */\n getRange?: (range: NativeRange) => Promise<T[]>;\n /** Fields that have secondary indexes — used to detect when a filter op can use `getIndexRange`. */\n indexedFields?: ReadonlySet<string>;\n /** Primary key field name — used to detect when a filter op can be pushed to `getRange`. */\n keyField?: string;\n source: () => Promise<T[]>;\n};\n\n/* -------------------- Public interfaces -------------------- */\n\n/**\n * Shared query methods. `T` is the base record type; `N` is the progressively-narrowed type\n * accumulated by `equals()` calls.\n */\ntype ChainedQuery<T extends object, N extends T, Self extends ChainedQuery<T, N, Self>> = {\n /**\n * Filter records where `field` is between `lower` and `upper` (inclusive).\n * Preserves any type narrowing already accumulated by prior `equals()` calls.\n */\n between<K extends ComparableFieldKeys<T>>(\n field: K,\n lower: Extract<NonNullable<T[K]>, number | string>,\n upper: Extract<NonNullable<T[K]>, number | string>,\n ): QueryBuilder<T, N>;\n /**\n * Returns the number of records matching the applied filter predicates.\n * Presentation-only ops (`limit`, `offset`, `orderBy`) are intentionally ignored — this\n * always counts the full filtered set, making paginated total-count queries possible\n * without a second query.\n */\n count(): Promise<number>;\n /**\n * Filter records where `field` exactly equals `value`.\n * The return type is narrowed to `QueryBuilder<T & Record<K, V>>` so that subsequent\n * `.toArray()`, `.first()`, etc. reflect the equality constraint in their result type.\n *\n * ```ts\n * // result: (User & { role: 'admin' })[]\n * const admins = await db.query('users').equals('role', 'admin').toArray();\n * ```\n */\n equals<K extends keyof T & string, V extends T[K]>(field: K, value: V): QueryBuilder<T & Record<K, V>>;\n /**\n * Returns `true` if at least one record matches all applied filter operations.\n * Equivalent to `(await query.first()) !== undefined` but makes the intent explicit.\n * Presentation-only ops (`limit`, `offset`, `orderBy`) are respected before checking.\n */\n exists(): Promise<boolean>;\n filter(fn: Predicate<N>): Self;\n first(): Promise<N | undefined>;\n limit(n: number): Self;\n offset(n: number): Self;\n orderBy<K extends keyof T>(field: K, direction?: 'asc' | 'desc'): Self;\n /**\n * Filter records where `field` starts with `prefix`.\n * Preserves any type narrowing already accumulated by prior `equals()` calls.\n *\n * **Index push-down** is only active when `ignoreCase` is `false` (default) and this\n * is the first operation in the chain. With `ignoreCase: true`, a full-table scan is\n * always performed regardless of whether the field has an index.\n */\n startsWith<K extends keyof T>(field: K, prefix: string, options?: { ignoreCase?: boolean }): QueryBuilder<T, N>;\n toArray(): Promise<N[]>;\n};\n\n/** Extends the shared query API with `delete()`. Available on stores and IndexedDB transaction callbacks. */\nexport interface QueryBuilder<T extends object, N extends T = T> extends ChainedQuery<T, N, QueryBuilder<T, N>> {\n delete(): Promise<number>;\n}\n\n/* -------------------- Helpers -------------------- */\n\nasync function applyOps<T extends object>(ctx: QueryContext<T>, ops: readonly QueryOp<T>[]): Promise<T[]> {\n let data = await ctx.source();\n\n for (const op of ops) {\n data = op.apply(data);\n }\n\n return data;\n}\n\nfunction assertNonNegativeInteger(value: number, name: string): number {\n if (!Number.isInteger(value) || value < 0) {\n throw new VaultError(`${name} must be a non-negative integer`);\n }\n\n return value;\n}\n\n/* -------------------- Push-down helpers -------------------- */\n\n/** Build a new QueryContext with source replaced by a range/index fetch. Drops range hints since push-down is done. */\nfunction pushDownContext<T extends object>(ctx: QueryContext<T>, newSource: () => Promise<T[]>): QueryContext<T> {\n return { deleteMany: ctx.deleteMany, source: newSource };\n}\n\n/* -------------------- Factory -------------------- */\n\nexport function createQueryBuilder<T extends object, N extends T = T>(\n ctx: QueryContext<T>,\n ops: readonly QueryOp<T>[] = [],\n): QueryBuilder<T, N> {\n const append = (op: QueryOp<T>): QueryBuilder<T, N> => createQueryBuilder<T, N>(ctx, [...ops, op]);\n\n return {\n between(field, lower, upper) {\n const fieldStr = String(field);\n\n if (ops.length === 0) {\n if (ctx.getRange && ctx.keyField === fieldStr) {\n return createQueryBuilder(\n pushDownContext(ctx, () => ctx.getRange!({ lower, type: 'between', upper })),\n ops,\n );\n }\n\n if (ctx.getIndexRange && ctx.indexedFields?.has(fieldStr)) {\n return createQueryBuilder(\n pushDownContext(ctx, () => ctx.getIndexRange!(fieldStr, { lower, type: 'between', upper })),\n ops,\n );\n }\n }\n\n return append({\n apply: (data) =>\n data.filter((r) => {\n const v = r[field] as number | string;\n\n return v >= lower && v <= upper;\n }),\n }) as unknown as QueryBuilder<T, N>;\n },\n count(): Promise<number> {\n const filterOps = ops.filter((op) => !op.isNonFilter);\n\n return applyOps(ctx, filterOps).then((r) => r.length);\n },\n async delete(): Promise<number> {\n if (!ctx.deleteMany) {\n throw new VaultError('query.delete is not available for this adapter context');\n }\n\n const records = await applyOps(ctx, ops);\n\n return ctx.deleteMany(records);\n },\n equals<K extends keyof T & string, V extends T[K]>(field: K, value: V): QueryBuilder<T & Record<K, V>> {\n const fieldStr = String(field);\n\n if (ops.length === 0) {\n if (ctx.getRange && ctx.keyField === fieldStr) {\n return createQueryBuilder<T & Record<K, V>>(\n pushDownContext(\n ctx as unknown as QueryContext<T & Record<K, V>>,\n () => ctx.getRange!({ type: 'eq', value }) as Promise<(T & Record<K, V>)[]>,\n ),\n ) as QueryBuilder<T & Record<K, V>>;\n }\n\n if (ctx.getIndexRange && ctx.indexedFields?.has(fieldStr)) {\n return createQueryBuilder<T & Record<K, V>>(\n pushDownContext(\n ctx as unknown as QueryContext<T & Record<K, V>>,\n () => ctx.getIndexRange!(fieldStr, { type: 'eq', value }) as Promise<(T & Record<K, V>)[]>,\n ),\n ) as QueryBuilder<T & Record<K, V>>;\n }\n }\n\n return createQueryBuilder<T & Record<K, V>>(ctx as unknown as QueryContext<T & Record<K, V>>, [\n ...(ops as unknown as QueryOp<T & Record<K, V>>[]),\n { apply: (data) => data.filter((r) => r[field] === value) as (T & Record<K, V>)[] },\n ]);\n },\n async exists(): Promise<boolean> {\n if (ops.length === 0) return ctx.source().then((r) => r.length > 0);\n\n return applyOps(ctx, ops).then((r) => r.length > 0);\n },\n filter(fn) {\n return append({ apply: (data) => data.filter(fn as Predicate<T>) });\n },\n first(): Promise<N | undefined> {\n if (ops.length === 0) return ctx.source().then((r) => r[0] as N | undefined);\n\n return applyOps(ctx, ops).then((r) => r[0] as N | undefined);\n },\n limit(n) {\n const safeN = assertNonNegativeInteger(n, 'query.limit');\n\n return append({ apply: (data) => data.slice(0, safeN), isNonFilter: true });\n },\n offset(n) {\n const safeN = assertNonNegativeInteger(n, 'query.offset');\n\n return append({ apply: (data) => data.slice(safeN), isNonFilter: true });\n },\n orderBy(field, direction = 'asc') {\n return append({\n apply: (data) => {\n const sign = direction === 'asc' ? 1 : -1;\n\n return [...data].sort((a, b) => {\n const av = a[field] as number | string;\n const bv = b[field] as number | string;\n\n if (av === bv) return 0;\n\n return av > bv ? sign : -sign;\n });\n },\n isNonFilter: true,\n });\n },\n startsWith(field, prefix, { ignoreCase = false } = {}) {\n const fieldStr = String(field);\n\n if (!ignoreCase && prefix.length > 0 && ops.length === 0) {\n if (ctx.getRange && ctx.keyField === fieldStr) {\n return createQueryBuilder(\n pushDownContext(ctx, () => ctx.getRange!({ prefix, type: 'starts' })),\n ops,\n );\n }\n\n if (ctx.getIndexRange && ctx.indexedFields?.has(fieldStr)) {\n return createQueryBuilder(\n pushDownContext(ctx, () => ctx.getIndexRange!(fieldStr, { prefix, type: 'starts' })),\n ops,\n );\n }\n }\n\n const needle = ignoreCase ? prefix.toLowerCase() : prefix;\n\n return append({\n apply: (data) =>\n data.filter((r) => {\n const v = r[field];\n\n if (typeof v !== 'string') return false;\n\n const haystack = ignoreCase ? v.toLowerCase() : v;\n\n return haystack.startsWith(needle);\n }),\n }) as unknown as QueryBuilder<T, N>;\n },\n toArray(): Promise<N[]> {\n return applyOps(ctx, ops) as Promise<N[]>;\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"query.js","names":[],"sources":["../src/query.ts"],"sourcesContent":["import { VaultError } from './errors';\n\ntype Predicate<T> = (value: T, index: number, array: T[]) => boolean;\n/**\n * `isNonFilter`: when true this op is excluded from `count()` — it does not restrict\n * *which* records match (limit, offset, orderBy), only how results are presented.\n */\ntype QueryOp<T> = { apply: (data: T[]) => T[]; isNonFilter?: boolean };\n\nexport type QueryContext<T extends object> = {\n deleteMany?: (records: T[]) => Promise<number>;\n source: () => Promise<T[]>;\n};\n\n/* -------------------- Public interfaces -------------------- */\n\n/**\n * Fluent query builder. `T` is the record type.\n */\nexport interface QueryBuilder<T extends object> {\n /**\n * Returns the number of records matching the applied filter predicates.\n * Presentation-only ops (`limit`, `offset`, `orderBy`) are intentionally ignored — this\n * always counts the full filtered set, making paginated total-count queries possible\n * without a second query.\n */\n count(): Promise<number>;\n delete(): Promise<number>;\n /**\n * Filter records where `field` exactly equals `value`.\n */\n equals<K extends keyof T & string, V extends T[K]>(field: K, value: V): QueryBuilder<T>;\n filter(fn: Predicate<T>): QueryBuilder<T>;\n first(): Promise<T | undefined>;\n limit(n: number): QueryBuilder<T>;\n offset(n: number): QueryBuilder<T>;\n orderBy<K extends keyof T>(field: K, direction?: 'asc' | 'desc'): QueryBuilder<T>;\n toArray(): Promise<T[]>;\n}\n\n/* -------------------- Helpers -------------------- */\n\nasync function applyOps<T extends object>(ctx: QueryContext<T>, ops: readonly QueryOp<T>[]): Promise<T[]> {\n let data = await ctx.source();\n\n for (const op of ops) {\n data = op.apply(data);\n }\n\n return data;\n}\n\nfunction assertNonNegativeInteger(value: number, name: string): number {\n if (!Number.isInteger(value) || value < 0) {\n throw new VaultError(`${name} must be a non-negative integer`);\n }\n\n return value;\n}\n\n/* -------------------- Factory -------------------- */\n\nexport function createQueryBuilder<T extends object>(\n ctx: QueryContext<T>,\n ops: readonly QueryOp<T>[] = [],\n): QueryBuilder<T> {\n const append = (op: QueryOp<T>): QueryBuilder<T> => createQueryBuilder<T>(ctx, [...ops, op]);\n\n return {\n count(): Promise<number> {\n const filterOps = ops.filter((op) => !op.isNonFilter);\n\n return applyOps(ctx, filterOps).then((r) => r.length);\n },\n async delete(): Promise<number> {\n if (!ctx.deleteMany) {\n throw new VaultError('query.delete is not available for this adapter context');\n }\n\n const records = await applyOps(ctx, ops);\n\n return ctx.deleteMany(records);\n },\n equals<K extends keyof T & string, V extends T[K]>(field: K, value: V): QueryBuilder<T> {\n return append({ apply: (data) => data.filter((r) => r[field] === value) });\n },\n filter(fn) {\n return append({ apply: (data) => data.filter(fn) });\n },\n first(): Promise<T | undefined> {\n if (ops.length === 0) return ctx.source().then((r) => r[0]);\n\n return applyOps(ctx, ops).then((r) => r[0]);\n },\n limit(n) {\n const safeN = assertNonNegativeInteger(n, 'query.limit');\n\n return append({ apply: (data) => data.slice(0, safeN), isNonFilter: true });\n },\n offset(n) {\n const safeN = assertNonNegativeInteger(n, 'query.offset');\n\n return append({ apply: (data) => data.slice(safeN), isNonFilter: true });\n },\n orderBy(field, direction = 'asc') {\n return append({\n apply: (data) => {\n const sign = direction === 'asc' ? 1 : -1;\n\n return [...data].sort((a, b) => {\n const av = a[field] as number | string;\n const bv = b[field] as number | string;\n\n if (av === bv) return 0;\n\n return av > bv ? sign : -sign;\n });\n },\n isNonFilter: true,\n });\n },\n toArray(): Promise<T[]> {\n return applyOps(ctx, ops);\n },\n };\n}\n"],"mappings":";;AA0CA,eAAe,EAA2B,GAAsB,GAA0C;CACxG,IAAI,IAAO,MAAM,EAAI,OAAO;CAE5B,KAAK,IAAM,KAAM,GACf,IAAO,EAAG,MAAM,CAAI;CAGtB,OAAO;AACT;AAEA,SAAS,EAAyB,GAAe,GAAsB;CACrE,IAAI,CAAC,OAAO,UAAU,CAAK,KAAK,IAAQ,GACtC,MAAM,IAAI,EAAW,GAAG,EAAK,gCAAgC;CAG/D,OAAO;AACT;AAIA,SAAgB,EACd,GACA,IAA6B,CAAC,GACb;CACjB,IAAM,KAAU,MAAoC,EAAsB,GAAK,CAAC,GAAG,GAAK,CAAE,CAAC;CAE3F,OAAO;EACL,QAAyB;GAGvB,OAAO,EAAS,GAFE,EAAI,QAAQ,MAAO,CAAC,EAAG,WAEpB,CAAS,CAAC,CAAC,MAAM,MAAM,EAAE,MAAM;EACtD;EACA,MAAM,SAA0B;GAC9B,IAAI,CAAC,EAAI,YACP,MAAM,IAAI,EAAW,wDAAwD;GAG/E,IAAM,IAAU,MAAM,EAAS,GAAK,CAAG;GAEvC,OAAO,EAAI,WAAW,CAAO;EAC/B;EACA,OAAmD,GAAU,GAA2B;GACtF,OAAO,EAAO,EAAE,QAAQ,MAAS,EAAK,QAAQ,MAAM,EAAE,OAAW,CAAK,EAAE,CAAC;EAC3E;EACA,OAAO,GAAI;GACT,OAAO,EAAO,EAAE,QAAQ,MAAS,EAAK,OAAO,CAAE,EAAE,CAAC;EACpD;EACA,QAAgC;GAG9B,OAFI,EAAI,WAAW,IAAU,EAAI,OAAO,CAAC,CAAC,MAAM,MAAM,EAAE,EAAE,IAEnD,EAAS,GAAK,CAAG,CAAC,CAAC,MAAM,MAAM,EAAE,EAAE;EAC5C;EACA,MAAM,GAAG;GACP,IAAM,IAAQ,EAAyB,GAAG,aAAa;GAEvD,OAAO,EAAO;IAAE,QAAQ,MAAS,EAAK,MAAM,GAAG,CAAK;IAAG,aAAa;GAAK,CAAC;EAC5E;EACA,OAAO,GAAG;GACR,IAAM,IAAQ,EAAyB,GAAG,cAAc;GAExD,OAAO,EAAO;IAAE,QAAQ,MAAS,EAAK,MAAM,CAAK;IAAG,aAAa;GAAK,CAAC;EACzE;EACA,QAAQ,GAAO,IAAY,OAAO;GAChC,OAAO,EAAO;IACZ,QAAQ,MAAS;KACf,IAAM,IAAO,MAAc,QAAQ,IAAI;KAEvC,OAAO,CAAC,GAAG,CAAI,CAAC,CAAC,MAAM,GAAG,MAAM;MAC9B,IAAM,IAAK,EAAE,IACP,IAAK,EAAE;MAIb,OAFI,MAAO,IAAW,IAEf,IAAK,IAAK,IAAO,CAAC;KAC3B,CAAC;IACH;IACA,aAAa;GACf,CAAC;EACH;EACA,UAAwB;GACtB,OAAO,EAAS,GAAK,CAAG;EAC1B;CACF;AACF"}
|
package/dist/sqlite.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type { SQLiteDatabase, SQLiteParameter, SQLiteStatement, SQLiteVaultOptions,
|
|
1
|
+
export type { SQLiteDatabase, SQLiteParameter, SQLiteStatement, SQLiteVaultOptions, TransactionContext, } from './adapters/sqlite';
|
|
2
2
|
export { createSQLite } from './adapters/sqlite';
|
|
3
3
|
//# sourceMappingURL=sqlite.d.ts.map
|
package/dist/sqlite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../src/sqlite.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../src/sqlite.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/types.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["/// <reference lib=\"dom\" />\n\nimport { VaultError } from './errors';\nimport type { QueryBuilder } from './query';\nimport { assertPositiveFinite } from './ttl';\n\n/** Portable primary-key values. Vault preserves their type and encodes them distinctly at rest. */\nexport type VaultKey = number | string;\n\n/** A typed table definition whose primary-key field must hold a portable Vault key. */\nexport type SchemaEntry<T extends object, Key extends keyof T & string = keyof T & string> = {\n defaultTtl?: number;\n /** IndexedDB creates these as `value.<field>` indexes; other stores filter in memory. */\n indexes?: readonly (keyof T & string)[];\n key: Key;\n};\n\nexport type AnySchema = Record<string, { defaultTtl?: number; indexes?: readonly string[]; key: string }>;\n\nexport type RecordOf<S extends AnySchema, K extends keyof S> =\n S[K] extends SchemaEntry<infer R, infer _Key> ? R : never;\nexport type KeyOf<S extends AnySchema, K extends keyof S> = Extract<\n S[K] extends SchemaEntry<infer R, infer Key> ? R[Key] : never,\n VaultKey\n>;\n\nexport type
|
|
1
|
+
{"version":3,"file":"types.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["/// <reference lib=\"dom\" />\n\nimport { VaultError } from './errors';\nimport type { QueryBuilder } from './query';\nimport { assertPositiveFinite } from './ttl';\n\n/** Portable primary-key values. Vault preserves their type and encodes them distinctly at rest. */\nexport type VaultKey = number | string;\n\n/** A typed table definition whose primary-key field must hold a portable Vault key. */\nexport type SchemaEntry<T extends object, Key extends keyof T & string = keyof T & string> = {\n defaultTtl?: number;\n /** IndexedDB creates these as `value.<field>` indexes; other stores filter in memory. */\n indexes?: readonly (keyof T & string)[];\n key: Key;\n};\n\nexport type AnySchema = Record<string, { defaultTtl?: number; indexes?: readonly string[]; key: string }>;\n\nexport type RecordOf<S extends AnySchema, K extends keyof S> =\n S[K] extends SchemaEntry<infer R, infer _Key> ? R : never;\nexport type KeyOf<S extends AnySchema, K extends keyof S> = Extract<\n S[K] extends SchemaEntry<infer R, infer Key> ? R[Key] : never,\n VaultKey\n>;\n\nexport type RecordValidator<T> = {\n parse(value: unknown): T;\n};\n\nexport type TableValidators<S extends AnySchema> = {\n [K in keyof S]?: RecordValidator<RecordOf<S, K>>;\n};\n\nexport type Observer<T> = (records: T[]) => void;\nexport type Unsubscribe = () => void;\n\n/** Shared factory options. Values always use Vault's fixed `{ value, expiresAt? }` envelope. */\nexport type BaseAdapterOptions<S extends AnySchema> = {\n schema: S;\n validators?: TableValidators<S>;\n};\n\n/** Methods available within an IndexedDB or SQLite transaction. */\nexport type TransactionContext<S extends AnySchema, K extends keyof S & string = keyof S & string> = {\n clear<T extends K>(table: T): Promise<void>;\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 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 getMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<Array<RecordOf<S, T> | undefined>>;\n has<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;\n isEmpty<T extends K>(table: T): Promise<boolean>;\n keys<T extends K>(table: T, filter?: (record: RecordOf<S, T>) => boolean): Promise<KeyOf<S, T>[]>;\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 query<T extends K>(table: T): QueryBuilder<RecordOf<S, T>>;\n update<T extends K>(\n table: T,\n key: KeyOf<S, T>,\n changes: Partial<RecordOf<S, T>>,\n ttl?: number,\n ): Promise<RecordOf<S, T> | undefined>;\n upsert<T extends K>(\n table: T,\n key: KeyOf<S, T>,\n fn: (existing: RecordOf<S, T> | undefined) => RecordOf<S, T>,\n ttl?: number,\n ): Promise<RecordOf<S, T>>;\n};\n\n/** Portable API returned by memory and Web Storage factories. */\nexport interface VaultStore<S extends AnySchema> {\n clear<K extends keyof S & string>(table: K): Promise<void>;\n count<K extends keyof S & string>(table: K): Promise<number>;\n delete<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<boolean>;\n deleteMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<number>;\n readonly disposalSignal: AbortSignal;\n dispose(): Promise<void>;\n readonly disposed: boolean;\n get<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<RecordOf<S, K> | undefined>;\n getAll<K extends keyof S & string>(table: K): Promise<RecordOf<S, K>[]>;\n getMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<Array<RecordOf<S, K> | undefined>>;\n has<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<boolean>;\n isEmpty<K extends keyof S & string>(table: K): Promise<boolean>;\n keys<K extends keyof S & string>(table: K, filter?: (record: RecordOf<S, K>) => boolean): Promise<KeyOf<S, K>[]>;\n observe<K extends keyof S & string>(\n table: K,\n listener: Observer<RecordOf<S, K>>,\n options?: { immediate?: boolean; signal?: AbortSignal },\n ): Unsubscribe;\n pruneExpired(): Promise<Record<keyof S & string, number>>;\n put<K extends keyof S & string>(table: K, value: RecordOf<S, K>, ttl?: number): Promise<void>;\n putAll<K extends keyof S & string>(table: K, values: RecordOf<S, K>[], ttl?: number): Promise<void>;\n query<K extends keyof S & string>(table: K): QueryBuilder<RecordOf<S, K>>;\n update<K extends keyof S & string>(\n table: K,\n key: KeyOf<S, K>,\n changes: Partial<RecordOf<S, K>>,\n ttl?: number,\n ): Promise<RecordOf<S, K> | undefined>;\n upsert<K extends keyof S & string>(\n table: K,\n key: KeyOf<S, K>,\n fn: (existing: RecordOf<S, K> | undefined) => RecordOf<S, K>,\n ttl?: number,\n ): Promise<RecordOf<S, K>>;\n [Symbol.asyncDispose](): Promise<void>;\n}\n\n/** Atomic, scoped transactions and lazy iteration supplied by storage engines that support them. */\nexport interface TransactionalVaultStore<S extends AnySchema> extends VaultStore<S> {\n batch<K extends keyof S & string, R>(\n tables: readonly K[],\n fn: (tx: TransactionContext<S, K>) => Promise<R>,\n ): Promise<R>;\n iterate<K extends keyof S & string>(table: K): AsyncIterable<RecordOf<S, K>>;\n}\n\n/** Define a typed table whose primary-key field holds a portable Vault key. */\nexport function table<T extends object, Key extends keyof T & string = keyof T & string>(\n key: Key,\n options: { defaultTtl?: number; indexes?: readonly (keyof T & string)[] } = {},\n): SchemaEntry<T, Key> {\n const { defaultTtl, indexes } = options;\n\n if (defaultTtl !== undefined) assertPositiveFinite(defaultTtl, 'table: defaultTtl');\n\n if (indexes) {\n const seen = new Set<string>();\n\n for (const field of indexes) {\n if (seen.has(field)) {\n throw new VaultError(`table: index \"${field}\" is already registered`);\n }\n\n seen.add(field);\n }\n }\n\n return { defaultTtl, indexes, key };\n}\n"],"mappings":"uDAyHA,SAAgB,EACd,EACA,EAA4E,CAAC,EACxD,CACrB,GAAM,CAAE,aAAY,WAAY,EAIhC,GAFI,IAAe,IAAA,IAAW,EAAA,qBAAqB,EAAY,mBAAmB,EAE9E,EAAS,CACX,IAAM,EAAO,IAAI,IAEjB,IAAK,IAAM,KAAS,EAAS,CAC3B,GAAI,EAAK,IAAI,CAAK,EAChB,MAAM,IAAI,EAAA,WAAW,iBAAiB,EAAM,wBAAwB,EAGtE,EAAK,IAAI,CAAK,CAChB,CACF,CAEA,MAAO,CAAE,aAAY,UAAS,KAAI,CACpC"}
|