@vielzeug/vault 1.0.2
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 +74 -0
- package/dist/_dev.cjs +2 -0
- package/dist/_dev.cjs.map +1 -0
- package/dist/_dev.d.ts +2 -0
- package/dist/_dev.d.ts.map +1 -0
- package/dist/_dev.js +12 -0
- package/dist/_dev.js.map +1 -0
- package/dist/adapter-core.cjs +2 -0
- package/dist/adapter-core.cjs.map +1 -0
- package/dist/adapter-core.d.ts +8 -0
- package/dist/adapter-core.d.ts.map +1 -0
- package/dist/adapter-core.js +269 -0
- package/dist/adapter-core.js.map +1 -0
- package/dist/adapters/indexeddb.cjs +2 -0
- package/dist/adapters/indexeddb.cjs.map +1 -0
- package/dist/adapters/indexeddb.d.ts +13 -0
- package/dist/adapters/indexeddb.d.ts.map +1 -0
- package/dist/adapters/indexeddb.js +336 -0
- package/dist/adapters/indexeddb.js.map +1 -0
- package/dist/adapters/memory.cjs +2 -0
- package/dist/adapters/memory.cjs.map +1 -0
- package/dist/adapters/memory.d.ts +14 -0
- package/dist/adapters/memory.d.ts.map +1 -0
- package/dist/adapters/memory.js +200 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/webstorage.cjs +2 -0
- package/dist/adapters/webstorage.cjs.map +1 -0
- package/dist/adapters/webstorage.d.ts +14 -0
- package/dist/adapters/webstorage.d.ts.map +1 -0
- package/dist/adapters/webstorage.js +182 -0
- package/dist/adapters/webstorage.js.map +1 -0
- package/dist/errors.cjs +2 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +17 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/internal.cjs +2 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.ts +50 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +125 -0
- package/dist/internal.js.map +1 -0
- package/dist/migration.cjs +2 -0
- package/dist/migration.cjs.map +1 -0
- package/dist/migration.d.ts +37 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +27 -0
- package/dist/migration.js.map +1 -0
- package/dist/prune.cjs +2 -0
- package/dist/prune.cjs.map +1 -0
- package/dist/prune.d.ts +32 -0
- package/dist/prune.d.ts.map +1 -0
- package/dist/prune.js +20 -0
- package/dist/prune.js.map +1 -0
- package/dist/query.cjs +2 -0
- package/dist/query.cjs.map +1 -0
- package/dist/query.d.ts +113 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +126 -0
- package/dist/query.js.map +1 -0
- package/dist/streaming.cjs +2 -0
- package/dist/streaming.cjs.map +1 -0
- package/dist/streaming.d.ts +22 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +21 -0
- package/dist/streaming.js.map +1 -0
- package/dist/ttl.cjs +2 -0
- package/dist/ttl.cjs.map +1 -0
- package/dist/ttl.d.ts +74 -0
- package/dist/ttl.d.ts.map +1 -0
- package/dist/ttl.js +32 -0
- package/dist/ttl.js.map +1 -0
- package/dist/types.cjs +2 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.ts +409 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +27 -0
- package/dist/types.js.map +1 -0
- package/dist/vault.cjs +2 -0
- package/dist/vault.cjs.map +1 -0
- package/dist/vault.iife.js +2 -0
- package/dist/vault.iife.js.map +1 -0
- package/dist/vault.js +2 -0
- package/dist/vault.js.map +1 -0
- package/dist/versioned-codec.cjs +2 -0
- package/dist/versioned-codec.cjs.map +1 -0
- package/dist/versioned-codec.d.ts +44 -0
- package/dist/versioned-codec.d.ts.map +1 -0
- package/dist/versioned-codec.js +32 -0
- package/dist/versioned-codec.js.map +1 -0
- package/package.json +41 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import type { QueryBuilder } from './query';
|
|
2
|
+
import { type TtlMs, type VaultCodec } from './ttl';
|
|
3
|
+
export type { TtlMs, VaultCodec };
|
|
4
|
+
/**
|
|
5
|
+
* Schema entry for a single table.
|
|
6
|
+
* `T` is the record type; `Key` is the primary key field name.
|
|
7
|
+
*
|
|
8
|
+
* The phantom brand `[schemaEntryBrand]` holds `T` in a directly inferable position so
|
|
9
|
+
* TypeScript can recover `T` in `RecordOf` conditional types. It uses a unique symbol key
|
|
10
|
+
* so it is invisible in IDE autocompletion and cannot be set accidentally.
|
|
11
|
+
*/
|
|
12
|
+
export type SchemaEntry<T extends Record<string, unknown>, Key extends keyof T & string = keyof T & string> = {
|
|
13
|
+
defaultTtl?: TtlMs;
|
|
14
|
+
/**
|
|
15
|
+
* Secondary index field names. The IndexedDB adapter creates an IDB index for each field,
|
|
16
|
+
* enabling push-down optimisation for `equals`, `between`, and `startsWith` queries on those
|
|
17
|
+
* fields — avoiding a full-table scan.
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* const schema = {
|
|
21
|
+
* users: table<User>('id').index('email').index('city'),
|
|
22
|
+
* };
|
|
23
|
+
* // db.query('users').equals('email', 'alice@example.com') → uses IDB index
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
indexes?: readonly (keyof T & string)[];
|
|
27
|
+
key: Key;
|
|
28
|
+
};
|
|
29
|
+
/** A schema is any record of `SchemaEntry`-compatible values. Checked structurally so that
|
|
30
|
+
* concrete `SchemaEntry<T, Key>` values satisfy it without covariance constraints. */
|
|
31
|
+
export type AnySchema = Record<string, {
|
|
32
|
+
defaultTtl?: TtlMs;
|
|
33
|
+
indexes?: readonly string[];
|
|
34
|
+
key: string;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Fluent builder returned by `table()` — satisfies `SchemaEntry` and adds `.ttl()` and `.index()` chaining.
|
|
38
|
+
* Export this type to annotate schema entry variables without using `ReturnType<typeof table<T, K>>`.
|
|
39
|
+
*/
|
|
40
|
+
export type TableBuilder<T extends Record<string, unknown>, Key extends keyof T & string = keyof T & string> = SchemaEntry<T, Key> & {
|
|
41
|
+
/**
|
|
42
|
+
* Register a secondary index on the given field. Can be chained multiple times.
|
|
43
|
+
* Only used by the IndexedDB adapter; other adapters fall back to in-memory filtering.
|
|
44
|
+
*
|
|
45
|
+
* **Custom codec caveat:** the IndexedDB adapter creates the index with keyPath
|
|
46
|
+
* `value.<field>`, which assumes the default `{ value, expiresAt? }` storage envelope
|
|
47
|
+
* (see `VaultCodec`). A custom codec that changes the top-level shape (e.g.
|
|
48
|
+
* `createVersionedCodec`, or a compact/encrypted format) breaks index push-down silently —
|
|
49
|
+
* queries on the indexed field return empty results instead of throwing. Avoid combining
|
|
50
|
+
* `.index()` with a non-default codec, or design the custom codec to preserve `value.<field>`.
|
|
51
|
+
*/
|
|
52
|
+
index: <F extends keyof T & string>(field: F) => TableBuilder<T, Key>;
|
|
53
|
+
/** Set a default TTL (ms) applied to all `put`/`putAll` calls that don't specify one explicitly. */
|
|
54
|
+
ttl: (ms: TtlMs) => TableBuilder<T, Key>;
|
|
55
|
+
};
|
|
56
|
+
export declare function table<T extends Record<string, unknown>, Key extends keyof T & string = keyof T & string>(key: Key): TableBuilder<T, Key>;
|
|
57
|
+
/** Extracts the record type from a schema table entry. */
|
|
58
|
+
export type RecordOf<S extends AnySchema, K extends keyof S> = S[K] extends SchemaEntry<infer R, string> ? R : never;
|
|
59
|
+
/** Extracts the primary key value type from a schema table entry. */
|
|
60
|
+
export type KeyOf<S extends AnySchema, K extends keyof S> = S[K] extends SchemaEntry<infer R, infer Key> ? R[Key] : never;
|
|
61
|
+
export type MigrationContext = {
|
|
62
|
+
db: IDBDatabase;
|
|
63
|
+
newVersion: number | null;
|
|
64
|
+
oldVersion: number;
|
|
65
|
+
tx: IDBTransaction;
|
|
66
|
+
};
|
|
67
|
+
export type MigrationFn = (ctx: MigrationContext) => void;
|
|
68
|
+
/**
|
|
69
|
+
* Minimal logger interface satisfied structurally by `/rune` Logger.
|
|
70
|
+
* Pass a rune Logger instance directly — no adapter needed:
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* import { createLogger } from '@vielzeug/rune';
|
|
74
|
+
* const db = createMemory({ schema, logger: createLogger('db') });
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* Vault only emits error-level logs, so a single rune-compatible `error`
|
|
78
|
+
* method is enough.
|
|
79
|
+
*/
|
|
80
|
+
export interface VaultLogger {
|
|
81
|
+
error(messageOrContext?: Record<string, unknown> | Error | string, message?: string): void;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Minimal synchronous validator interface satisfied structurally by
|
|
85
|
+
* `/sieve` Schema.
|
|
86
|
+
* Pass a sieve schema directly — no adapter needed:
|
|
87
|
+
*
|
|
88
|
+
* ```ts
|
|
89
|
+
* import { s } from '@vielzeug/spell';
|
|
90
|
+
* const db = createMemory({
|
|
91
|
+
* schema: { users: table<User>('id') },
|
|
92
|
+
* validators: { users: v.object({ id: v.number(), name: v.string() }) },
|
|
93
|
+
* });
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* Vault requires synchronous validation because writes may execute inside a
|
|
97
|
+
* live IndexedDB transaction. Any object with a `parse(value: unknown): T`
|
|
98
|
+
* method works.
|
|
99
|
+
*/
|
|
100
|
+
export interface RecordValidator<T> {
|
|
101
|
+
parse(value: unknown): T;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Per-table record parsers. Keys match your vault schema table names.
|
|
105
|
+
* Validators run before every `put`, `putAll`, and inside `update`/`upsert`.
|
|
106
|
+
*/
|
|
107
|
+
export type TableValidators<S extends AnySchema> = {
|
|
108
|
+
[K in keyof S]?: RecordValidator<RecordOf<S, K>>;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Minimal writable-signal interface satisfied structurally by
|
|
112
|
+
* `/ripple` `Signal<T>` and `Store<T>`.
|
|
113
|
+
* Pass a ripple signal directly — no adapter needed:
|
|
114
|
+
*
|
|
115
|
+
* ```ts
|
|
116
|
+
* import { signal } from '@vielzeug/ripple';
|
|
117
|
+
* const usersSignal = signal<User[]>([]);
|
|
118
|
+
*
|
|
119
|
+
* const db = createMemory({
|
|
120
|
+
* schema: { users: table<User>('id') },
|
|
121
|
+
* signals: { users: usersSignal },
|
|
122
|
+
* });
|
|
123
|
+
*
|
|
124
|
+
* // usersSignal.value is now always in sync with the users table.
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* Any object with an `update(fn: (current: T) => T): void` method satisfies
|
|
128
|
+
* this interface. Vault calls `signal.update(() => snapshot)` on each change.
|
|
129
|
+
*/
|
|
130
|
+
export interface ReactiveSignal<T> {
|
|
131
|
+
update(fn: (current: T) => T): void;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Per-table reactive signals. Keys match your vault schema table names.
|
|
135
|
+
* Each signal is automatically kept in sync with the table via `observe()`.
|
|
136
|
+
* Signals are wired at construction time and cleaned up on `dispose()`.
|
|
137
|
+
*/
|
|
138
|
+
export type TableSignals<S extends AnySchema> = {
|
|
139
|
+
[K in keyof S]?: ReactiveSignal<RecordOf<S, K>[]>;
|
|
140
|
+
};
|
|
141
|
+
export type Observer<T> = (records: T[]) => void;
|
|
142
|
+
/** A function that cancels an active subscription. Returned by `observe()` and `observeMany()`. */
|
|
143
|
+
export type Unsubscribe = () => void;
|
|
144
|
+
/**
|
|
145
|
+
* Common options shared by all adapter factories.
|
|
146
|
+
* Individual adapters extend this with adapter-specific fields.
|
|
147
|
+
*/
|
|
148
|
+
export type BaseAdapterOptions<S extends AnySchema> = {
|
|
149
|
+
/**
|
|
150
|
+
* Pluggable serialization codec. Provide a custom implementation to change how values
|
|
151
|
+
* are encoded at rest (e.g. compact keys, encryption, msgpack).
|
|
152
|
+
* Defaults to the standard `{ value, expiresAt? }` JSON envelope.
|
|
153
|
+
*
|
|
154
|
+
* **IndexedDB + `.index()` caveat:** secondary indexes are created with keyPath
|
|
155
|
+
* `value.<field>`, which assumes the default envelope shape. A custom codec that changes
|
|
156
|
+
* the top-level shape breaks index push-down silently (queries return empty results
|
|
157
|
+
* instead of throwing) — see `TableBuilder.index`.
|
|
158
|
+
*/
|
|
159
|
+
codec?: VaultCodec;
|
|
160
|
+
/** Structured logger. A /rune Logger satisfies VaultLogger directly. */
|
|
161
|
+
logger?: VaultLogger;
|
|
162
|
+
/** Performance monitoring hook. Called after every operation with duration in ms. */
|
|
163
|
+
onMetrics?: (event: MetricsEvent) => void;
|
|
164
|
+
schema: S;
|
|
165
|
+
/**
|
|
166
|
+
* Per-table reactive signals. A /ripple Signal<T[]> satisfies ReactiveSignal directly.
|
|
167
|
+
* Each signal is kept in sync with its table automatically via observe().
|
|
168
|
+
*/
|
|
169
|
+
signals?: TableSignals<S>;
|
|
170
|
+
/** Per-table validators. A /sieve Schema satisfies this directly via `parse()`. */
|
|
171
|
+
validators?: TableValidators<S>;
|
|
172
|
+
};
|
|
173
|
+
export type MetricsEvent = {
|
|
174
|
+
duration: number;
|
|
175
|
+
operation: 'batch' | 'clear' | 'count' | 'delete' | 'deleteMany' | 'entries' | 'get' | 'getAll' | 'getMany' | 'getOrDefault' | 'has' | 'isEmpty' | 'keys' | 'put' | 'putAll' | 'query' | 'queryDelete' | 'update' | 'upsert';
|
|
176
|
+
/**
|
|
177
|
+
* The table name. For `batch` operations this is `'*'` because a batch may
|
|
178
|
+
* span multiple tables and there is no single canonical table name.
|
|
179
|
+
*/
|
|
180
|
+
table: string;
|
|
181
|
+
};
|
|
182
|
+
export type DebugStats = {
|
|
183
|
+
/** Number of TTL-expired records still physically in the store (not yet lazily evicted). */
|
|
184
|
+
expiredCount: number;
|
|
185
|
+
/** Number of live (non-expired) records. */
|
|
186
|
+
recordCount: number;
|
|
187
|
+
};
|
|
188
|
+
export type DebugInfo<S extends AnySchema> = {
|
|
189
|
+
tables: Array<{
|
|
190
|
+
name: keyof S & string;
|
|
191
|
+
} & DebugStats>;
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Available inside `batch()` callbacks. For IndexedDB, operations run in a real atomic IDB transaction.
|
|
195
|
+
* The batch scope restricts accessed tables to those declared in `batch(tables, fn)` — accessing
|
|
196
|
+
* others throws `VaultScopeError`.
|
|
197
|
+
*/
|
|
198
|
+
export type TransactionContext<S extends AnySchema, K extends keyof S & string = keyof S & string> = {
|
|
199
|
+
clear<T extends K>(table: T): Promise<void>;
|
|
200
|
+
count<T extends K>(table: T): Promise<number>;
|
|
201
|
+
delete<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;
|
|
202
|
+
/** Delete multiple records by key in a single operation. Returns the number of records removed. */
|
|
203
|
+
deleteMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<number>;
|
|
204
|
+
/**
|
|
205
|
+
* Returns all `[key, record]` pairs in the table.
|
|
206
|
+
* Useful for cache-warming, migration scripts, and debugging.
|
|
207
|
+
*/
|
|
208
|
+
entries<T extends K>(table: T): Promise<Array<[KeyOf<S, T>, RecordOf<S, T>]>>;
|
|
209
|
+
get<T extends K>(table: T, key: KeyOf<S, T>): Promise<RecordOf<S, T> | undefined>;
|
|
210
|
+
/** Fetch all records in the table. */
|
|
211
|
+
getAll<T extends K>(table: T): Promise<RecordOf<S, T>[]>;
|
|
212
|
+
/** Fetch multiple records by key in a single operation. Preserves key order; missing keys yield `undefined`. */
|
|
213
|
+
getMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<Array<RecordOf<S, T> | undefined>>;
|
|
214
|
+
/**
|
|
215
|
+
* Read-or-insert: returns the existing record if present, otherwise calls `defaultFn()`,
|
|
216
|
+
* writes the result, and returns it. Equivalent to an `upsert` that never overwrites.
|
|
217
|
+
*
|
|
218
|
+
* **Not atomic for memory and WebStorage adapters.** Two concurrent calls may both observe
|
|
219
|
+
* a missing record and both invoke `defaultFn()`, writing twice. For guaranteed atomicity,
|
|
220
|
+
* wrap in `batch(['table'], tx => tx.getOrDefault(...))` with the IndexedDB adapter.
|
|
221
|
+
*/
|
|
222
|
+
getOrDefault<T extends K>(table: T, key: KeyOf<S, T>, defaultFn: () => RecordOf<S, T>, ttl?: TtlMs): Promise<RecordOf<S, T>>;
|
|
223
|
+
has<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;
|
|
224
|
+
/** Returns `true` if the table contains no live records. Equivalent to `(await count(table)) === 0`. */
|
|
225
|
+
isEmpty<T extends K>(table: T): Promise<boolean>;
|
|
226
|
+
/**
|
|
227
|
+
* Returns the primary key of every live record in the table.
|
|
228
|
+
* Without a `filter`, uses a key-only backend path (no full records fetched).
|
|
229
|
+
* Useful for existence checks, diffing, and cache-invalidation.
|
|
230
|
+
*
|
|
231
|
+
* Pass an optional `filter` predicate to restrict results — when provided, all records are
|
|
232
|
+
* fetched internally and the predicate is applied before key extraction.
|
|
233
|
+
*/
|
|
234
|
+
keys<T extends K>(table: T, filter?: (record: RecordOf<S, T>) => boolean): Promise<KeyOf<S, T>[]>;
|
|
235
|
+
put<T extends K>(table: T, value: RecordOf<S, T>, ttl?: TtlMs): Promise<void>;
|
|
236
|
+
putAll<T extends K>(table: T, values: RecordOf<S, T>[], ttl?: TtlMs): Promise<void>;
|
|
237
|
+
query<T extends K>(table: T): QueryBuilder<RecordOf<S, T>>;
|
|
238
|
+
/**
|
|
239
|
+
* Merge `changes` into the existing record identified by `key` and persist the result.
|
|
240
|
+
* Returns `undefined` when the key does not exist — use `upsert` for insert-or-update semantics.
|
|
241
|
+
*/
|
|
242
|
+
update<T extends K>(table: T, key: KeyOf<S, T>, changes: Partial<RecordOf<S, T>>, ttl?: TtlMs): Promise<RecordOf<S, T> | undefined>;
|
|
243
|
+
upsert<T extends K>(table: T, key: KeyOf<S, T>, fn: (existing: RecordOf<S, T> | undefined) => RecordOf<S, T>, ttl?: TtlMs): Promise<RecordOf<S, T>>;
|
|
244
|
+
};
|
|
245
|
+
/** Full client API for vault adapters. */
|
|
246
|
+
export interface Adapter<S extends AnySchema> {
|
|
247
|
+
clear<K extends keyof S & string>(table: K): Promise<void>;
|
|
248
|
+
count<K extends keyof S & string>(table: K): Promise<number>;
|
|
249
|
+
delete<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<boolean>;
|
|
250
|
+
/** Delete multiple records by key in a single operation. Returns the number of records removed. */
|
|
251
|
+
deleteMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<number>;
|
|
252
|
+
/**
|
|
253
|
+
* Returns all `[key, record]` pairs in the table.
|
|
254
|
+
* Useful for cache-warming, migration scripts, and debugging.
|
|
255
|
+
*/
|
|
256
|
+
entries<K extends keyof S & string>(table: K): Promise<Array<[KeyOf<S, K>, RecordOf<S, K>]>>;
|
|
257
|
+
get<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<RecordOf<S, K> | undefined>;
|
|
258
|
+
/** Fetch all records in the table. */
|
|
259
|
+
getAll<K extends keyof S & string>(table: K): Promise<RecordOf<S, K>[]>;
|
|
260
|
+
/** Fetch multiple records by key in a single operation. Preserves key order; missing keys yield `undefined`. */
|
|
261
|
+
getMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<Array<RecordOf<S, K> | undefined>>;
|
|
262
|
+
/**
|
|
263
|
+
* Read-or-insert: returns the existing record if present, otherwise calls `defaultFn()`,
|
|
264
|
+
* writes the result, and returns it. Equivalent to an `upsert` that never overwrites.
|
|
265
|
+
*
|
|
266
|
+
* **Not atomic for memory and WebStorage adapters.** Two concurrent calls may both observe
|
|
267
|
+
* a missing record and both invoke `defaultFn()`, writing twice. For guaranteed atomicity,
|
|
268
|
+
* wrap in `batch(['table'], tx => tx.getOrDefault(...))` with the IndexedDB adapter.
|
|
269
|
+
*/
|
|
270
|
+
getOrDefault<K extends keyof S & string>(table: K, key: KeyOf<S, K>, defaultFn: () => RecordOf<S, K>, ttl?: TtlMs): Promise<RecordOf<S, K>>;
|
|
271
|
+
has<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<boolean>;
|
|
272
|
+
/** Returns `true` if the table contains no live records. Equivalent to `(await count(table)) === 0`. */
|
|
273
|
+
isEmpty<K extends keyof S & string>(table: K): Promise<boolean>;
|
|
274
|
+
/**
|
|
275
|
+
* Returns the primary key of every live record in the table.
|
|
276
|
+
* Without a `filter`, uses a key-only backend path (no full records fetched).
|
|
277
|
+
* Useful for existence checks, diffing, and cache-invalidation.
|
|
278
|
+
*
|
|
279
|
+
* Pass an optional `filter` predicate to restrict results — when provided, all records are
|
|
280
|
+
* fetched internally and the predicate is applied before key extraction.
|
|
281
|
+
*/
|
|
282
|
+
keys<K extends keyof S & string>(table: K, filter?: (record: RecordOf<S, K>) => boolean): Promise<KeyOf<S, K>[]>;
|
|
283
|
+
put<K extends keyof S & string>(table: K, value: RecordOf<S, K>, ttl?: TtlMs): Promise<void>;
|
|
284
|
+
putAll<K extends keyof S & string>(table: K, values: RecordOf<S, K>[], ttl?: TtlMs): Promise<void>;
|
|
285
|
+
query<K extends keyof S & string>(table: K): QueryBuilder<RecordOf<S, K>>;
|
|
286
|
+
/**
|
|
287
|
+
* Merge `changes` into the existing record identified by `key` and persist the result.
|
|
288
|
+
* Returns `undefined` when the key does not exist — use `upsert` for insert-or-update semantics.
|
|
289
|
+
*/
|
|
290
|
+
update<K extends keyof S & string>(table: K, key: KeyOf<S, K>, changes: Partial<RecordOf<S, K>>, ttl?: TtlMs): Promise<RecordOf<S, K> | undefined>;
|
|
291
|
+
upsert<K extends keyof S & string>(table: K, key: KeyOf<S, K>, fn: (existing: RecordOf<S, K> | undefined) => RecordOf<S, K>, ttl?: TtlMs): Promise<RecordOf<S, K>>;
|
|
292
|
+
/**
|
|
293
|
+
* Execute multiple operations against a set of tables with deferred observer notifications.
|
|
294
|
+
*
|
|
295
|
+
* All observer callbacks fire once per dirty table after `fn` resolves, instead of after
|
|
296
|
+
* each individual write. Inside `fn`, only the tables declared in `tables` may be accessed.
|
|
297
|
+
*
|
|
298
|
+
* **Atomicity:** For the IndexedDB adapter this runs as a real IDB transaction — all writes
|
|
299
|
+
* are atomic and rolled back on error. For the memory and WebStorage adapters the operation
|
|
300
|
+
* is **not atomic** — concurrent `batch()` calls or concurrent mutations may interleave.
|
|
301
|
+
*/
|
|
302
|
+
batch<K extends keyof S & string, R>(tables: readonly K[], fn: (tx: TransactionContext<S, K>) => Promise<R>): Promise<R>;
|
|
303
|
+
/** Returns live record counts and expired-but-not-yet-evicted counts per table. */
|
|
304
|
+
debug(): Promise<DebugInfo<S>>;
|
|
305
|
+
/** `AbortSignal` aborted when `dispose()` is called. Use to tie external lifetimes to this adapter. */
|
|
306
|
+
readonly disposalSignal: AbortSignal;
|
|
307
|
+
/** Releases all resources (observers, cross-tab channel, DB connection). */
|
|
308
|
+
dispose(): Promise<void>;
|
|
309
|
+
/** `true` after `dispose()` has been called. */
|
|
310
|
+
readonly disposed: boolean;
|
|
311
|
+
/** Delegates to `dispose()`. Enables `await using` declarations. */
|
|
312
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
313
|
+
observe<K extends keyof S & string>(table: K, listener: Observer<RecordOf<S, K>>, options?: {
|
|
314
|
+
/**
|
|
315
|
+
* When `false`, skips the automatic initial snapshot fired on registration.
|
|
316
|
+
* Useful when the caller already has the current table state (e.g. from a
|
|
317
|
+
* preceding `getAll()` call) and only wants change notifications.
|
|
318
|
+
* Defaults to `true`.
|
|
319
|
+
*/
|
|
320
|
+
immediate?: boolean;
|
|
321
|
+
/**
|
|
322
|
+
* An `AbortSignal` that, when aborted, automatically unsubscribes this observer.
|
|
323
|
+
* Aligns with the platform pattern used by `fetch`, `addEventListener`, etc.
|
|
324
|
+
*
|
|
325
|
+
* ```ts
|
|
326
|
+
* const controller = new AbortController();
|
|
327
|
+
* db.observe('users', (users) => render(users), { signal: controller.signal });
|
|
328
|
+
* // later:
|
|
329
|
+
* controller.abort(); // stops the observer
|
|
330
|
+
* ```
|
|
331
|
+
*/
|
|
332
|
+
signal?: AbortSignal;
|
|
333
|
+
}): Unsubscribe;
|
|
334
|
+
/**
|
|
335
|
+
* Observe multiple tables simultaneously. The listener receives a combined snapshot
|
|
336
|
+
* `{ [tableName]: RecordOf<S, T>[] }` and fires once after all tables have delivered
|
|
337
|
+
* their first snapshot (initial load). Subsequent firings are coalesced per microtask,
|
|
338
|
+
* so a batch that writes to multiple observed tables triggers exactly one combined callback.
|
|
339
|
+
*
|
|
340
|
+
* @param tables - The tables to observe. Must be non-empty.
|
|
341
|
+
* @param listener - Called with a snapshot map keyed by table name.
|
|
342
|
+
* @param options.signal - An `AbortSignal` that automatically unsubscribes all observers.
|
|
343
|
+
* @returns An unsubscribe function that stops all underlying observers.
|
|
344
|
+
*/
|
|
345
|
+
observeMany<K extends keyof S & string>(tables: readonly K[], listener: (snapshots: {
|
|
346
|
+
[T in K]: RecordOf<S, T>[];
|
|
347
|
+
}) => void, options?: {
|
|
348
|
+
/**
|
|
349
|
+
* When `true`, fires the listener as soon as any table delivers its first snapshot,
|
|
350
|
+
* using empty arrays for tables not yet resolved. Defaults to `false` (wait for all tables).
|
|
351
|
+
*
|
|
352
|
+
* Useful when some tables may be large and you want to render partial data immediately.
|
|
353
|
+
*/
|
|
354
|
+
eager?: boolean;
|
|
355
|
+
signal?: AbortSignal;
|
|
356
|
+
}): Unsubscribe;
|
|
357
|
+
/**
|
|
358
|
+
* Explicitly delete all TTL-expired records from the specified tables (or all tables when
|
|
359
|
+
* no filter is provided). Returns the number of records pruned per table.
|
|
360
|
+
*
|
|
361
|
+
* Useful as a scheduled maintenance task for write-heavy tables that are rarely
|
|
362
|
+
* read (lazy eviction would not reclaim storage otherwise).
|
|
363
|
+
*
|
|
364
|
+
* ```ts
|
|
365
|
+
* // Prune all tables
|
|
366
|
+
* await db.pruneExpired();
|
|
367
|
+
*
|
|
368
|
+
* // Prune only TTL-bearing tables
|
|
369
|
+
* await db.pruneExpired(['sessions', 'tokens']);
|
|
370
|
+
* ```
|
|
371
|
+
*
|
|
372
|
+
* **Does not trigger observer callbacks.** TTL-expired records are already logically
|
|
373
|
+
* absent, so their physical removal does not change observable state.
|
|
374
|
+
*/
|
|
375
|
+
pruneExpired(tables?: readonly (keyof S & string)[]): Promise<{
|
|
376
|
+
[K in keyof S & string]: number;
|
|
377
|
+
}>;
|
|
378
|
+
/**
|
|
379
|
+
* An AsyncIterable that yields a fresh snapshot of the table on every change.
|
|
380
|
+
* The first value is emitted immediately.
|
|
381
|
+
*
|
|
382
|
+
* ```ts
|
|
383
|
+
* for await (const users of db.watch('users')) {
|
|
384
|
+
* render(users);
|
|
385
|
+
* }
|
|
386
|
+
* ```
|
|
387
|
+
*
|
|
388
|
+
* Pass an `AbortSignal` to stop iteration from outside the loop:
|
|
389
|
+
* ```ts
|
|
390
|
+
* const controller = new AbortController();
|
|
391
|
+
* for await (const users of db.watch('users', { signal: controller.signal })) {
|
|
392
|
+
* render(users);
|
|
393
|
+
* }
|
|
394
|
+
* controller.abort(); // stops the iteration
|
|
395
|
+
* ```
|
|
396
|
+
*
|
|
397
|
+
* @param options.mode
|
|
398
|
+
* - `'latest'` (default): if the consumer lags, intermediate snapshots are dropped and
|
|
399
|
+
* only the most recent one is retained. Best for rendering/display use-cases.
|
|
400
|
+
* - `'all'`: every snapshot is queued and delivered in order. Use when every intermediate
|
|
401
|
+
* state matters (audit trails, animation frames).
|
|
402
|
+
* @param options.signal - An `AbortSignal` that stops the iteration.
|
|
403
|
+
*/
|
|
404
|
+
watch<K extends keyof S & string>(table: K, options?: {
|
|
405
|
+
mode?: 'all' | 'latest';
|
|
406
|
+
signal?: AbortSignal;
|
|
407
|
+
}): AsyncIterable<RecordOf<S, K>[]>;
|
|
408
|
+
}
|
|
409
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { VaultError as e } from "./errors.js";
|
|
2
|
+
import { assertTtlMs as t } from "./ttl.js";
|
|
3
|
+
//#region src/types.ts
|
|
4
|
+
function n(n) {
|
|
5
|
+
function r(n) {
|
|
6
|
+
return {
|
|
7
|
+
...n,
|
|
8
|
+
index: (t) => {
|
|
9
|
+
let i = n.indexes ?? [];
|
|
10
|
+
if (i.includes(t)) throw new e(`table index "${t}" is already registered`);
|
|
11
|
+
return r({
|
|
12
|
+
...n,
|
|
13
|
+
indexes: [...i, t]
|
|
14
|
+
});
|
|
15
|
+
},
|
|
16
|
+
ttl: (e) => (t(e, "table.ttl"), r({
|
|
17
|
+
...n,
|
|
18
|
+
defaultTtl: e
|
|
19
|
+
}))
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return r({ key: n });
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { n as table };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["import type { QueryBuilder } from './query';\n\nimport { VaultError } from './errors';\nimport { assertTtlMs, type TtlMs, type VaultCodec } from './ttl';\n\n/* -------------------- Re-export TtlMs and VaultCodec for public API -------------------- */\n\nexport type { TtlMs, VaultCodec };\n\n/* -------------------- Schema types -------------------- */\n\n/** @internal Unique symbol used as a phantom type brand — never appears at runtime. */\ndeclare const schemaEntryBrand: unique symbol;\n\n/**\n * Schema entry for a single table.\n * `T` is the record type; `Key` is the primary key field name.\n *\n * The phantom brand `[schemaEntryBrand]` holds `T` in a directly inferable position so\n * TypeScript can recover `T` in `RecordOf` conditional types. It uses a unique symbol key\n * so it is invisible in IDE autocompletion and cannot be set accidentally.\n */\nexport type SchemaEntry<T extends Record<string, unknown>, Key extends keyof T & string = keyof T & string> = {\n defaultTtl?: TtlMs;\n /**\n * Secondary index field names. The IndexedDB adapter creates an IDB index for each field,\n * enabling push-down optimisation for `equals`, `between`, and `startsWith` queries on those\n * fields — avoiding a full-table scan.\n *\n * ```ts\n * const schema = {\n * users: table<User>('id').index('email').index('city'),\n * };\n * // db.query('users').equals('email', 'alice@example.com') → uses IDB index\n * ```\n */\n indexes?: readonly (keyof T & string)[];\n key: Key;\n /** @internal Phantom brand — enables TypeScript to infer T. Never has a runtime value. */\n readonly [schemaEntryBrand]?: T;\n};\n\n/** A schema is any record of `SchemaEntry`-compatible values. Checked structurally so that\n * concrete `SchemaEntry<T, Key>` values satisfy it without covariance constraints. */\nexport type AnySchema = Record<string, { defaultTtl?: TtlMs; indexes?: readonly string[]; key: string }>;\n\n/**\n * Fluent builder returned by `table()` — satisfies `SchemaEntry` and adds `.ttl()` and `.index()` chaining.\n * Export this type to annotate schema entry variables without using `ReturnType<typeof table<T, K>>`.\n */\nexport type TableBuilder<\n T extends Record<string, unknown>,\n Key extends keyof T & string = keyof T & string,\n> = SchemaEntry<T, Key> & {\n /**\n * Register a secondary index on the given field. Can be chained multiple times.\n * Only used by the IndexedDB adapter; other adapters fall back to in-memory filtering.\n *\n * **Custom codec caveat:** the IndexedDB adapter creates the index with keyPath\n * `value.<field>`, which assumes the default `{ value, expiresAt? }` storage envelope\n * (see `VaultCodec`). A custom codec that changes the top-level shape (e.g.\n * `createVersionedCodec`, or a compact/encrypted format) breaks index push-down silently —\n * queries on the indexed field return empty results instead of throwing. Avoid combining\n * `.index()` with a non-default codec, or design the custom codec to preserve `value.<field>`.\n */\n index: <F extends keyof T & string>(field: F) => TableBuilder<T, Key>;\n /** Set a default TTL (ms) applied to all `put`/`putAll` calls that don't specify one explicitly. */\n ttl: (ms: TtlMs) => TableBuilder<T, Key>;\n};\n\nexport function table<T extends Record<string, unknown>, Key extends keyof T & string = keyof T & string>(\n key: Key,\n): TableBuilder<T, Key> {\n function makeBuilder(entry: SchemaEntry<T, Key>): TableBuilder<T, Key> {\n return {\n ...entry,\n index: <F extends keyof T & string>(field: F): TableBuilder<T, Key> => {\n const current = (entry.indexes ?? []) as readonly (keyof T & string)[];\n\n if (current.includes(field)) {\n throw new VaultError(`table index \"${field}\" is already registered`);\n }\n\n return makeBuilder({ ...entry, indexes: [...current, field] });\n },\n ttl: (ms: TtlMs): TableBuilder<T, Key> => {\n assertTtlMs(ms, 'table.ttl');\n\n return makeBuilder({ ...entry, defaultTtl: ms });\n },\n };\n }\n\n return makeBuilder({ key });\n}\n\n/** Extracts the record type from a schema table entry. */\nexport type RecordOf<S extends AnySchema, K extends keyof S> = S[K] extends SchemaEntry<infer R, string> ? R : never;\n\n/** Extracts the primary key value type from a schema table entry. */\nexport type KeyOf<S extends AnySchema, K extends keyof S> =\n S[K] extends SchemaEntry<infer R, infer Key> ? R[Key] : never;\n\n/* -------------------- Migration -------------------- */\n\nexport type MigrationContext = {\n db: IDBDatabase;\n newVersion: number | null;\n oldVersion: number;\n tx: IDBTransaction;\n};\n\nexport type MigrationFn = (ctx: MigrationContext) => void;\n\n/* -------------------- Plugin / integration types -------------------- */\n\n/**\n * Minimal logger interface satisfied structurally by `/rune` Logger.\n * Pass a rune Logger instance directly — no adapter needed:\n *\n * ```ts\n * import { createLogger } from '@vielzeug/rune';\n * const db = createMemory({ schema, logger: createLogger('db') });\n * ```\n *\n * Vault only emits error-level logs, so a single rune-compatible `error`\n * method is enough.\n */\nexport interface VaultLogger {\n error(messageOrContext?: Record<string, unknown> | Error | string, message?: string): void;\n}\n\n/**\n * Minimal synchronous validator interface satisfied structurally by\n * `/sieve` Schema.\n * Pass a sieve schema directly — no adapter needed:\n *\n * ```ts\n * import { s } from '@vielzeug/spell';\n * const db = createMemory({\n * schema: { users: table<User>('id') },\n * validators: { users: v.object({ id: v.number(), name: v.string() }) },\n * });\n * ```\n *\n * Vault requires synchronous validation because writes may execute inside a\n * live IndexedDB transaction. Any object with a `parse(value: unknown): T`\n * method works.\n */\nexport interface RecordValidator<T> {\n parse(value: unknown): T;\n}\n\n/**\n * Per-table record parsers. Keys match your vault schema table names.\n * Validators run before every `put`, `putAll`, and inside `update`/`upsert`.\n */\nexport type TableValidators<S extends AnySchema> = {\n [K in keyof S]?: RecordValidator<RecordOf<S, K>>;\n};\n\n/**\n * Minimal writable-signal interface satisfied structurally by\n * `/ripple` `Signal<T>` and `Store<T>`.\n * Pass a ripple signal directly — no adapter needed:\n *\n * ```ts\n * import { signal } from '@vielzeug/ripple';\n * const usersSignal = signal<User[]>([]);\n *\n * const db = createMemory({\n * schema: { users: table<User>('id') },\n * signals: { users: usersSignal },\n * });\n *\n * // usersSignal.value is now always in sync with the users table.\n * ```\n *\n * Any object with an `update(fn: (current: T) => T): void` method satisfies\n * this interface. Vault calls `signal.update(() => snapshot)` on each change.\n */\nexport interface ReactiveSignal<T> {\n update(fn: (current: T) => T): void;\n}\n\n/**\n * Per-table reactive signals. Keys match your vault schema table names.\n * Each signal is automatically kept in sync with the table via `observe()`.\n * Signals are wired at construction time and cleaned up on `dispose()`.\n */\nexport type TableSignals<S extends AnySchema> = {\n [K in keyof S]?: ReactiveSignal<RecordOf<S, K>[]>;\n};\n\n/* -------------------- Observer -------------------- */\n\nexport type Observer<T> = (records: T[]) => void;\n\n/** A function that cancels an active subscription. Returned by `observe()` and `observeMany()`. */\nexport type Unsubscribe = () => void;\n\n/* -------------------- Shared adapter options -------------------- */\n\n/**\n * Common options shared by all adapter factories.\n * Individual adapters extend this with adapter-specific fields.\n */\nexport type BaseAdapterOptions<S extends AnySchema> = {\n /**\n * Pluggable serialization codec. Provide a custom implementation to change how values\n * are encoded at rest (e.g. compact keys, encryption, msgpack).\n * Defaults to the standard `{ value, expiresAt? }` JSON envelope.\n *\n * **IndexedDB + `.index()` caveat:** secondary indexes are created with keyPath\n * `value.<field>`, which assumes the default envelope shape. A custom codec that changes\n * the top-level shape breaks index push-down silently (queries return empty results\n * instead of throwing) — see `TableBuilder.index`.\n */\n codec?: VaultCodec;\n /** Structured logger. A /rune Logger satisfies VaultLogger directly. */\n logger?: VaultLogger;\n /** Performance monitoring hook. Called after every operation with duration in ms. */\n onMetrics?: (event: MetricsEvent) => void;\n schema: S;\n /**\n * Per-table reactive signals. A /ripple Signal<T[]> satisfies ReactiveSignal directly.\n * Each signal is kept in sync with its table automatically via observe().\n */\n signals?: TableSignals<S>;\n /** Per-table validators. A /sieve Schema satisfies this directly via `parse()`. */\n validators?: TableValidators<S>;\n};\n\n/* -------------------- Metrics -------------------- */\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 /**\n * The table name. For `batch` operations this is `'*'` because a batch may\n * span multiple tables and there is no single canonical table name.\n */\n table: string;\n};\n\n/* -------------------- Debug info -------------------- */\n\nexport type DebugStats = {\n /** Number of TTL-expired records still physically in the store (not yet lazily evicted). */\n expiredCount: number;\n /** Number of live (non-expired) records. */\n recordCount: number;\n};\n\nexport type DebugInfo<S extends AnySchema> = {\n tables: Array<{ name: keyof S & string } & DebugStats>;\n};\n\n/* -------------------- Transaction context -------------------- */\n\n/**\n * Available inside `batch()` callbacks. For IndexedDB, operations run in a real atomic IDB transaction.\n * The batch scope restricts accessed tables to those declared in `batch(tables, fn)` — accessing\n * others throws `VaultScopeError`.\n */\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 /** Delete multiple records by key in a single operation. Returns the number of records removed. */\n deleteMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<number>;\n /**\n * Returns all `[key, record]` pairs in the table.\n * Useful for cache-warming, migration scripts, and debugging.\n */\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 /** Fetch all records in the table. */\n getAll<T extends K>(table: T): Promise<RecordOf<S, T>[]>;\n /** Fetch multiple records by key in a single operation. Preserves key order; missing keys yield `undefined`. */\n getMany<T extends K>(table: T, keys: KeyOf<S, T>[]): Promise<Array<RecordOf<S, T> | undefined>>;\n /**\n * Read-or-insert: returns the existing record if present, otherwise calls `defaultFn()`,\n * writes the result, and returns it. Equivalent to an `upsert` that never overwrites.\n *\n * **Not atomic for memory and WebStorage adapters.** Two concurrent calls may both observe\n * a missing record and both invoke `defaultFn()`, writing twice. For guaranteed atomicity,\n * wrap in `batch(['table'], tx => tx.getOrDefault(...))` with the IndexedDB adapter.\n */\n getOrDefault<T extends K>(\n table: T,\n key: KeyOf<S, T>,\n defaultFn: () => RecordOf<S, T>,\n ttl?: TtlMs,\n ): Promise<RecordOf<S, T>>;\n has<T extends K>(table: T, key: KeyOf<S, T>): Promise<boolean>;\n /** Returns `true` if the table contains no live records. Equivalent to `(await count(table)) === 0`. */\n isEmpty<T extends K>(table: T): Promise<boolean>;\n /**\n * Returns the primary key of every live record in the table.\n * Without a `filter`, uses a key-only backend path (no full records fetched).\n * Useful for existence checks, diffing, and cache-invalidation.\n *\n * Pass an optional `filter` predicate to restrict results — when provided, all records are\n * fetched internally and the predicate is applied before key extraction.\n */\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?: TtlMs): Promise<void>;\n putAll<T extends K>(table: T, values: RecordOf<S, T>[], ttl?: TtlMs): Promise<void>;\n query<T extends K>(table: T): QueryBuilder<RecordOf<S, T>>;\n /**\n * Merge `changes` into the existing record identified by `key` and persist the result.\n * Returns `undefined` when the key does not exist — use `upsert` for insert-or-update semantics.\n */\n update<T extends K>(\n table: T,\n key: KeyOf<S, T>,\n changes: Partial<RecordOf<S, T>>,\n ttl?: TtlMs,\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?: TtlMs,\n ): Promise<RecordOf<S, T>>;\n};\n\n/* -------------------- Adapter interface -------------------- */\n\n/** Full client API for vault adapters. */\nexport interface Adapter<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 /** Delete multiple records by key in a single operation. Returns the number of records removed. */\n deleteMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<number>;\n /**\n * Returns all `[key, record]` pairs in the table.\n * Useful for cache-warming, migration scripts, and debugging.\n */\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 /** Fetch all records in the table. */\n getAll<K extends keyof S & string>(table: K): Promise<RecordOf<S, K>[]>;\n /** Fetch multiple records by key in a single operation. Preserves key order; missing keys yield `undefined`. */\n getMany<K extends keyof S & string>(table: K, keys: KeyOf<S, K>[]): Promise<Array<RecordOf<S, K> | undefined>>;\n /**\n * Read-or-insert: returns the existing record if present, otherwise calls `defaultFn()`,\n * writes the result, and returns it. Equivalent to an `upsert` that never overwrites.\n *\n * **Not atomic for memory and WebStorage adapters.** Two concurrent calls may both observe\n * a missing record and both invoke `defaultFn()`, writing twice. For guaranteed atomicity,\n * wrap in `batch(['table'], tx => tx.getOrDefault(...))` with the IndexedDB adapter.\n */\n getOrDefault<K extends keyof S & string>(\n table: K,\n key: KeyOf<S, K>,\n defaultFn: () => RecordOf<S, K>,\n ttl?: TtlMs,\n ): Promise<RecordOf<S, K>>;\n has<K extends keyof S & string>(table: K, key: KeyOf<S, K>): Promise<boolean>;\n /** Returns `true` if the table contains no live records. Equivalent to `(await count(table)) === 0`. */\n isEmpty<K extends keyof S & string>(table: K): Promise<boolean>;\n /**\n * Returns the primary key of every live record in the table.\n * Without a `filter`, uses a key-only backend path (no full records fetched).\n * Useful for existence checks, diffing, and cache-invalidation.\n *\n * Pass an optional `filter` predicate to restrict results — when provided, all records are\n * fetched internally and the predicate is applied before key extraction.\n */\n keys<K extends keyof S & string>(table: K, filter?: (record: RecordOf<S, K>) => boolean): Promise<KeyOf<S, K>[]>;\n put<K extends keyof S & string>(table: K, value: RecordOf<S, K>, ttl?: TtlMs): Promise<void>;\n putAll<K extends keyof S & string>(table: K, values: RecordOf<S, K>[], ttl?: TtlMs): Promise<void>;\n query<K extends keyof S & string>(table: K): QueryBuilder<RecordOf<S, K>>;\n /**\n * Merge `changes` into the existing record identified by `key` and persist the result.\n * Returns `undefined` when the key does not exist — use `upsert` for insert-or-update semantics.\n */\n update<K extends keyof S & string>(\n table: K,\n key: KeyOf<S, K>,\n changes: Partial<RecordOf<S, K>>,\n ttl?: TtlMs,\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?: TtlMs,\n ): Promise<RecordOf<S, K>>;\n /**\n * Execute multiple operations against a set of tables with deferred observer notifications.\n *\n * All observer callbacks fire once per dirty table after `fn` resolves, instead of after\n * each individual write. Inside `fn`, only the tables declared in `tables` may be accessed.\n *\n * **Atomicity:** For the IndexedDB adapter this runs as a real IDB transaction — all writes\n * are atomic and rolled back on error. For the memory and WebStorage adapters the operation\n * is **not atomic** — concurrent `batch()` calls or concurrent mutations may interleave.\n */\n batch<K extends keyof S & string, R>(\n tables: readonly K[],\n fn: (tx: TransactionContext<S, K>) => Promise<R>,\n ): Promise<R>;\n /** Returns live record counts and expired-but-not-yet-evicted counts per table. */\n debug(): Promise<DebugInfo<S>>;\n /** `AbortSignal` aborted when `dispose()` is called. Use to tie external lifetimes to this adapter. */\n readonly disposalSignal: AbortSignal;\n /** Releases all resources (observers, cross-tab channel, DB connection). */\n dispose(): Promise<void>;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Delegates to `dispose()`. Enables `await using` declarations. */\n [Symbol.asyncDispose](): Promise<void>;\n observe<K extends keyof S & string>(\n table: K,\n listener: Observer<RecordOf<S, K>>,\n options?: {\n /**\n * When `false`, skips the automatic initial snapshot fired on registration.\n * Useful when the caller already has the current table state (e.g. from a\n * preceding `getAll()` call) and only wants change notifications.\n * Defaults to `true`.\n */\n immediate?: boolean;\n /**\n * An `AbortSignal` that, when aborted, automatically unsubscribes this observer.\n * Aligns with the platform pattern used by `fetch`, `addEventListener`, etc.\n *\n * ```ts\n * const controller = new AbortController();\n * db.observe('users', (users) => render(users), { signal: controller.signal });\n * // later:\n * controller.abort(); // stops the observer\n * ```\n */\n signal?: AbortSignal;\n },\n ): Unsubscribe;\n /**\n * Observe multiple tables simultaneously. The listener receives a combined snapshot\n * `{ [tableName]: RecordOf<S, T>[] }` and fires once after all tables have delivered\n * their first snapshot (initial load). Subsequent firings are coalesced per microtask,\n * so a batch that writes to multiple observed tables triggers exactly one combined callback.\n *\n * @param tables - The tables to observe. Must be non-empty.\n * @param listener - Called with a snapshot map keyed by table name.\n * @param options.signal - An `AbortSignal` that automatically unsubscribes all observers.\n * @returns An unsubscribe function that stops all underlying observers.\n */\n observeMany<K extends keyof S & string>(\n tables: readonly K[],\n listener: (snapshots: { [T in K]: RecordOf<S, T>[] }) => void,\n options?: {\n /**\n * When `true`, fires the listener as soon as any table delivers its first snapshot,\n * using empty arrays for tables not yet resolved. Defaults to `false` (wait for all tables).\n *\n * Useful when some tables may be large and you want to render partial data immediately.\n */\n eager?: boolean;\n signal?: AbortSignal;\n },\n ): Unsubscribe;\n /**\n * Explicitly delete all TTL-expired records from the specified tables (or all tables when\n * no filter is provided). Returns the number of records pruned per table.\n *\n * Useful as a scheduled maintenance task for write-heavy tables that are rarely\n * read (lazy eviction would not reclaim storage otherwise).\n *\n * ```ts\n * // Prune all tables\n * await db.pruneExpired();\n *\n * // Prune only TTL-bearing tables\n * await db.pruneExpired(['sessions', 'tokens']);\n * ```\n *\n * **Does not trigger observer callbacks.** TTL-expired records are already logically\n * absent, so their physical removal does not change observable state.\n */\n pruneExpired(tables?: readonly (keyof S & string)[]): Promise<{ [K in keyof S & string]: number }>;\n /**\n * An AsyncIterable that yields a fresh snapshot of the table on every change.\n * The first value is emitted immediately.\n *\n * ```ts\n * for await (const users of db.watch('users')) {\n * render(users);\n * }\n * ```\n *\n * Pass an `AbortSignal` to stop iteration from outside the loop:\n * ```ts\n * const controller = new AbortController();\n * for await (const users of db.watch('users', { signal: controller.signal })) {\n * render(users);\n * }\n * controller.abort(); // stops the iteration\n * ```\n *\n * @param options.mode\n * - `'latest'` (default): if the consumer lags, intermediate snapshots are dropped and\n * only the most recent one is retained. Best for rendering/display use-cases.\n * - `'all'`: every snapshot is queued and delivered in order. Use when every intermediate\n * state matters (audit trails, animation frames).\n * @param options.signal - An `AbortSignal` that stops the iteration.\n */\n watch<K extends keyof S & string>(\n table: K,\n options?: { mode?: 'all' | 'latest'; signal?: AbortSignal },\n ): AsyncIterable<RecordOf<S, K>[]>;\n}\n"],"mappings":";;;AAsEA,SAAgB,EACd,GACsB;CACtB,SAAS,EAAY,GAAkD;EACrE,OAAO;GACL,GAAG;GACH,QAAoC,MAAmC;IACrE,IAAM,IAAW,EAAM,WAAW,CAAC;IAEnC,IAAI,EAAQ,SAAS,CAAK,GACxB,MAAM,IAAI,EAAW,gBAAgB,EAAM,wBAAwB;IAGrE,OAAO,EAAY;KAAE,GAAG;KAAO,SAAS,CAAC,GAAG,GAAS,CAAK;IAAE,CAAC;GAC/D;GACA,MAAM,OACJ,EAAY,GAAI,WAAW,GAEpB,EAAY;IAAE,GAAG;IAAO,YAAY;GAAG,CAAC;EAEnD;CACF;CAEA,OAAO,EAAY,EAAE,OAAI,CAAC;AAC5B"}
|
package/dist/vault.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=!globalThis.__VAULT_PROD__;function t(t){e&&console.warn(`[@vielzeug/vault] ${t}`)}function n(t,...n){e&&console.error(`[@vielzeug/vault] ${t}`,...n)}var r=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},i=class extends r{constructor(e=`adapter is disposed`,t){super(e,t)}},a=class extends r{},o=class extends r{},s=class extends r{},c=`\0`;function l(e){return`${encodeURIComponent(e)}${c}`}function u(e,t,n){return`${encodeURIComponent(e)}${c}${encodeURIComponent(t)}${c}${encodeURIComponent(n)}`}function d(e,t){return`${encodeURIComponent(e)}${c}${encodeURIComponent(t)}${c}`}function f(e,t){if(!t)return;let n=l(e);if(!t.startsWith(n))return;let r=t.slice(n.length),i=r.indexOf(c);if(i!==-1)try{return decodeURIComponent(r.slice(0,i))}catch{return}}function p(e,t){let r=new Map,a=!1,o=e=>{t?t(e):n(`observer notification failed`,e)},s=t=>{if(a)return;let n=r.get(t);!n||n.size===0||e(t).then(e=>{if(a)return;let n=r.get(t);if(!(!n||n.size===0))for(let t of n)try{t(e)}catch(e){o(e)}}).catch(e=>o(e))};return{dispose:()=>{a=!0,r.clear()},notify:s,observe:(e,t,{immediate:n=!0,signal:o}={})=>{if(a)throw new i(`observer hub is disposed`);if(o?.aborted)return()=>{};let c=e,l=t,u=r.get(c);u||(u=new Set,r.set(c,u)),u.add(l),n&&s(e);let d=()=>{let e=r.get(c);e&&(e.delete(l),e.size===0&&r.delete(c))};return o?.addEventListener(`abort`,d,{once:!0}),d}}}function m(e,t=`latest`,n){return{[Symbol.asyncIterator](){let r=[],i=null,a=n?.aborted??!1,o=null,s=()=>{a||(a=!0,o?.(),o=null,i&&=(i({done:!0,value:void 0}),null))};return n?.addEventListener(`abort`,s,{once:!0}),a||(o=e(e=>{if(!a)if(i){let t=i;i=null,t({done:!1,value:e})}else t===`all`?r.push(e):r=[e]})),{async next(){return a?{done:!0,value:void 0}:r.length>0?{done:!1,value:r.shift()}:new Promise(e=>{i=e})},async return(){return s(),{done:!0,value:void 0}},async throw(e){return s(),Promise.reject(e)}}}}}function h(e){return function(n,r,{eager:i=!1,signal:o}={}){let s=[...new Set(n)];if(s.length===0)throw new a(`observeMany requires at least one table`);if(s.length<n.length&&t(`observeMany: duplicate table names were ignored`),o?.aborted)return()=>{};let c=new Map,l=!1,u=!1,d=()=>Object.fromEntries(n.map(e=>[e,c.get(e)??[]])),f=()=>{l||!i&&c.size<s.length||(l=!0,queueMicrotask(()=>{l=!1,u||r(d())}))},p=s.map(t=>e.observe(t,e=>{c.set(t,e),f()})),m=()=>{u=!0;for(let e of p)e()};return o?.addEventListener(`abort`,m,{once:!0}),m}}function g(e,t,n){let i=String(e[t].key),a=n[i];if(a==null)throw new r(`key field "${i}" in table "${String(t)}" must be a non-null value, got ${String(a)}`);return a}async function _(e,t){let n=await e.source();for(let e of t)n=e.apply(n);return n}function v(e,t){if(!Number.isInteger(e)||e<0)throw new r(`${t} must be a non-negative integer`);return e}function y(e,t){return{deleteMany:e.deleteMany,source:t}}function b(e,t=[]){let n=n=>b(e,[...t,n]);return{between(r,i,a){let o=String(r);if(t.length===0){if(e.getRange&&e.keyField===o)return b(y(e,()=>e.getRange({lower:i,type:`between`,upper:a})),t);if(e.getIndexRange&&e.indexedFields?.has(o))return b(y(e,()=>e.getIndexRange(o,{lower:i,type:`between`,upper:a})),t)}return n({apply:e=>e.filter(e=>{let t=e[r];return t>=i&&t<=a})})},count(){return _(e,t).then(e=>e.length)},async delete(){if(!e.deleteMany)throw new r(`query.delete is not available for this adapter context`);let n=await _(e,t);return e.deleteMany(n)},equals(n,r){let i=String(n);if(t.length===0){if(e.getRange&&e.keyField===i)return b(y(e,()=>e.getRange({type:`eq`,value:r})));if(e.getIndexRange&&e.indexedFields?.has(i))return b(y(e,()=>e.getIndexRange(i,{type:`eq`,value:r})))}return b(e,[...t,{apply:e=>e.filter(e=>e[n]===r)}])},async exists(){return t.length===0?e.source().then(e=>e.length>0):_(e,t).then(e=>e.length>0)},filter(e){return n({apply:t=>t.filter(e)})},first(){return t.length===0?e.source().then(e=>e[0]):_(e,t).then(e=>e[0])},limit(e){let t=v(e,`query.limit`);return n({apply:e=>e.slice(0,t),isNonFilter:!0})},offset(e){let t=v(e,`query.offset`);return n({apply:e=>e.slice(t),isNonFilter:!0})},orderBy(e,t=`asc`){return n({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(r,i,{ignoreCase:a=!1}={}){let o=String(r);if(!a&&i.length>0&&t.length===0){if(e.getRange&&e.keyField===o)return b(y(e,()=>e.getRange({prefix:i,type:`starts`})),t);if(e.getIndexRange&&e.indexedFields?.has(o))return b(y(e,()=>e.getIndexRange(o,{prefix:i,type:`starts`})),t)}let s=a?i.toLowerCase():i;return n({apply:e=>e.filter(e=>{let t=e[r];return typeof t==`string`?(a?t.toLowerCase():t).startsWith(s):!1})})},toArray(){return _(e,t)},totalCount(){return _(e,t.filter(e=>!e.isNonFilter)).then(e=>e.length)}}}var x={days:e=>C(C(e,`ttl.days`)*864e5,`ttl.days (result)`),hours:e=>C(C(e,`ttl.hours`)*36e5,`ttl.hours (result)`),minutes:e=>C(C(e,`ttl.minutes`)*6e4,`ttl.minutes (result)`),ms:e=>C(e,`ttl.ms`),seconds:e=>C(C(e,`ttl.seconds`)*1e3,`ttl.seconds (result)`)};function S(e){return e!==void 0&&Date.now()>=e}function C(e,t){if(!Number.isFinite(e)||e<=0)throw new r(`${t} expected a finite positive number, received ${String(e)}`);return e}function w(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}var T={decode:w,encode:(e,t)=>t===void 0?{value:e}:{expiresAt:t,value:e}},E=typeof performance<`u`?()=>performance.now():()=>Date.now();function D(e,t,n){return n!==void 0&&C(n,`put/putAll`),n??e[t].defaultTtl}function O(e,t,n,i,a){let o=g(e,t,i);if(o!==n)throw new r(`${a}: key field "${e[t].key}" must be "${String(n)}" but got "${String(o)}" in table "${t}"`)}function k(e,t,n){return e.getMany?e.getMany(t,n):Promise.all(n.map(n=>e.get(t,n)))}function A(e,t,n,r){let i=n[e],a=i.key,o=i.indexes?new Set(i.indexes):void 0;return{deleteMany:async i=>{if(i.length===0)return 0;let a=i.map(t=>g(n,e,t)),o=await t.deleteMany(e,a);return o>0&&r(e),o},getIndexRange:t.getByIndexRange?(n,r)=>t.getByIndexRange(e,n,r):void 0,getRange:t.getByKeyRange?n=>t.getByKeyRange(e,n):void 0,indexedFields:o,keyField:a,source:()=>t.getAll(e)}}function j(e,t,n,r,i){let o=r??((e,t)=>t),s=i?e=>{if(!i.has(e))throw new a(`table "${e}" is not part of this batch scope`)}:e=>{};return{async clear(e){s(e),await t.count(e)!==0&&(await t.clear(e),n(e))},async count(e){return s(e),t.count(e)},async delete(e,r){s(e);let i=await t.delete(e,r);return i&&n(e),i},async deleteMany(e,r){s(e);let i=await t.deleteMany(e,r);return i>0&&n(e),i},async entries(n){s(n);let r=await t.getAll(n),i=e[n].key;return r.map(e=>[e[i],e])},async get(e,n){return s(e),t.get(e,n)},async getAll(e){return s(e),t.getAll(e)},async getMany(e,n){return s(e),k(t,e,n)},async getOrDefault(r,i,a,c){s(r);let l=await t.get(r,i);if(l!==void 0)return l;let u=a();return O(e,r,i,u,`getOrDefault: defaultFn()`),await t.put(r,o(r,u),D(e,r,c)),n(r),u},async has(e,n){return s(e),t.has(e,n)},async isEmpty(e){return s(e),await t.count(e)===0},async keys(n,r){if(s(n),r){let i=await t.getAll(n),a=e[n].key;return i.filter(r).map(e=>e[a])}if(t.getAllKeys)return t.getAllKeys(n);let i=await t.getAll(n),a=e[n].key;return i.map(e=>e[a])},async put(r,i,a){s(r),await t.put(r,o(r,i),D(e,r,a)),n(r)},async putAll(r,i,a){if(s(r),i.length===0)return;let c=i.map(e=>o(r,e));await t.putAll(r,c,D(e,r,a)),n(r)},query(r){return s(r),b(A(r,t,e,n))},async update(r,i,a,c){s(r);let l=await t.get(r,i);if(!l)return;let u={...l,...a};return O(e,r,i,u,`update`),await t.put(r,o(r,u),D(e,r,c)),n(r),u},async upsert(r,i,a,c){s(r);let l=a(await t.get(r,i));return O(e,r,i,l,`upsert: fn()`),await t.put(r,o(r,l),D(e,r,c)),n(r),l}}}function M(e,n,o){let{logger:s,onMetrics:c,signals:l,validators:u}=o??{},d=p(e=>n.getAll(e),s?e=>s.error(e instanceof Error?e:Error(String(e)),`[vault] observer notification failed`):void 0),f=new Map,g=async e=>{if(f.has(e))return f.get(e);let t=await n.count(e);return f.set(e,t),t},_=e=>{f.delete(e),d.notify(e),o?.onMutation?.(e)},v=e=>{f.delete(e),d.notify(e)},y=n.pruneAllExpired?.bind(n),x=n.getRawCount?.bind(n),S=o?.onCrossTabMessage?.(v)??void 0;if(l)for(let[e,t]of Object.entries(l))t&&d.observe(e,e=>{t.update(()=>e)});let C=(e,t)=>{let n=u?.[e];if(!n)return t;try{return n.parse(t)}catch(t){throw new r(`validation failed for table "${e}"`,{cause:t})}},w=(e,t,n)=>{if(!c)return n();let r=E();return n().finally(()=>c({duration:E()-r,operation:t,table:e}))},T=async(t,r)=>{let i=new Set,a=await t(j(e,n,e=>i.add(e),C,r));for(let e of i)_(e);return a},D=o?.buildBatch?.({notifyMutation:_,validate:C}),O=!1,k=j(e,n,_,C),M=h(d),N=new AbortController,P=!1,F=()=>{if(P)throw new i},I={async batch(e,n){if(F(),e.length===0)throw new a(`batch requires at least one table`);return!D&&!O&&(O=!0,t(`batch() on this adapter is not atomic — concurrent mutations may interleave. Use createIndexedDB for atomic batches.`)),w(`*`,`batch`,()=>D?D(e,n):T(n,new Set(e)))},async clear(e){F(),await w(e,`clear`,()=>k.clear(e)),f.set(e,0)},async count(e){return F(),w(e,`count`,()=>g(e))},async debug(){F();let t=Object.keys(e);return{tables:await Promise.all(t.map(async e=>{let t=x?await x(e):void 0,r=await n.count(e);return f.set(e,r),{expiredCount:t===void 0?0:Math.max(0,t-r),name:e,recordCount:r}}))}},async delete(e,t){return F(),w(e,`delete`,()=>k.delete(e,t))},async deleteMany(e,t){return F(),w(e,`deleteMany`,()=>k.deleteMany(e,t))},get disposalSignal(){return N.signal},async dispose(){P||(P=!0,N.abort(),S?.(),d.dispose(),await n.dispose?.())},get disposed(){return P},async entries(e){return F(),w(e,`entries`,()=>k.entries(e))},async get(e,t){return F(),w(e,`get`,()=>k.get(e,t))},async getAll(e){return F(),w(e,`getAll`,()=>k.getAll(e))},async getMany(e,t){return F(),w(e,`getMany`,()=>k.getMany(e,t))},async getOrDefault(e,t,n,r){return F(),w(e,`getOrDefault`,()=>k.getOrDefault(e,t,n,r))},async has(e,t){return F(),w(e,`has`,()=>k.has(e,t))},async isEmpty(e){return F(),w(e,`isEmpty`,async()=>await g(e)===0)},async keys(e,t){return F(),w(e,`keys`,()=>k.keys(e,t))},observe(e,t,n){return F(),d.observe(e,t,n)},observeMany:(e,t,n)=>(F(),M(e,t,n)),async pruneExpired(t){F();let r=t??Object.keys(e);if(!t&&y){let e=await y();for(let[t,n]of Object.entries(e))n>0&&f.delete(t);return e}let i=await Promise.all(r.map(async e=>{let t=await n.pruneExpiredInTable(e);return t>0&&f.delete(e),[e,t]}));return Object.fromEntries(i)},async put(e,t,n){F(),await w(e,`put`,()=>k.put(e,t,n))},async putAll(e,t,n){F(),await w(e,`putAll`,()=>k.putAll(e,t,n))},query(t){F();let r=A(t,n,e,_);return b(c?{...r,deleteMany:r.deleteMany?e=>w(t,`queryDelete`,()=>r.deleteMany(e)):void 0,source:()=>w(t,`query`,()=>n.getAll(t))}:r)},async[Symbol.asyncDispose](){await I.dispose()},async update(e,t,n,r){return F(),w(e,`update`,()=>k.update(e,t,n,r))},async upsert(e,t,n,r){return F(),w(e,`upsert`,()=>k.upsert(e,t,n,r))},watch(e,t){return F(),m(t=>d.observe(e,t),t?.mode??`latest`,t?.signal)}};return I}function N(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error??new r(`IndexedDB request failed`))})}function P(e,t,n){return new r(`${t} on "${e}"${n instanceof Error&&n.message?`: ${n.message}`:``}`,{cause:n})}function F(e,t,n){return new Promise((r,i)=>{let a,o;Promise.resolve().then(n).then(e=>{a=e}).catch(t=>{o=t;try{e.abort()}catch{}});let s=(e,n=`transaction failed`)=>{o instanceof Error?i(o):i(P(t,n,o??e))};e.oncomplete=()=>{if(o){s(void 0);return}r(a)},e.onerror=()=>i(P(t,`transaction error`,e.error)),e.onabort=()=>s(e.error,`transaction aborted`)})}async function I(e,t){let n=await N(e.getAll()),r=[];for(let e of n){let n=t(e);n!==void 0&&r.push(n)}return r}async function L(e,t,n,r){let i=e.index(t),a=n.type===`eq`?IDBKeyRange.only(n.value):n.type===`between`?IDBKeyRange.bound(n.lower,n.upper):IDBKeyRange.bound(n.prefix,n.prefix+``),o=await N(i.getAll(a)),s=[];for(let e of o){let t=r(e);t!==void 0&&s.push(t)}return s}async function R(e,t,n){if(t.type===`eq`){let r=await z(e,t.value,n);return r===void 0?[]:[r]}let r=t.type===`between`?IDBKeyRange.bound(t.lower,t.upper):IDBKeyRange.bound(t.prefix,t.prefix+``),i=await N(e.getAll(r)),a=[];for(let e of i){let t=n(e);t!==void 0&&a.push(t)}return a}async function z(e,t,n){let r=await N(e.get(t));if(r!=null)return n(r)}async function B(e,t,n){return await z(e,t,n)!==void 0}async function V(e,t,n){let r=await B(e,t,n);return await N(e.delete(t)),r}async function H(e,t,n){return t.length===0?0:(await Promise.all(t.map(t=>V(e,t,n)))).filter(Boolean).length}async function U(e,t,n,r,i){await N(e.put(r(n,i),t))}function W(e,t){return new Promise((n,i)=>{let a=0,o=e.openCursor();o.onerror=()=>i(o.error??new r(`IndexedDB cursor failed during prune`)),o.onsuccess=()=>{let e=o.result;if(!e){n(a);return}let r=t.decode(e.value);(!r||S(r.expiresAt))&&(e.delete(),a+=1),e.continue()}})}function G(e,t){return{[Symbol.asyncIterator](){let n=e.openCursor(),i={type:`idle`},a=e=>{if(i.type===`waiting`){let{resolve:t}=i;i=e.done?{type:`done`}:{type:`idle`},t(e)}else i={result:e,type:`buffered`}};return n.onerror=()=>{let e=n.error??new r(`IndexedDB cursor iteration failed`);if(i.type===`waiting`){let{reject:t}=i;i={type:`done`},t(e)}else i={error:e,type:`error`}},n.onsuccess=()=>{let e=n.result;if(!e){a({done:!0,value:void 0});return}let r=t(e.value);e.continue(),r!==void 0&&a({done:!1,value:r})},{next(){if(i.type===`error`){let{error:e}=i;return i={type:`done`},Promise.reject(e)}if(i.type===`buffered`){let{result:e}=i;return i=e.done?{type:`done`}:{type:`idle`},Promise.resolve(e)}return i.type===`done`?Promise.resolve({done:!0,value:void 0}):new Promise((e,t)=>{i={reject:t,resolve:e,type:`waiting`}})},return(e){return i.type===`waiting`&&i.resolve({done:!0,value:void 0}),i={type:`done`},Promise.resolve({done:!0,value:e})},throw(e){return i.type===`waiting`&&i.reject(e),i={type:`done`},Promise.reject(e)}}}}}function K(e,t,n,r){let i=e=>t.objectStore(e);return{clear:async e=>{await N(i(e).clear())},count:async e=>(await N(i(e).getAll())).filter(e=>n(e)!==void 0).length,delete:(e,t)=>V(i(e),t,n),deleteMany:(e,t)=>H(i(e),t,n),get:(e,t)=>z(i(e),t,n),getAll:e=>I(i(e),n),getByIndexRange:(e,t,r)=>L(i(e),t,r,n),getByKeyRange:(e,t)=>R(i(e),t,n),getMany:(e,t)=>Promise.all(t.map(t=>z(i(e),t,n))),has:(e,t)=>B(i(e),t,n),pruneExpiredInTable:e=>W(i(e),{decode:n,encode:r}),put(t,n,a){return U(i(t),g(e,t,n),n,r,a)},putAll(t,n,a){return Promise.all(n.map(n=>U(i(t),g(e,t,n),n,r,a))).then(()=>void 0)}}}function q(e){let{codec:t=T,logger:n,migrate:a,name:o,onMetrics:c,schema:l,signals:u,validators:d,version:f=1}=e;if(!Number.isInteger(f)||f<1)throw new r(`createIndexedDB: version must be a positive integer, got ${String(f)}`);let p=e=>{let n=t.decode(e);if(n&&!S(n.expiresAt))return n.value},m=(e,n)=>{let r=n===void 0?void 0:Date.now()+n;return t.encode(e,r)},h=typeof BroadcastChannel<`u`?new BroadcastChannel(`vault:${o}`):void 0;!h&&n&&n.error(`[vault] BroadcastChannel unavailable — cross-tab sync disabled for "${o}"`);let _=null,v=null,y=!1,b=(e,t)=>{for(let[n,r]of Object.entries(l)){let i;i=e.objectStoreNames.contains(n)?t.objectStore(n):e.createObjectStore(n);let a=r.indexes??[];for(let e of a)i.indexNames.contains(e)||i.createIndex(e,`value.${e}`)}},x=async()=>(v||=new Promise((e,t)=>{let n=indexedDB.open(o,f);n.onupgradeneeded=e=>{let r=n.result,i=n.transaction;if(a)try{a({db:r,newVersion:e.newVersion??null,oldVersion:e.oldVersion,tx:i})}catch(e){try{i.abort()}catch{}t(new s(`migration failed for "${o}"`,{cause:e}));return}b(r,i)},n.onsuccess=()=>{if(y){n.result.close(),e();return}let t=n.result;t.onversionchange=()=>{t.close(),_=null,v=null},_=t,e()},n.onerror=()=>{v=null,t(new r(`failed to open "${o}"`,{cause:n.error}))}}),v),C=async(e,t,n)=>{if(y||(_||await x(),!_))throw new i(`"${o}" is disposed`);let r=String(e),a=_.transaction(r,t);return F(a,`${o}/${r}`,()=>n(a.objectStore(r)))},w=async()=>{if(y||(_||await x(),!_))throw new i(`"${o}" is disposed`);return _},E=e=>{h?.postMessage({table:String(e)})},D={clear:e=>C(e,`readwrite`,e=>N(e.clear()).then(()=>void 0)),count:e=>C(e,`readonly`,async e=>(await N(e.getAll())).filter(e=>p(e)!==void 0).length),delete:(e,t)=>C(e,`readwrite`,e=>V(e,t,p)),deleteMany:(e,t)=>t.length===0?Promise.resolve(0):C(e,`readwrite`,e=>H(e,t,p)),async dispose(){y=!0,h?.close(),v&&await v.catch(()=>{}),_?.close(),_=null,v=null},get:(e,t)=>C(e,`readonly`,e=>z(e,t,p)),getAll:e=>C(e,`readonly`,e=>I(e,p)),getAllKeys:e=>C(e,`readonly`,async t=>{let n=await I(t,p),r=l[e].key;return n.map(e=>e[r])}),getByIndexRange:(e,t,n)=>C(e,`readonly`,e=>L(e,t,n,p)),getByKeyRange:(e,t)=>C(e,`readonly`,e=>R(e,t,p)),getMany:(e,t)=>t.length===0?Promise.resolve([]):C(e,`readonly`,e=>Promise.all(t.map(t=>z(e,t,p)))),getRawCount:e=>C(e,`readonly`,e=>N(e.count())),has:(e,t)=>C(e,`readonly`,e=>B(e,t,p)),async pruneAllExpired(){let e=await w(),n=Object.keys(l),r=e.transaction(n,`readwrite`),i=await F(r,`${o}/pruneAll`,()=>Promise.all(n.map(async e=>[e,await W(r.objectStore(e),t)])));return Object.fromEntries(i)},pruneExpiredInTable:e=>C(e,`readwrite`,e=>W(e,t)),put(e,t,n){let r=g(l,e,t);return C(e,`readwrite`,e=>U(e,r,t,m,n))},putAll(e,t,n){return C(e,`readwrite`,r=>Promise.all(t.map(t=>U(r,g(l,e,t),t,m,n))).then(()=>void 0))}},O=async(e,t,n,r)=>{let i=(await w()).transaction([...e],`readwrite`),a=new Set,s=K(l,i,p,m),c=new Set(e),u=j(l,s,e=>a.add(e),r,c),d=await F(i,o,()=>t(u));for(let e of a)n(e);return d};return{...M(l,D,{buildBatch:({notifyMutation:e,validate:t})=>(n,r)=>O(n,r,e,t),logger:n,onCrossTabMessage(e){if(h)return h.onmessage=t=>{let n=t.data?.table;!n||!Object.hasOwn(l,n)||e(n)},()=>{h.onmessage=null}},onMetrics:c,onMutation:E,schema:l,signals:u,validators:d}),iterate(e){if(y)throw new i(`"${o}" is disposed`);let t=async()=>{if(_||await x(),!_||y)throw new i(`"${o}" is disposed`);return G(_.transaction(String(e),`readonly`).objectStore(String(e)),p)},n,r=()=>t().then(e=>(n=e[Symbol.asyncIterator](),n));return{[Symbol.asyncIterator](){return{next(){return n?n.next():r().then(e=>e.next())},return(e){return n?n.return?.(e)??Promise.resolve({done:!0,value:e}):Promise.resolve({done:!0,value:e})},throw(e){return n?n.throw?.(e)??Promise.reject(e):Promise.reject(e)}}}}}}}function J(e){return function(t){if(typeof t!=`object`||!t)return!1;let n=t;if(typeof n.table!=`string`||typeof n.type!=`string`||n.table===`__proto__`||n.table===`constructor`||n.table===`prototype`)return!1;switch(n.type){case`clear`:return!0;case`delete`:return typeof n.key==`string`;case`deleteMany`:return Array.isArray(n.keys)&&n.keys.every(e=>typeof e==`string`);case`put`:return typeof n.key==`string`&&e.decode(n.stored)!==void 0;case`putAll`:return Array.isArray(n.entries)&&n.entries.every(t=>typeof t==`object`&&!!t&&typeof t.key==`string`&&e.decode(t.stored)!==void 0);default:return!1}}}function Y(e){let{codec:t=T,logger:n,name:r,onMetrics:a,schema:o,signals:s,validators:c}=e,l=new Map(Object.keys(o).map(e=>[e,new Map])),u=e=>l.get(e),d=J(t),f=r!==void 0&&typeof BroadcastChannel<`u`?new BroadcastChannel(`vault-memory:${r}`):void 0,p=M(o,{async clear(e){u(e).clear(),f?.postMessage({table:e,type:`clear`})},async count(e){let n=u(e),r=0,i=[];for(let[e,a]of n){let n=t.decode(a);!n||S(n.expiresAt)?i.push(e):r+=1}for(let e of i)n.delete(e);return r},async delete(e,n){let r=u(e),i=r.get(String(n));if(!i)return!1;let a=t.decode(i),o=a!==void 0&&!S(a.expiresAt);return r.delete(String(n)),o&&f?.postMessage({key:String(n),table:e,type:`delete`}),o},async deleteMany(e,n){let r=u(e),i=[];for(let e of n){let n=String(e),a=r.get(n);if(a){let e=t.decode(a);e!==void 0&&!S(e.expiresAt)&&i.push(n),r.delete(n)}}return i.length>0&&f?.postMessage({keys:i,table:e,type:`deleteMany`}),i.length},dispose:f?async()=>f.close():void 0,async get(e,n){let r=u(e),i=r.get(String(n));if(!i)return;let a=t.decode(i);if(!a||S(a.expiresAt)){r.delete(String(n));return}return a.value},async getAll(e){let n=u(e),r=[],i=[];for(let[e,a]of n){let n=t.decode(a);!n||S(n.expiresAt)?i.push(e):r.push(n.value)}for(let e of i)n.delete(e);return r},async getRawCount(e){return u(e).size},async has(e,n){let r=u(e),i=r.get(String(n));if(!i)return!1;let a=t.decode(i),o=a!==void 0&&!S(a.expiresAt);return o||r.delete(String(n)),o},async pruneExpiredInTable(e){let n=u(e),r=0;for(let[e,i]of n){let a=t.decode(i);(!a||S(a.expiresAt))&&(n.delete(e),r+=1)}return r},async put(e,n,r){let i=String(g(o,e,n)),a=r===void 0?void 0:Date.now()+r,s=t.encode(n,a);u(e).set(i,s),f?.postMessage({key:i,stored:s,table:e,type:`put`})},async putAll(e,n,r){let i=u(e),a=[],s=r===void 0?void 0:Date.now()+r;for(let r of n){let n=String(g(o,e,r)),c=t.encode(r,s);i.set(n,c),a.push({key:n,stored:c})}a.length>0&&f?.postMessage({entries:a,table:e,type:`putAll`})}},{logger:n,onCrossTabMessage:f?e=>(f.onmessage=n=>{let r;try{if(!d(n.data))return;r=n.data}catch{return}let i=l.get(r.table);if(!i)return;let a=c?.[r.table],s=o[r.table]?.key,u=(e,n)=>{try{let r=t.decode(n);if(!r)return!1;let o=a?a.parse(r.value):r.value;if(s&&String(o[s])!==e)return!1;let c=r.expiresAt===void 0?{value:o}:{expiresAt:r.expiresAt,value:o};return i.set(e,c),!0}catch{return!1}};switch(r.type){case`clear`:i.clear(),e(r.table);return;case`delete`:i.delete(r.key),e(r.table);return;case`deleteMany`:for(let e of r.keys)i.delete(e);e(r.table);return;case`put`:u(r.key,r.stored)&&e(r.table);return;case`putAll`:{let t=!1;for(let{key:e,stored:n}of r.entries)u(e,n)&&(t=!0);t&&e(r.table)}return}},()=>{f.onmessage=null}):void 0,onMetrics:a,schema:o,signals:s,validators:c});return{...p,iterate(e){let n=u(String(e));return{async*[Symbol.asyncIterator](){if(p.disposed)throw new i;let e=[];for(let[r,i]of n){let n=t.decode(i);!n||S(n.expiresAt)?e.push(r):yield n.value}for(let t of e)n.delete(t)}}}}}var X=new Set([`QuotaExceededError`,`NS_ERROR_DOM_QUOTA_REACHED`]);function Z(e){let{codec:t=T,getStorage:n,logger:i,name:a,onMetrics:s,onQuotaExceeded:c,schema:p,signals:m,storageLabel:h,validators:_}=e,v;try{v=n()}catch(e){throw new r(`${h} is not available in this environment (private browsing or sandboxed iframe?)`,{cause:e})}let y=()=>v,b=new Map(Object.keys(p).map(e=>[e,d(a,e)])),x=e=>{let t=b.get(e);if(!t)throw new r(`table "${e}" not in schema`);return t},C=(e,t,n)=>{try{y().setItem(t,JSON.stringify(n))}catch(t){if(t instanceof DOMException&&X.has(t.name)){let n=new o(`${h} quota exceeded while writing record`,{cause:t});if(c?.(e,n)===`ignore`)return;throw n}throw t}},w=new Set;(()=>{let e=l(a);for(let t=0;t<v.length;t++){let n=v.key(t);n!==null&&n.startsWith(e)&&w.add(n)}})();let E=e=>{let n=y().getItem(e);if(n)try{let e=t.decode(JSON.parse(n));return!e||S(e.expiresAt)?void 0:e.value}catch{return}},D=e=>{y().removeItem(e),w.delete(e)};return M(p,{async clear(e){let t=y(),n=x(e),r=[];for(let e of w)e.startsWith(n)&&r.push(e);for(let e of r)t.removeItem(e),w.delete(e)},async count(e){let t=x(e),n=[],r=0;for(let e of w)e.startsWith(t)&&(E(e)===void 0?n.push(e):r+=1);for(let e of n)D(e);return r},async delete(e,t){let n=u(a,e,String(t));return E(n)===void 0?(w.has(n)&&D(n),!1):(D(n),!0)},async deleteMany(e,t){let n=0;for(let r of t){let t=u(a,e,String(r));E(t)===void 0?w.has(t)&&D(t):(D(t),n+=1)}return n},async get(e,t){let n=u(a,e,String(t)),r=E(n);return r===void 0&&w.has(n)&&D(n),r},async getAll(e){let t=[],n=[],r=x(e);for(let e of w){if(!e.startsWith(r))continue;let i=E(e);if(i===void 0){n.push(e);continue}t.push(i)}for(let e of n)D(e);return t},async getAllKeys(e){let t=x(e),n=[],r=[];for(let i of w){if(!i.startsWith(t))continue;let a=E(i);if(a===void 0){r.push(i);continue}n.push(a[p[e].key])}for(let e of r)D(e);return n},async getRawCount(e){let t=x(e),n=0;for(let e of w)e.startsWith(t)&&(n+=1);return n},async has(e,t){let n=u(a,e,String(t)),r=E(n);return r===void 0&&w.has(n)&&D(n),r!==void 0},async pruneExpiredInTable(e){let n=x(e),r=[];for(let e of w){if(!e.startsWith(n))continue;let i=y().getItem(e);if(i===null){r.push(e);continue}try{let n=t.decode(JSON.parse(i));(!n||S(n.expiresAt))&&r.push(e)}catch{r.push(e)}}for(let e of r)D(e);return r.length},async put(e,n,r){let i=u(a,e,String(g(p,e,n))),o=r===void 0?void 0:Date.now()+r;C(e,i,t.encode(n,o)),w.add(i)},async putAll(e,n,r){let i=r===void 0?void 0:Date.now()+r;for(let r of n){let n=u(a,e,String(g(p,e,r)));C(e,n,t.encode(r,i)),w.add(n)}}},{logger:i,onCrossTabMessage(e){if(typeof window>`u`||typeof window.addEventListener!=`function`)return;let t=t=>{if(t.storageArea&&t.storageArea!==v)return;if(t.key===null){w.clear();for(let t of Object.keys(p))e(t);return}let n=f(a,t.key);n&&Object.hasOwn(p,n)&&(t.newValue===null?w.delete(t.key):w.add(t.key),e(n))};return window.addEventListener(`storage`,t),()=>window.removeEventListener(`storage`,t)},onMetrics:s,schema:p,signals:m,validators:_})}function Q(e){return Z({...e,getStorage:()=>typeof window<`u`?window.localStorage:localStorage,storageLabel:`localStorage`})}function $(e){return Z({...e,getStorage:()=>typeof window<`u`?window.sessionStorage:sessionStorage,storageLabel:`sessionStorage`})}function ee(e){return({db:t,tx:n})=>{for(let r of e)switch(r.type){case`addIndex`:{let e=n.objectStore(r.table);e.indexNames.contains(r.field)||e.createIndex(r.field,`value.${r.field}`);break}case`addTable`:t.objectStoreNames.contains(r.name)||t.createObjectStore(r.name);break;case`removeIndex`:{let e=n.objectStore(r.table);e.indexNames.contains(r.field)&&e.deleteIndex(r.field);break}case`removeTable`:t.objectStoreNames.contains(r.name)&&t.deleteObjectStore(r.name);break}}}function te(e,n){if(!Number.isFinite(n.interval)||n.interval<=0)throw new r(`scheduleExpiredPrune: interval must be a finite positive number`);let a=!0,o=()=>{a=!1,clearInterval(s)};n.signal?.addEventListener(`abort`,o,{once:!0});let s=setInterval(()=>{a&&e.pruneExpired().catch(e=>{e instanceof i?(a=!1,clearInterval(s)):n.onError?n.onError(e):t(`scheduleExpiredPrune: pruneExpired() threw — pass onError to handle this. ${String(e)}`)})},n.interval);return o}function ne(e){let t=e[Symbol.asyncIterator]();return new ReadableStream({async cancel(){await t.return?.()},async pull(e){try{let{done:n,value:r}=await t.next();n?e.close():e.enqueue(r)}catch(t){e.error(t)}}})}function re(e,t){if(e.length===0)throw new r(`createVersionedCodec: at least one version entry is required`);let n=new Map;for(let t of e){if(!Number.isInteger(t.version)||t.version<0)throw new r(`createVersionedCodec: version must be a non-negative integer, got ${String(t.version)}`);if(n.has(t.version))throw new r(`createVersionedCodec: duplicate version ${String(t.version)}`);n.set(t.version,t.codec)}if(!n.has(t))throw new r(`createVersionedCodec: currentVersion ${String(t)} is not listed in versions`);let i=n.get(t);return{decode(e){if(typeof e!=`object`||!e||!(`__v`in e))return;let t=e,r=t.__v;if(typeof r!=`number`)return;let i=n.get(r);if(i)return i.decode(t.__d)},encode(e,n){return{__d:i.encode(e,n),__v:t}}}}function ie(e){function t(e){return{...e,index:n=>{let i=e.indexes??[];if(i.includes(n))throw new r(`table index "${n}" is already registered`);return t({...e,indexes:[...i,n]})},ttl:n=>(C(n,`table.ttl`),t({...e,defaultTtl:n}))}}return t({key:e})}exports.VaultDisposedError=i,exports.VaultError=r,exports.VaultMigrationError=s,exports.VaultQuotaError=o,exports.VaultScopeError=a,exports.createIndexedDB=q,exports.createLocalStorage=Q,exports.createMemory=Y,exports.createSessionStorage=$,exports.createVersionedCodec=re,exports.defaultCodec=T,exports.defineMigration=ee,exports.isExpired=S,exports.scheduleExpiredPrune=te,exports.table=ie,exports.toReadableStream=ne,exports.ttl=x;
|
|
2
|
+
//# sourceMappingURL=vault.cjs.map
|