@vielzeug/vault 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -19
- package/dist/_dev.cjs +1 -1
- package/dist/_dev.cjs.map +1 -1
- package/dist/_dev.js +2 -3
- package/dist/_dev.js.map +1 -1
- package/dist/adapter-core.cjs +1 -1
- package/dist/adapter-core.cjs.map +1 -1
- package/dist/adapter-core.d.ts.map +1 -1
- package/dist/adapter-core.js +2 -2
- package/dist/adapter-core.js.map +1 -1
- package/dist/adapters/indexeddb.cjs +1 -1
- package/dist/adapters/indexeddb.cjs.map +1 -1
- package/dist/adapters/indexeddb.d.ts +46 -2
- package/dist/adapters/indexeddb.d.ts.map +1 -1
- package/dist/adapters/indexeddb.js +76 -56
- package/dist/adapters/indexeddb.js.map +1 -1
- package/dist/adapters/memory.cjs.map +1 -1
- package/dist/adapters/memory.d.ts.map +1 -1
- package/dist/adapters/memory.js.map +1 -1
- package/dist/adapters/sqlite.cjs +1 -1
- package/dist/adapters/sqlite.cjs.map +1 -1
- package/dist/adapters/sqlite.d.ts +2 -2
- package/dist/adapters/sqlite.d.ts.map +1 -1
- package/dist/adapters/sqlite.js +1 -1
- 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 +1 -1
- package/dist/adapters/webstorage.d.ts.map +1 -1
- package/dist/adapters/webstorage.js +1 -1
- package/dist/adapters/webstorage.js.map +1 -1
- package/dist/errors.cjs +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +0 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -4
- package/dist/errors.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/indexeddb.cjs +1 -1
- package/dist/indexeddb.d.ts +3 -4
- package/dist/indexeddb.d.ts.map +1 -1
- package/dist/indexeddb.js +1 -2
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/prune.cjs +1 -1
- package/dist/prune.cjs.map +1 -1
- package/dist/prune.d.ts +8 -19
- package/dist/prune.d.ts.map +1 -1
- package/dist/prune.js +9 -13
- package/dist/prune.js.map +1 -1
- package/dist/query.cjs +1 -1
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.ts +5 -11
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +1 -4
- package/dist/query.js.map +1 -1
- package/dist/sqlite.d.ts +1 -1
- package/dist/sqlite.d.ts.map +1 -1
- package/dist/ttl.cjs +1 -1
- package/dist/ttl.cjs.map +1 -1
- package/dist/ttl.d.ts +8 -19
- package/dist/ttl.d.ts.map +1 -1
- package/dist/ttl.js +5 -5
- package/dist/ttl.js.map +1 -1
- package/dist/types.cjs +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.ts +28 -40
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +14 -19
- package/dist/types.js.map +1 -1
- package/dist/vault.cjs +1 -1
- package/dist/vault.cjs.map +1 -1
- package/dist/vault.iife.js +1 -1
- package/dist/vault.iife.js.map +1 -1
- package/dist/vault.js +1 -1
- package/dist/vault.js.map +1 -1
- package/package.json +44 -39
- package/dist/migration.cjs +0 -2
- package/dist/migration.cjs.map +0 -1
- package/dist/migration.d.ts +0 -37
- package/dist/migration.d.ts.map +0 -1
- package/dist/migration.js +0 -25
- package/dist/migration.js.map +0 -1
package/dist/prune.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
require("./_dev.cjs");const e=require("./ttl.cjs");function t(t,n){e.assertPositiveFinite(n.interval,`scheduleExpiredPrune: interval`);let r=setInterval(()=>{t.pruneExpired().catch(e=>{n.onError&&n.onError(e)})},n.interval),i=()=>clearInterval(r);return n.signal?.addEventListener(`abort`,i,{once:!0}),i}exports.scheduleExpiredPrune=t;
|
|
2
2
|
//# sourceMappingURL=prune.cjs.map
|
package/dist/prune.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prune.cjs","names":[],"sources":["../src/prune.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"prune.cjs","names":[],"sources":["../src/prune.ts"],"sourcesContent":["import { error as logError } from './_dev';\nimport { assertPositiveFinite } from './ttl';\nimport type { AnySchema, VaultStore } from './types';\n\n/**\n * Schedules periodic `pruneExpired()` calls. Returns a `stop` function.\n *\n * Pass `signal: store.disposalSignal` to auto-cancel when the store is torn down.\n * Pass `onError` to handle non-disposal failures explicitly; without it, errors\n * are logged via the dev channel and the schedule continues.\n *\n * ```ts\n * const stop = scheduleExpiredPrune(db, {\n * interval: ttl.hours(1),\n * signal: db.disposalSignal,\n * });\n * ```\n */\nexport function scheduleExpiredPrune<S extends AnySchema>(\n adapter: Pick<VaultStore<S>, 'pruneExpired'>,\n options: {\n interval: number;\n onError?: (err: unknown) => void;\n signal?: AbortSignal;\n },\n): () => void {\n assertPositiveFinite(options.interval, 'scheduleExpiredPrune: interval');\n\n const id = setInterval(() => {\n void adapter.pruneExpired().catch((err) => {\n if (options.onError) options.onError(err);\n else logError('scheduleExpiredPrune: pruneExpired() threw — pass onError to handle this.', err);\n });\n }, options.interval);\n\n const stop = (): void => clearInterval(id);\n\n options.signal?.addEventListener('abort', stop, { once: true });\n\n return stop;\n}\n"],"mappings":"mDAkBA,SAAgB,EACd,EACA,EAKY,CACZ,EAAA,qBAAqB,EAAQ,SAAU,gCAAgC,EAEvE,IAAM,EAAK,gBAAkB,CAC3B,EAAa,aAAa,CAAC,CAAC,MAAO,GAAQ,CACrC,EAAQ,SAAS,EAAQ,QAAQ,CAAG,CAE1C,CAAC,CACH,EAAG,EAAQ,QAAQ,EAEb,MAAmB,cAAc,CAAE,EAIzC,OAFA,EAAQ,QAAQ,iBAAiB,QAAS,EAAM,CAAE,KAAM,EAAK,CAAC,EAEvD,CACT"}
|
package/dist/prune.d.ts
CHANGED
|
@@ -1,32 +1,21 @@
|
|
|
1
1
|
import type { AnySchema, VaultStore } from './types';
|
|
2
2
|
/**
|
|
3
|
-
* Schedules periodic
|
|
4
|
-
* Returns a `stop` function to cancel the schedule.
|
|
3
|
+
* Schedules periodic `pruneExpired()` calls. Returns a `stop` function.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Pass `signal: store.disposalSignal` to auto-cancel when the store is torn down.
|
|
6
|
+
* Pass `onError` to handle non-disposal failures explicitly; without it, errors
|
|
7
|
+
* are logged via the dev channel and the schedule continues.
|
|
8
8
|
*
|
|
9
9
|
* ```ts
|
|
10
|
-
* const
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* const stop = scheduleExpiredPrune(db, {
|
|
11
|
+
* interval: ttl.hours(1),
|
|
12
|
+
* signal: db.disposalSignal,
|
|
13
|
+
* });
|
|
13
14
|
* ```
|
|
14
15
|
*/
|
|
15
16
|
export declare function scheduleExpiredPrune<S extends AnySchema>(adapter: Pick<VaultStore<S>, 'pruneExpired'>, options: {
|
|
16
17
|
interval: number;
|
|
17
|
-
/**
|
|
18
|
-
* Called when `pruneExpired()` throws an error that is NOT a `VaultDisposedError`.
|
|
19
|
-
* `VaultDisposedError` always stops the schedule automatically.
|
|
20
|
-
* Without this callback, non-disposal errors are silently swallowed.
|
|
21
|
-
*/
|
|
22
18
|
onError?: (err: unknown) => void;
|
|
23
|
-
/**
|
|
24
|
-
* When aborted, stops the schedule. Useful for tying the schedule lifetime
|
|
25
|
-
* to an adapter's `disposalSignal`:
|
|
26
|
-
* ```ts
|
|
27
|
-
* scheduleExpiredPrune(db, { interval: ttl.hours(1), signal: db.disposalSignal });
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
19
|
signal?: AbortSignal;
|
|
31
20
|
}): () => void;
|
|
32
21
|
//# sourceMappingURL=prune.d.ts.map
|
package/dist/prune.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prune.d.ts","sourceRoot":"","sources":["../src/prune.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prune.d.ts","sourceRoot":"","sources":["../src/prune.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,SAAS,EACtD,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,EAC5C,OAAO,EAAE;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GACA,MAAM,IAAI,CAeZ"}
|
package/dist/prune.js
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
import { VaultDisposedError as e, VaultError as t } from "./errors.js";
|
|
2
1
|
import "./_dev.js";
|
|
2
|
+
import { assertPositiveFinite as e } from "./ttl.js";
|
|
3
3
|
//#region src/prune.ts
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
let
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
r.signal?.addEventListener("abort", a, { once: !0 });
|
|
10
|
-
let o = setInterval(() => {
|
|
11
|
-
i && n.pruneExpired().catch((t) => {
|
|
12
|
-
t instanceof e ? (i = !1, clearInterval(o)) : r.onError ? r.onError(t) : `${String(t)}`;
|
|
4
|
+
function t(t, n) {
|
|
5
|
+
e(n.interval, "scheduleExpiredPrune: interval");
|
|
6
|
+
let r = setInterval(() => {
|
|
7
|
+
t.pruneExpired().catch((e) => {
|
|
8
|
+
n.onError && n.onError(e);
|
|
13
9
|
});
|
|
14
|
-
},
|
|
15
|
-
return
|
|
10
|
+
}, n.interval), i = () => clearInterval(r);
|
|
11
|
+
return n.signal?.addEventListener("abort", i, { once: !0 }), i;
|
|
16
12
|
}
|
|
17
13
|
//#endregion
|
|
18
|
-
export {
|
|
14
|
+
export { t as scheduleExpiredPrune };
|
|
19
15
|
|
|
20
16
|
//# sourceMappingURL=prune.js.map
|
package/dist/prune.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prune.js","names":[],"sources":["../src/prune.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"prune.js","names":[],"sources":["../src/prune.ts"],"sourcesContent":["import { error as logError } from './_dev';\nimport { assertPositiveFinite } from './ttl';\nimport type { AnySchema, VaultStore } from './types';\n\n/**\n * Schedules periodic `pruneExpired()` calls. Returns a `stop` function.\n *\n * Pass `signal: store.disposalSignal` to auto-cancel when the store is torn down.\n * Pass `onError` to handle non-disposal failures explicitly; without it, errors\n * are logged via the dev channel and the schedule continues.\n *\n * ```ts\n * const stop = scheduleExpiredPrune(db, {\n * interval: ttl.hours(1),\n * signal: db.disposalSignal,\n * });\n * ```\n */\nexport function scheduleExpiredPrune<S extends AnySchema>(\n adapter: Pick<VaultStore<S>, 'pruneExpired'>,\n options: {\n interval: number;\n onError?: (err: unknown) => void;\n signal?: AbortSignal;\n },\n): () => void {\n assertPositiveFinite(options.interval, 'scheduleExpiredPrune: interval');\n\n const id = setInterval(() => {\n void adapter.pruneExpired().catch((err) => {\n if (options.onError) options.onError(err);\n else logError('scheduleExpiredPrune: pruneExpired() threw — pass onError to handle this.', err);\n });\n }, options.interval);\n\n const stop = (): void => clearInterval(id);\n\n options.signal?.addEventListener('abort', stop, { once: true });\n\n return stop;\n}\n"],"mappings":";;;AAkBA,SAAgB,EACd,GACA,GAKY;CACZ,EAAqB,EAAQ,UAAU,gCAAgC;CAEvE,IAAM,IAAK,kBAAkB;EAC3B,EAAa,aAAa,CAAC,CAAC,OAAO,MAAQ;GACzC,AAAI,EAAQ,WAAS,EAAQ,QAAQ,CAAG;EAE1C,CAAC;CACH,GAAG,EAAQ,QAAQ,GAEb,UAAmB,cAAc,CAAE;CAIzC,OAFA,EAAQ,QAAQ,iBAAiB,SAAS,GAAM,EAAE,MAAM,GAAK,CAAC,GAEvD;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(e,t){return{deleteMany:e.deleteMany,source:t}}function i(a,o=[]){let s=e=>i(a,[...o,e]);return{between(e,t,n){let c=String(e);if(o.length===0){if(a.getRange&&a.keyField===c)return i(r(a,()=>a.getRange({lower:t,type:`between`,upper:n})),o);if(a.getIndexRange&&a.indexedFields?.has(c))return i(r(a,()=>a.getIndexRange(c,{lower:t,type:`between`,upper:n})),o)}return s({apply:r=>r.filter(r=>{let i=r[e];return i>=t&&i<=n})})},count(){return t(a,o).then(e=>e.length)},async delete(){if(!a.deleteMany)throw new e.VaultError(`query.delete is not available for this adapter context`);let n=await t(a,o);return a.deleteMany(n)},equals(e,t){let n=String(e);if(o.length===0){if(a.getRange&&a.keyField===n)return i(r(a,()=>a.getRange({type:`eq`,value:t})));if(a.getIndexRange&&a.indexedFields?.has(n))return i(r(a,()=>a.getIndexRange(n,{type:`eq`,value:t})))}return i(a,[...o,{apply:n=>n.filter(n=>n[e]===t)}])},async exists(){return o.length===0?a.source().then(e=>e.length>0):t(a,o).then(e=>e.length>0)},filter(e){return s({apply:t=>t.filter(e)})},first(){return o.length===0?a.source().then(e=>e[0]):t(a,o).then(e=>e[0])},limit(e){let t=n(e,`query.limit`);return s({apply:e=>e.slice(0,t),isNonFilter:!0})},offset(e){let t=n(e,`query.offset`);return s({apply:e=>e.slice(t),isNonFilter:!0})},orderBy(e,t=`asc`){return s({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})},startsWith(e,t,{ignoreCase:n=!1}={}){let c=String(e);if(!n&&t.length>0&&o.length===0){if(a.getRange&&a.keyField===c)return i(r(a,()=>a.getRange({prefix:t,type:`starts`})),o);if(a.getIndexRange&&a.indexedFields?.has(c))return i(r(a,()=>a.getIndexRange(c,{prefix:t,type:`starts`})),o)}let l=n?t.toLowerCase():t;return s({apply:t=>t.filter(t=>{let r=t[e];return typeof r==`string`&&(n?r.toLowerCase():r).startsWith(l)})})},toArray(){return t(a,o)}
|
|
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(e,t){return{deleteMany:e.deleteMany,source:t}}function i(a,o=[]){let s=e=>i(a,[...o,e]);return{between(e,t,n){let c=String(e);if(o.length===0){if(a.getRange&&a.keyField===c)return i(r(a,()=>a.getRange({lower:t,type:`between`,upper:n})),o);if(a.getIndexRange&&a.indexedFields?.has(c))return i(r(a,()=>a.getIndexRange(c,{lower:t,type:`between`,upper:n})),o)}return s({apply:r=>r.filter(r=>{let i=r[e];return i>=t&&i<=n})})},count(){return t(a,o.filter(e=>!e.isNonFilter)).then(e=>e.length)},async delete(){if(!a.deleteMany)throw new e.VaultError(`query.delete is not available for this adapter context`);let n=await t(a,o);return a.deleteMany(n)},equals(e,t){let n=String(e);if(o.length===0){if(a.getRange&&a.keyField===n)return i(r(a,()=>a.getRange({type:`eq`,value:t})));if(a.getIndexRange&&a.indexedFields?.has(n))return i(r(a,()=>a.getIndexRange(n,{type:`eq`,value:t})))}return i(a,[...o,{apply:n=>n.filter(n=>n[e]===t)}])},async exists(){return o.length===0?a.source().then(e=>e.length>0):t(a,o).then(e=>e.length>0)},filter(e){return s({apply:t=>t.filter(e)})},first(){return o.length===0?a.source().then(e=>e[0]):t(a,o).then(e=>e[0])},limit(e){let t=n(e,`query.limit`);return s({apply:e=>e.slice(0,t),isNonFilter:!0})},offset(e){let t=n(e,`query.offset`);return s({apply:e=>e.slice(t),isNonFilter:!0})},orderBy(e,t=`asc`){return s({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})},startsWith(e,t,{ignoreCase:n=!1}={}){let c=String(e);if(!n&&t.length>0&&o.length===0){if(a.getRange&&a.keyField===c)return i(r(a,()=>a.getRange({prefix:t,type:`starts`})),o);if(a.getIndexRange&&a.indexedFields?.has(c))return i(r(a,()=>a.getIndexRange(c,{prefix:t,type:`starts`})),o)}let l=n?t.toLowerCase():t;return s({apply:t=>t.filter(t=>{let r=t[e];return typeof r==`string`&&(n?r.toLowerCase():r).startsWith(l)})})},toArray(){return t(a,o)}}}exports.createQueryBuilder=i;
|
|
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 `totalCount()` — 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 all applied operations, including `limit` and `offset`.\n * To get the full filtered set size regardless of pagination (e.g. for \"page X of N\" UIs),\n * use `totalCount()` instead.\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 * 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 totalCount(): Promise<number>;\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 return applyOps(ctx, ops).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 totalCount(): Promise<number> {\n const filterOps = ops.filter((op) => !op.isNonFilter);\n\n return applyOps(ctx, filterOps).then((r) => r.length);\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 };\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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"}
|
package/dist/query.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
type Predicate<T> = (value: T, index: number, array: T[]) => boolean;
|
|
2
2
|
/**
|
|
3
|
-
* `isNonFilter`: when true this op is excluded from `
|
|
3
|
+
* `isNonFilter`: when true this op is excluded from `count()` — it does not restrict
|
|
4
4
|
* *which* records match (limit, offset, orderBy), only how results are presented.
|
|
5
5
|
*/
|
|
6
6
|
type QueryOp<T> = {
|
|
@@ -57,9 +57,10 @@ type ChainedQuery<T extends object, N extends T, Self extends ChainedQuery<T, N,
|
|
|
57
57
|
*/
|
|
58
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>;
|
|
59
59
|
/**
|
|
60
|
-
* Returns the number of records matching
|
|
61
|
-
*
|
|
62
|
-
*
|
|
60
|
+
* Returns the number of records matching the applied filter predicates.
|
|
61
|
+
* Presentation-only ops (`limit`, `offset`, `orderBy`) are intentionally ignored — this
|
|
62
|
+
* always counts the full filtered set, making paginated total-count queries possible
|
|
63
|
+
* without a second query.
|
|
63
64
|
*/
|
|
64
65
|
count(): Promise<number>;
|
|
65
66
|
/**
|
|
@@ -96,13 +97,6 @@ type ChainedQuery<T extends object, N extends T, Self extends ChainedQuery<T, N,
|
|
|
96
97
|
ignoreCase?: boolean;
|
|
97
98
|
}): QueryBuilder<T, N>;
|
|
98
99
|
toArray(): Promise<N[]>;
|
|
99
|
-
/**
|
|
100
|
-
* Returns the number of records matching the applied filter predicates.
|
|
101
|
-
* Presentation-only ops (`limit`, `offset`, `orderBy`) are intentionally ignored — this
|
|
102
|
-
* always counts the full filtered set, making paginated total-count queries possible
|
|
103
|
-
* without a second query.
|
|
104
|
-
*/
|
|
105
|
-
totalCount(): Promise<number>;
|
|
106
100
|
};
|
|
107
101
|
/** Extends the shared query API with `delete()`. Available on stores and IndexedDB transaction callbacks. */
|
|
108
102
|
export interface QueryBuilder<T extends object, N extends T = T> extends ChainedQuery<T, N, QueryBuilder<T, N>> {
|
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;AACvE,KAAK,mBAAmB,CAAC,CAAC,SAAS,MAAM,IAAI;KAC1C,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,SAAS,KAAK,GAAG,KAAK,GAAG,CAAC;CACxF,CAAC,MAAM,CAAC,CAAC,CAAC;AAEX;;;;GAIG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC9B;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACnD;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEvC,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;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IACpE;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAChD,oGAAoG;IACpG,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACpC,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;CAC5B,CAAC;AAIF;;;GAGG;AACH,KAAK,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,IAAI,SAAS,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,IAAI;IACxF;;;OAGG;IACH,OAAO,CAAC,CAAC,SAAS,mBAAmB,CAAC,CAAC,CAAC,EACtC,KAAK,EAAE,CAAC,EACR,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,EAClD,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,GACjD,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACtB
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}
|
package/dist/query.js
CHANGED
|
@@ -38,7 +38,7 @@ function i(a, o = []) {
|
|
|
38
38
|
}) });
|
|
39
39
|
},
|
|
40
40
|
count() {
|
|
41
|
-
return t(a, o).then((e) => e.length);
|
|
41
|
+
return t(a, o.filter((e) => !e.isNonFilter)).then((e) => e.length);
|
|
42
42
|
},
|
|
43
43
|
async delete() {
|
|
44
44
|
if (!a.deleteMany) throw new e("query.delete is not available for this adapter context");
|
|
@@ -114,9 +114,6 @@ function i(a, o = []) {
|
|
|
114
114
|
},
|
|
115
115
|
toArray() {
|
|
116
116
|
return t(a, o);
|
|
117
|
-
},
|
|
118
|
-
totalCount() {
|
|
119
|
-
return t(a, o.filter((e) => !e.isNonFilter)).then((e) => e.length);
|
|
120
117
|
}
|
|
121
118
|
};
|
|
122
119
|
}
|
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 `totalCount()` — 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 all applied operations, including `limit` and `offset`.\n * To get the full filtered set size regardless of pagination (e.g. for \"page X of N\" UIs),\n * use `totalCount()` instead.\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 * 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 totalCount(): Promise<number>;\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 return applyOps(ctx, ops).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 totalCount(): Promise<number> {\n const filterOps = ops.filter((op) => !op.isNonFilter);\n\n return applyOps(ctx, filterOps).then((r) => r.length);\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 };\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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"}
|
package/dist/sqlite.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
+
export type { SQLiteDatabase, SQLiteParameter, SQLiteStatement, SQLiteVaultOptions, SQLiteVaultStore, TransactionContext, } from './adapters/sqlite';
|
|
1
2
|
export { createSQLite } from './adapters/sqlite';
|
|
2
|
-
export type { SQLiteDatabase, SQLiteParameter, SQLiteStatement, SQLiteVaultOptions, SQLiteVaultStore, } 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,
|
|
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,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/ttl.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./errors.cjs");var t={days:e=>r(
|
|
1
|
+
const e=require("./errors.cjs");var t={days:e=>r(e*864e5,`ttl.days`),hours:e=>r(e*36e5,`ttl.hours`),minutes:e=>r(e*6e4,`ttl.minutes`),ms:e=>r(e,`ttl.ms`),seconds:e=>r(e*1e3,`ttl.seconds`)};function n(e){return e!==void 0&&Date.now()>=e}function r(t,n){if(!Number.isFinite(t)||t<=0)throw new e.VaultError(`${n} expected a finite positive number, received ${String(t)}`);return t}function i(e){if(typeof e!=`object`||!e||!(`value`in e))return;let t=e;if(!(t.expiresAt!==void 0&&(typeof t.expiresAt!=`number`||!Number.isFinite(t.expiresAt))))return t}exports.assertPositiveFinite=r,exports.isExpired=n,exports.parseStored=i,exports.ttl=t;
|
|
2
2
|
//# sourceMappingURL=ttl.cjs.map
|
package/dist/ttl.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ttl.cjs","names":[],"sources":["../src/ttl.ts"],"sourcesContent":["import { VaultError } from './errors';\n\
|
|
1
|
+
{"version":3,"file":"ttl.cjs","names":[],"sources":["../src/ttl.ts"],"sourcesContent":["import { VaultError } from './errors';\n\n/** Duration helpers that produce finite, positive millisecond values. */\nexport const ttl = {\n days: (n: number) => assertPositiveFinite(n * 86_400_000, 'ttl.days'),\n hours: (n: number) => assertPositiveFinite(n * 3_600_000, 'ttl.hours'),\n minutes: (n: number) => assertPositiveFinite(n * 60_000, 'ttl.minutes'),\n ms: (n: number) => assertPositiveFinite(n, 'ttl.ms'),\n seconds: (n: number) => assertPositiveFinite(n * 1000, 'ttl.seconds'),\n} as const;\n\n/** Fixed storage envelope shared by every adapter, so data and indexes stay portable. */\nexport type StoredRecord<T> = { expiresAt?: number; value: T };\n\nexport function isExpired(expiresAt: number | undefined): boolean {\n return expiresAt !== undefined && Date.now() >= expiresAt;\n}\n\n/** Throws when `ttlMs` is not a finite, positive number. Returns it unchanged otherwise. */\nexport function assertPositiveFinite(ttlMs: number, source: string): number {\n if (!Number.isFinite(ttlMs) || ttlMs <= 0) {\n throw new VaultError(`${source} expected a finite positive number, received ${String(ttlMs)}`);\n }\n\n return ttlMs;\n}\n\nexport function parseStored<T>(raw: unknown): StoredRecord<T> | undefined {\n if (typeof raw !== 'object' || raw === null || !('value' in raw)) return undefined;\n\n const record = raw as { expiresAt?: unknown; value: unknown };\n\n if (record.expiresAt !== undefined && (typeof record.expiresAt !== 'number' || !Number.isFinite(record.expiresAt))) {\n return undefined;\n }\n\n return record as StoredRecord<T>;\n}\n"],"mappings":"gCAGA,IAAa,EAAM,CACjB,KAAO,GAAc,EAAqB,EAAI,MAAY,UAAU,EACpE,MAAQ,GAAc,EAAqB,EAAI,KAAW,WAAW,EACrE,QAAU,GAAc,EAAqB,EAAI,IAAQ,aAAa,EACtE,GAAK,GAAc,EAAqB,EAAG,QAAQ,EACnD,QAAU,GAAc,EAAqB,EAAI,IAAM,aAAa,CACtE,EAKA,SAAgB,EAAU,EAAwC,CAChE,OAAO,IAAc,IAAA,IAAa,KAAK,IAAI,GAAK,CAClD,CAGA,SAAgB,EAAqB,EAAe,EAAwB,CAC1E,GAAI,CAAC,OAAO,SAAS,CAAK,GAAK,GAAS,EACtC,MAAM,IAAI,EAAA,WAAW,GAAG,EAAO,+CAA+C,OAAO,CAAK,GAAG,EAG/F,OAAO,CACT,CAEA,SAAgB,EAAe,EAA2C,CACxE,GAAI,OAAO,GAAQ,WAAY,GAAgB,EAAE,UAAW,GAAM,OAElE,IAAM,EAAS,EAEX,OAAO,YAAc,IAAA,KAAc,OAAO,EAAO,WAAc,UAAY,CAAC,OAAO,SAAS,EAAO,SAAS,IAIhH,OAAO,CACT"}
|
package/dist/ttl.d.ts
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
/** A duration in milliseconds, produced by the `ttl.*` helpers. */
|
|
3
|
-
export type TtlMs = number & {
|
|
4
|
-
readonly [ttlMsBrand]: never;
|
|
5
|
-
};
|
|
1
|
+
/** Duration helpers that produce finite, positive millisecond values. */
|
|
6
2
|
export declare const ttl: {
|
|
7
|
-
readonly days: (n: number) =>
|
|
8
|
-
readonly hours: (n: number) =>
|
|
9
|
-
readonly minutes: (n: number) =>
|
|
10
|
-
readonly ms: (n: number) =>
|
|
11
|
-
readonly seconds: (n: number) =>
|
|
3
|
+
readonly days: (n: number) => number;
|
|
4
|
+
readonly hours: (n: number) => number;
|
|
5
|
+
readonly minutes: (n: number) => number;
|
|
6
|
+
readonly ms: (n: number) => number;
|
|
7
|
+
readonly seconds: (n: number) => number;
|
|
12
8
|
};
|
|
13
9
|
/** Fixed storage envelope shared by every adapter, so data and indexes stay portable. */
|
|
14
10
|
export type StoredRecord<T> = {
|
|
@@ -16,14 +12,7 @@ export type StoredRecord<T> = {
|
|
|
16
12
|
value: T;
|
|
17
13
|
};
|
|
18
14
|
export declare function isExpired(expiresAt: number | undefined): boolean;
|
|
19
|
-
|
|
20
|
-
export declare function
|
|
21
|
-
export declare function unwrapStored<T>(raw: StoredRecord<T>): T | undefined;
|
|
15
|
+
/** Throws when `ttlMs` is not a finite, positive number. Returns it unchanged otherwise. */
|
|
16
|
+
export declare function assertPositiveFinite(ttlMs: number, source: string): number;
|
|
22
17
|
export declare function parseStored<T>(raw: unknown): StoredRecord<T> | undefined;
|
|
23
|
-
export declare function readWithTtl<T>(raw: unknown): {
|
|
24
|
-
expired: boolean;
|
|
25
|
-
found: boolean;
|
|
26
|
-
value: T | undefined;
|
|
27
|
-
};
|
|
28
|
-
export {};
|
|
29
18
|
//# sourceMappingURL=ttl.d.ts.map
|
package/dist/ttl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ttl.d.ts","sourceRoot":"","sources":["../src/ttl.ts"],"names":[],"mappings":"AAEA,
|
|
1
|
+
{"version":3,"file":"ttl.d.ts","sourceRoot":"","sources":["../src/ttl.ts"],"names":[],"mappings":"AAEA,yEAAyE;AACzE,eAAO,MAAM,GAAG;uBACJ,MAAM;wBACL,MAAM;0BACJ,MAAM;qBACX,MAAM;0BACD,MAAM;CACX,CAAC;AAEX,yFAAyF;AACzF,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AAE/D,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEhE;AAED,4FAA4F;AAC5F,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAM1E;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAUxE"}
|
package/dist/ttl.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { VaultError as e } from "./errors.js";
|
|
2
2
|
//#region src/ttl.ts
|
|
3
3
|
var t = {
|
|
4
|
-
days: (e) => r(
|
|
5
|
-
hours: (e) => r(
|
|
6
|
-
minutes: (e) => r(
|
|
4
|
+
days: (e) => r(e * 864e5, "ttl.days"),
|
|
5
|
+
hours: (e) => r(e * 36e5, "ttl.hours"),
|
|
6
|
+
minutes: (e) => r(e * 6e4, "ttl.minutes"),
|
|
7
7
|
ms: (e) => r(e, "ttl.ms"),
|
|
8
|
-
seconds: (e) => r(
|
|
8
|
+
seconds: (e) => r(e * 1e3, "ttl.seconds")
|
|
9
9
|
};
|
|
10
10
|
function n(e) {
|
|
11
11
|
return e !== void 0 && Date.now() >= e;
|
|
@@ -20,6 +20,6 @@ function i(e) {
|
|
|
20
20
|
if (!(t.expiresAt !== void 0 && (typeof t.expiresAt != "number" || !Number.isFinite(t.expiresAt)))) return t;
|
|
21
21
|
}
|
|
22
22
|
//#endregion
|
|
23
|
-
export { r as
|
|
23
|
+
export { r as assertPositiveFinite, n as isExpired, i as parseStored, t as ttl };
|
|
24
24
|
|
|
25
25
|
//# sourceMappingURL=ttl.js.map
|
package/dist/ttl.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ttl.js","names":[],"sources":["../src/ttl.ts"],"sourcesContent":["import { VaultError } from './errors';\n\
|
|
1
|
+
{"version":3,"file":"ttl.js","names":[],"sources":["../src/ttl.ts"],"sourcesContent":["import { VaultError } from './errors';\n\n/** Duration helpers that produce finite, positive millisecond values. */\nexport const ttl = {\n days: (n: number) => assertPositiveFinite(n * 86_400_000, 'ttl.days'),\n hours: (n: number) => assertPositiveFinite(n * 3_600_000, 'ttl.hours'),\n minutes: (n: number) => assertPositiveFinite(n * 60_000, 'ttl.minutes'),\n ms: (n: number) => assertPositiveFinite(n, 'ttl.ms'),\n seconds: (n: number) => assertPositiveFinite(n * 1000, 'ttl.seconds'),\n} as const;\n\n/** Fixed storage envelope shared by every adapter, so data and indexes stay portable. */\nexport type StoredRecord<T> = { expiresAt?: number; value: T };\n\nexport function isExpired(expiresAt: number | undefined): boolean {\n return expiresAt !== undefined && Date.now() >= expiresAt;\n}\n\n/** Throws when `ttlMs` is not a finite, positive number. Returns it unchanged otherwise. */\nexport function assertPositiveFinite(ttlMs: number, source: string): number {\n if (!Number.isFinite(ttlMs) || ttlMs <= 0) {\n throw new VaultError(`${source} expected a finite positive number, received ${String(ttlMs)}`);\n }\n\n return ttlMs;\n}\n\nexport function parseStored<T>(raw: unknown): StoredRecord<T> | undefined {\n if (typeof raw !== 'object' || raw === null || !('value' in raw)) return undefined;\n\n const record = raw as { expiresAt?: unknown; value: unknown };\n\n if (record.expiresAt !== undefined && (typeof record.expiresAt !== 'number' || !Number.isFinite(record.expiresAt))) {\n return undefined;\n }\n\n return record as StoredRecord<T>;\n}\n"],"mappings":";;AAGA,IAAa,IAAM;CACjB,OAAO,MAAc,EAAqB,IAAI,OAAY,UAAU;CACpE,QAAQ,MAAc,EAAqB,IAAI,MAAW,WAAW;CACrE,UAAU,MAAc,EAAqB,IAAI,KAAQ,aAAa;CACtE,KAAK,MAAc,EAAqB,GAAG,QAAQ;CACnD,UAAU,MAAc,EAAqB,IAAI,KAAM,aAAa;AACtE;AAKA,SAAgB,EAAU,GAAwC;CAChE,OAAO,MAAc,KAAA,KAAa,KAAK,IAAI,KAAK;AAClD;AAGA,SAAgB,EAAqB,GAAe,GAAwB;CAC1E,IAAI,CAAC,OAAO,SAAS,CAAK,KAAK,KAAS,GACtC,MAAM,IAAI,EAAW,GAAG,EAAO,+CAA+C,OAAO,CAAK,GAAG;CAG/F,OAAO;AACT;AAEA,SAAgB,EAAe,GAA2C;CACxE,IAAI,OAAO,KAAQ,aAAY,KAAgB,EAAE,WAAW,IAAM;CAElE,IAAM,IAAS;CAEX,QAAO,cAAc,KAAA,MAAc,OAAO,EAAO,aAAc,YAAY,CAAC,OAAO,SAAS,EAAO,SAAS,KAIhH,OAAO;AACT"}
|
package/dist/types.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./errors.cjs"),t=require("./ttl.cjs");function n(n
|
|
1
|
+
const e=require("./errors.cjs"),t=require("./ttl.cjs");function n(n,r={}){let{defaultTtl:i,indexes:a}=r;if(i!==void 0&&t.assertPositiveFinite(i,`table: defaultTtl`),a){let t=new Set;for(let n of a){if(t.has(n))throw new e.VaultError(`table: index "${n}" is already registered`);t.add(n)}}return{defaultTtl:i,indexes:a,key:n}}exports.table=n;
|
|
2
2
|
//# sourceMappingURL=types.cjs.map
|
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
|
|
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> = T[Key] extends VaultKey\n ? {\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 : never;\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 VaultLogger = {\n error(message: string, context?: Error | Record<string, unknown>): void;\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 logger?: VaultLogger;\n onMetrics?: (event: MetricsEvent) => void;\n schema: S;\n validators?: TableValidators<S>;\n};\n\nexport type MetricsEvent = {\n duration: number;\n operation:\n | 'batch'\n | 'clear'\n | 'count'\n | 'delete'\n | 'deleteMany'\n | 'entries'\n | 'get'\n | 'getAll'\n | 'getMany'\n | 'getOrDefault'\n | 'has'\n | 'isEmpty'\n | 'keys'\n | 'put'\n | 'putAll'\n | 'query'\n | 'queryDelete'\n | 'update'\n | 'upsert';\n table: string;\n};\n\nexport type DebugStats = { expiredCount: number; recordCount: number };\nexport type DebugInfo<S extends AnySchema> = { tables: Array<{ name: keyof S & string } & DebugStats> };\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 entries<T extends K>(table: T): Promise<Array<[KeyOf<S, T>, RecordOf<S, T>]>>;\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 getOrDefault<T extends K>(\n table: T,\n key: KeyOf<S, T>,\n defaultFn: () => RecordOf<S, T>,\n ttl?: number,\n ): Promise<RecordOf<S, T>>;\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 debug(): Promise<DebugInfo<S>>;\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 entries<K extends keyof S & string>(table: K): Promise<Array<[KeyOf<S, K>, RecordOf<S, K>]>>;\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 getOrDefault<K extends keyof S & string>(\n table: K,\n key: KeyOf<S, K>,\n defaultFn: () => RecordOf<S, K>,\n ttl?: number,\n ): Promise<RecordOf<S, K>>;\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 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}\n\n/** Lazy record iteration supplied by storage engines that support it. */\nexport interface IterableVaultStore<S extends AnySchema> extends VaultStore<S> {\n iterate<K extends keyof S & string>(table: K): AsyncIterable<RecordOf<S, K>>;\n}\n\n/** IndexedDB-only guarantees: cursor iteration and atomic, scoped transactions. */\nexport interface IndexedDbVaultStore<S extends AnySchema> extends TransactionalVaultStore<S>, IterableVaultStore<S> {}\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 & (T[Key] extends VaultKey ? unknown : never),\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 } as unknown as SchemaEntry<T, Key>;\n}\n"],"mappings":"uDAmLA,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"}
|