@asaidimu/utils-database 3.1.9 → 3.1.10

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/index.d.mts DELETED
@@ -1,989 +0,0 @@
1
- import { QueryFilter, PaginationOptions } from '@asaidimu/query';
2
- import { IndexDefinition, SchemaChange, DataTransform, PredicateMap, SchemaDefinition } from '@asaidimu/anansi';
3
- import { StandardSchemaV1 } from '@standard-schema/spec';
4
-
5
- /**
6
- * Buffers write operations across one or more stores and commits them atomically.
7
- *
8
- * ## How atomicity works
9
- *
10
- * ### IndexedDB stores (same database)
11
- * At commit time, TransactionContext collects the names of every IDB store that
12
- * received operations, then opens a **single** `IDBTransaction` spanning all of
13
- * them via `ConnectionManager.openTransaction`. Each store's `executeInTransaction`
14
- * receives that shared transaction object and performs its writes against it
15
- * without opening a new transaction of its own. IDB commits or aborts the
16
- * entire multi-store transaction as one unit.
17
- *
18
- * ### MemoryStore
19
- * MemoryStore's `executeInTransaction` receives `null` for the shared transaction.
20
- * It applies ops against an internal staging map and returns. If a later
21
- * participant fails, TransactionContext calls `rollbackMemory` on each
22
- * MemoryStore that already applied its staged ops. MemoryStore restores its
23
- * pre-transaction snapshot.
24
- *
25
- * ### Mixed (IDB + Memory in the same transaction)
26
- * All IDB stores are committed first as a single atomic IDB transaction, then
27
- * each MemoryStore is committed. If a MemoryStore fails after IDB has already
28
- * committed, the IDB side cannot be rolled back — this is an inherent limitation
29
- * of mixing two different storage engines. In practice the schema store is
30
- * always MemoryStore-or-IDB consistently, so mixed transactions should not arise
31
- * in normal usage.
32
- */
33
- declare class TransactionContext {
34
- readonly id: string;
35
- /**
36
- * Flat list of every operation staged so far, in the order they were added.
37
- * We keep the store reference alongside the op so commit() can group them.
38
- */
39
- private staged;
40
- private done;
41
- constructor();
42
- /**
43
- * Stages a single write operation against a store.
44
- * Does NOT touch the store — no I/O happens until commit().
45
- */
46
- addOp<T extends Record<string, any>>(store: Store<T>, type: "put" | "delete" | "add", data: any): Promise<void>;
47
- /**
48
- * Commits all staged operations atomically.
49
- *
50
- * For IDB stores: opens one shared IDBTransaction across all participating
51
- * stores, then dispatches ops to each store's executeInTransaction.
52
- * For MemoryStores: dispatches sequentially; rolls back on failure.
53
- */
54
- commit(): Promise<void>;
55
- /**
56
- * Discards all staged operations. No I/O has occurred so there is nothing
57
- * to undo — we simply clear the buffer.
58
- */
59
- rollback(): void;
60
- /**
61
- * Opens ONE IDBTransaction across all participating IDB stores and lets
62
- * each store execute its ops against the shared transaction handle.
63
- *
64
- * We obtain the IDBDatabase from the first store (they all share the same
65
- * ConnectionManager / database) and open the transaction ourselves so that
66
- * the commit/abort lifecycle belongs entirely to this method.
67
- */
68
- private commitIDB;
69
- /**
70
- * Commits MemoryStore groups sequentially.
71
- * Maintains a list of stores that have already applied their ops; if any
72
- * store throws, all previously-applied stores are rolled back via the
73
- * store-level `_rollbackMemory(snapshot)` escape hatch.
74
- */
75
- private commitMemory;
76
- completed(): boolean;
77
- }
78
-
79
- declare const DEFAULT_KEYPATH = "$id";
80
- interface CursorPaginationOptions {
81
- limit: number;
82
- cursor?: any;
83
- direction?: "forward" | "backward";
84
- }
85
- interface CursorCallbackResult<T> {
86
- value: T | null;
87
- done: boolean;
88
- offset?: number;
89
- }
90
- /**
91
- * Callback function for cursor iteration over store records.
92
- *
93
- * @template T - The type of records stored.
94
- * @param value - The current record value (cloned, not a live reference).
95
- * @param key - The key (ID) of the current record.
96
- * @param cursor - The underlying cursor object (implementation-specific; may be `null` in memory adapters).
97
- * @returns A promise resolving to an object indicating whether iteration should stop,
98
- * and an optional offset to advance.
99
- */
100
- type CursorCallback<T> = (value: T, key: string | number, cursor: any) => Promise<CursorCallbackResult<T>>;
101
- /**
102
- * A generic representation of a key range, replacing the browser-specific IDBKeyRange.
103
- */
104
- interface StoreKeyRange {
105
- lower?: any;
106
- upper?: any;
107
- lowerOpen?: boolean;
108
- upperOpen?: boolean;
109
- }
110
- /**
111
- * A single buffered operation staged inside a TransactionContext.
112
- * Kept intentionally minimal — the context only needs to know what to
113
- * replay against a store during commit.
114
- */
115
- type BufferedOperation<T> = {
116
- type: "add" | "put";
117
- data: T | T[];
118
- } | {
119
- type: "delete";
120
- data: string | number | (string | number)[];
121
- };
122
- /**
123
- * Storage adapter interface for a single object store (collection).
124
- *
125
- * Stores own their indexes. Index lifecycle (create, drop) and index-aware reads
126
- * (findByIndex) are part of this contract so that both MemoryStore and IndexedDBStore
127
- * implement them natively — MemoryStore via in-memory index maps, IndexedDB via its
128
- * native index mechanism.
129
- *
130
- * @template T - The type of objects stored. Must include the key path property.
131
- */
132
- interface Store<T extends Record<string, any> = Record<string, any>> {
133
- /**
134
- * Returns the name of this store (the IDB object store / collection name).
135
- * Used by TransactionContext to group operations and open a correctly-scoped
136
- * multi-store IDB transaction at commit time.
137
- */
138
- name(): string;
139
- /**
140
- * Opens the store, ensuring underlying storage structures exist.
141
- */
142
- open(): Promise<void>;
143
- /**
144
- * Adds one or more records to the store.
145
- * If a record does not have a value for the store's key path, an automatic
146
- * key may be assigned. Throws if a record with the same key already exists.
147
- */
148
- add(data: T | T[]): Promise<string | number | (string | number)[]>;
149
- /**
150
- * Removes all records from the store without destroying index structures.
151
- */
152
- clear(): Promise<void>;
153
- /**
154
- * Returns the total number of records in the store.
155
- */
156
- count(): Promise<number>;
157
- /**
158
- * Deletes one or more records by their keys.
159
- */
160
- delete(id: string | number | (string | number)[]): Promise<void>;
161
- /**
162
- * Retrieves a single record by its primary key.
163
- */
164
- getById(id: string | number): Promise<T | undefined>;
165
- /**
166
- * Retrieves the first record matching an exact index key (point lookup).
167
- * Useful for unique indexes — returns the single matching record or undefined.
168
- *
169
- * @param indexName - The name of the index to query.
170
- * @param key - The exact key value to look up.
171
- */
172
- getByIndex(indexName: string, key: any): Promise<T | undefined>;
173
- /**
174
- * Retrieves all records from a named index, optionally filtered by a key range.
175
- * Use this for range scans over an index (e.g. all records where age >= 18).
176
- *
177
- * @param indexName - The name of the index to query.
178
- * @param keyRange - Optional range to filter results.
179
- */
180
- getByKeyRange(indexName: string, keyRange?: StoreKeyRange): Promise<T[]>;
181
- /**
182
- * Retrieves all records from the store without index involvement.
183
- */
184
- getAll(): Promise<T[]>;
185
- /**
186
- * Inserts or replaces a record. Validates OCC if a record with the same key exists.
187
- */
188
- put(data: T): Promise<string | number>;
189
- /**
190
- * Iterates over records using a cursor, allowing early termination and skipping.
191
- *
192
- * @param callback - Invoked for each record; return `{ done: true }` to stop,
193
- * `{ offset: n }` to skip ahead n records.
194
- * @param direction - Iteration order.
195
- * @param keyRange - Optional range to restrict iteration.
196
- */
197
- cursor(callback: CursorCallback<T>, direction?: "forward" | "backward", keyRange?: StoreKeyRange): Promise<T | null>;
198
- /**
199
- * Executes a batch of write operations atomically within this store.
200
- * All operations succeed or fail together.
201
- *
202
- * Used for standalone (single-store) atomic writes. For cross-store atomicity,
203
- * use executeInTransaction instead.
204
- */
205
- batch(operations: Array<{
206
- type: "add" | "put";
207
- data: T | T[];
208
- } | {
209
- type: "delete";
210
- data: string | number | (string | number)[];
211
- }>): Promise<void>;
212
- /**
213
- * Registers a new index on the store. Idempotent — no-op if the index already exists.
214
- * For IndexedDB, this triggers a database version upgrade.
215
- * For MemoryStore, this builds the index map from existing records.
216
- *
217
- * @param definition - The full index definition from the schema.
218
- */
219
- createIndex(definition: IndexDefinition): Promise<void>;
220
- /**
221
- * Removes a named index from the store.
222
- * For IndexedDB, this triggers a database version upgrade.
223
- * For MemoryStore, this drops the in-memory index map.
224
- *
225
- * @param name - The index name as declared in IndexDefinition.name.
226
- */
227
- dropIndex(name: string): Promise<void>;
228
- /**
229
- * Returns all records matching an exact index key.
230
- * Unlike getByIndex (which returns only the first match), this returns all matches —
231
- * essential for non-unique indexes where multiple records share the same indexed value.
232
- *
233
- * @param indexName - The name of the index to query.
234
- * @param value - The exact value to look up.
235
- */
236
- findByIndex(indexName: string, value: any): Promise<T[]>;
237
- /**
238
- * Executes a set of buffered operations as part of a cross-store atomic transaction.
239
- *
240
- * For IndexedDBStore: `sharedTx` is the single IDBTransaction opened across all
241
- * participating stores. Operations are applied directly to `sharedTx.objectStore(name)`
242
- * without opening a new transaction — IDB commits or aborts the whole thing atomically.
243
- *
244
- * For MemoryStore: `sharedTx` is null. The store applies ops against its own staging
245
- * area. The caller (TransactionContext) is responsible for coordinating rollback across
246
- * all MemoryStores if any participant fails.
247
- *
248
- * This method must NOT open, commit, or abort any transaction itself.
249
- *
250
- * @param ops - The buffered operations to apply.
251
- * @param sharedTx - The shared IDBTransaction (IndexedDB only), or null (MemoryStore).
252
- */
253
- executeInTransaction(ops: BufferedOperation<T>[], sharedTx: IDBTransaction | null): Promise<void>;
254
- }
255
- interface Collection<T> {
256
- /**
257
- * Finds a single document matching the query.
258
- */
259
- find: (query: QueryFilter<T>) => Promise<Document<T> | null>;
260
- /**
261
- * Lists documents with pagination. Returns an AsyncIterator so consumers can
262
- * wrap it in their own iteration protocol (e.g. for-await-of via AsyncIterable).
263
- */
264
- list: (query: PaginationOptions) => Promise<AsyncIterator<Document<T>[]>>;
265
- /**
266
- * Filters all documents matching the query.
267
- */
268
- filter: (query: QueryFilter<T>) => Promise<Document<T>[]>;
269
- /**
270
- * Creates a new document in the collection.
271
- *
272
- * When a TransactionContext is provided the initial store.add is buffered into
273
- * the transaction rather than written immediately. The document is returned in
274
- * its fully initialised in-memory state regardless — callers can use it before
275
- * the transaction commits.
276
- *
277
- * @param initial - The initial data for the document.
278
- * @param tx - Optional transaction to buffer the write into.
279
- */
280
- create: (initial: T, tx?: TransactionContext) => Promise<Document<T>>;
281
- /**
282
- * Updates all documents matching the query with the provided partial data.
283
- * Returns the number of documents updated.
284
- */
285
- update: (query: QueryFilter<T>, data: Partial<T>, tx?: TransactionContext) => Promise<number>;
286
- /**
287
- * Deletes all documents matching the query.
288
- * Returns the number of documents deleted.
289
- */
290
- delete: (query: QueryFilter<T>, tx?: TransactionContext) => Promise<number>;
291
- /**
292
- * Subscribes to collection-level events.
293
- */
294
- subscribe: (event: CollectionEventType | TelemetryEventType, callback: (event: CollectionEvent<T> | TelemetryEvent) => void) => () => void;
295
- /**
296
- * Validates data against the collection's schema.
297
- */
298
- validate(data: Record<string, any>): Promise<{
299
- value?: any;
300
- issues: Array<{
301
- message: string;
302
- path: Array<string>;
303
- }>;
304
- }>;
305
- invalidate(): void;
306
- }
307
- /**
308
- * Event payload for Collection events.
309
- */
310
- type CollectionEventType = "document:create" | "collection:read" | "migration:start" | "migration:end";
311
- type CollectionEvent<T> = {
312
- type: CollectionEventType;
313
- document?: T;
314
- model?: string;
315
- method?: keyof Collection<T>;
316
- metadata?: Record<string, unknown>;
317
- timestamp: number;
318
- };
319
- interface Database {
320
- /**
321
- * Opens an existing collection by name.
322
- */
323
- collection: <T>(schemaName: string) => Promise<Collection<T>>;
324
- /**
325
- * Creates a new collection from a schema definition.
326
- */
327
- createCollection: <T>(schema: SchemaDefinition) => Promise<Collection<T>>;
328
- /**
329
- * Deletes a collection and its schema record.
330
- */
331
- deleteCollection: (schemaName: string) => Promise<boolean>;
332
- /**
333
- * Updates an existing collection's schema record.
334
- */
335
- updateCollection: (schema: SchemaDefinition) => Promise<boolean>;
336
- /**
337
- * Migrates an existing collection's data and schema definition.
338
- * Processes data in a streaming fashion to avoid loading the full collection
339
- * into memory.
340
- */
341
- migrateCollection: <T>(name: string, opts: CollectionMigrationOptions, batchSize?: number) => Promise<Collection<T>>;
342
- /**
343
- * Executes a callback within a TransactionContext.
344
- * Writes buffered inside the callback are flushed atomically on commit.
345
- * If the callback throws, the buffer is discarded (no writes are flushed).
346
- */
347
- transaction: (callback: (tx: TransactionContext) => Promise<void>) => Promise<void>;
348
- /**
349
- * Subscribes to database-level events.
350
- */
351
- subscribe: (event: DatabaseEventType | "telemetry", callback: (event: DatabaseEvent | TelemetryEvent) => void) => () => void;
352
- /**
353
- * Releases in-memory references and event bus subscriptions.
354
- * Does not delete any persisted data.
355
- */
356
- close: () => void;
357
- clear: () => Promise<void>;
358
- /**
359
- * Ensures a collection exists; creates it if it doesn't. Idempotent.
360
- */
361
- ensureCollection: (schema: SchemaDefinition) => Promise<void>;
362
- /**
363
- * Ensures multiple collections exist; creates any that don't. Idempotent.
364
- */
365
- setupCollections: (schemas: SchemaDefinition[]) => Promise<void>;
366
- }
367
- type CollectionMigrationOptions = {
368
- changes: SchemaChange<any>[];
369
- description: string;
370
- rollback?: SchemaChange<any>[];
371
- transform?: string | DataTransform<any, any>;
372
- };
373
- type DatabaseEventType = "collection:create" | "collection:delete" | "collection:update" | "collection:read" | "migrate";
374
- type DatabaseEvent = {
375
- type: DatabaseEventType;
376
- schema?: SchemaDefinition | Partial<SchemaDefinition>;
377
- timestamp: number;
378
- };
379
- type DocumentMetadata = {
380
- $id?: string;
381
- $created?: string | Date;
382
- $updated?: string | Date;
383
- $version?: number;
384
- };
385
- type Document<T> = {
386
- readonly [K in keyof T]: T[K];
387
- } & DocumentMetadata & {
388
- read: () => Promise<T>;
389
- save: (tx?: TransactionContext) => Promise<boolean>;
390
- update: (props: Partial<T>, tx?: TransactionContext) => Promise<boolean>;
391
- delete: (tx?: TransactionContext) => Promise<boolean>;
392
- subscribe: (event: DocumentEventType | TelemetryEventType, callback: (event: DocumentEvent<T> | TelemetryEvent) => void) => () => void;
393
- state(): T;
394
- $metadata(): DocumentMetadata;
395
- };
396
- type DocumentEventType = "document:create" | "document:write" | "document:update" | "document:delete" | "document:read";
397
- type DocumentEvent<T> = {
398
- type: DocumentEventType;
399
- data?: Partial<T>;
400
- timestamp: number;
401
- };
402
- type TelemetryEventType = "telemetry";
403
- type TelemetryEvent = {
404
- type: TelemetryEventType;
405
- method: string;
406
- timestamp: number;
407
- source: any;
408
- metadata: {
409
- args: any[];
410
- performance: {
411
- durationMs: number;
412
- };
413
- source: {
414
- level: "database" | "collection" | "document";
415
- collection?: string;
416
- document?: string;
417
- };
418
- result?: {
419
- type: "array" | string;
420
- size?: number;
421
- };
422
- error: {
423
- message: string;
424
- name: string;
425
- stack?: string;
426
- } | null;
427
- };
428
- };
429
- interface DatabaseConfig {
430
- database: string;
431
- keyPath?: string;
432
- schemasStoreName?: string;
433
- enableTelemetry?: boolean;
434
- predicates?: PredicateMap;
435
- validate?: boolean;
436
- }
437
- type StoreConfig = DatabaseConfig & {
438
- collection: string;
439
- };
440
-
441
- /**
442
- * Internal structure for a single maintained index.
443
- */
444
- interface IndexEntry {
445
- definition: IndexDefinition;
446
- /** Maps a composite/single index key → set of record primary keys ($id) */
447
- map: Map<string, Set<string | number>>;
448
- }
449
- /**
450
- * Snapshot of the store's mutable state, used for cross-store transaction rollback.
451
- */
452
- interface MemoryStoreSnapshot {
453
- data: Map<string | number, Readonly<Record<string, any>>>;
454
- indexes: Map<string, IndexEntry>;
455
- nextId: number;
456
- }
457
- declare class MemoryStore<T extends Record<string, any>> implements Store<T> {
458
- private readonly storeName;
459
- private readonly keyPath;
460
- private data;
461
- private indexes;
462
- private nextId;
463
- /**
464
- * @param storeName - The logical name of this store (matches the collection name).
465
- * @param keyPath - The primary key field name (default: "$id").
466
- * @param indexDefs - Index definitions from the schema. The store will maintain
467
- * these indexes on every write operation.
468
- */
469
- constructor(storeName: string, keyPath?: string, indexDefs?: IndexDefinition[]);
470
- name(): string;
471
- open(): Promise<void>;
472
- /**
473
- * Returns a deep snapshot of the store's mutable state.
474
- * Called by TransactionContext immediately before executeInTransaction so
475
- * that if a later store in the same transaction fails, this store can be
476
- * fully restored via _rollbackMemory.
477
- *
478
- * Prefixed with underscore to signal it is an internal contract between
479
- * MemoryStore and TransactionContext — not part of the public Store API.
480
- */
481
- _snapshotMemory(): MemoryStoreSnapshot;
482
- /**
483
- * Restores the store to a previously snapshotted state.
484
- * Called by TransactionContext when a later participant in the same
485
- * cross-store transaction fails, requiring all already-applied stores to
486
- * be unwound.
487
- */
488
- _rollbackMemory(snapshot: MemoryStoreSnapshot): void;
489
- /**
490
- * Registers a new index. Idempotent — no-op if the name already exists.
491
- * Immediately indexes all existing records so the index is consistent.
492
- */
493
- createIndex(definition: IndexDefinition): Promise<void>;
494
- /**
495
- * Removes a named index. No-op if the index does not exist.
496
- */
497
- dropIndex(name: string): Promise<void>;
498
- /**
499
- * Returns the first record whose indexed value exactly matches `value`.
500
- * Intended for unique indexes — for non-unique indexes use findByIndex.
501
- */
502
- getByIndex(indexName: string, value: any): Promise<T | undefined>;
503
- /**
504
- * Returns all records whose indexed value exactly matches `value`.
505
- * O(k) where k is the result set size — avoids a full table scan.
506
- */
507
- findByIndex(indexName: string, value: any): Promise<T[]>;
508
- /**
509
- * Returns all records from a named index within an optional key range.
510
- */
511
- getByKeyRange(indexName: string, keyRange?: StoreKeyRange): Promise<T[]>;
512
- add(data: T | T[]): Promise<string | number | (string | number)[]>;
513
- put(data: T): Promise<string | number>;
514
- batch(operations: Array<{
515
- type: "add" | "put";
516
- data: T | T[];
517
- } | {
518
- type: "delete";
519
- data: string | number | (string | number)[];
520
- }>): Promise<void>;
521
- delete(id: string | number | (string | number)[]): Promise<void>;
522
- clear(): Promise<void>;
523
- /**
524
- * Applies buffered ops directly to the live data and index maps.
525
- *
526
- * `sharedTx` is always null for MemoryStore — there is no shared transaction
527
- * object. Atomicity across multiple MemoryStores is managed by the caller
528
- * (TransactionContext), which takes a snapshot via _snapshotMemory() before
529
- * calling this method and calls _rollbackMemory(snapshot) if a later store
530
- * in the same transaction fails.
531
- *
532
- * This method applies ops eagerly (no staging) because the snapshot already
533
- * guards against partial failure at the cross-store level.
534
- */
535
- executeInTransaction(ops: BufferedOperation<T>[], sharedTx: IDBTransaction | null): Promise<void>;
536
- getById(id: string | number): Promise<T | undefined>;
537
- getAll(): Promise<T[]>;
538
- count(): Promise<number>;
539
- cursor(callback: CursorCallback<T>, direction?: "forward" | "backward", keyRange?: StoreKeyRange): Promise<T | null>;
540
- private getKey;
541
- private clone;
542
- /**
543
- * Adds a document to all index maps. Skips indexes where the document
544
- * does not satisfy partial conditions or is missing indexed fields.
545
- */
546
- private indexDocument;
547
- private indexOne;
548
- /**
549
- * Removes a document from all index maps.
550
- */
551
- private unindexDocument;
552
- /**
553
- * Enforces unique index constraints before a write.
554
- * Throws CONFLICT if another record (other than `existing`) already holds the
555
- * same indexed value for any unique index.
556
- *
557
- * @param incoming - The record about to be written.
558
- * @param existing - The record currently stored at this key (undefined for new records).
559
- */
560
- private enforceUniqueIndexes;
561
- private isInKeyRange;
562
- }
563
-
564
- declare function createEphemeralStore<T extends Record<string, any>>(config: DatabaseConfig): MemoryStore<T>;
565
-
566
- /**
567
- * Manages the lifecycle and state of an IndexedDB connection, ensuring
568
- * schema stores and collections are created and upgraded safely.
569
- */
570
- declare class ConnectionManager {
571
- private readonly config;
572
- private connectionInitializer;
573
- private readonly schemasStoreName;
574
- constructor(config: DatabaseConfig);
575
- private openDatabase;
576
- /**
577
- * Bumps the database version and runs the provided upgrade callback.
578
- * The callback receives both the IDBDatabase and the active IDBTransaction
579
- * so callers can access existing object stores via tx.objectStore(name).
580
- *
581
- * Note: IDB only allows structural changes (createObjectStore, createIndex,
582
- * deleteIndex) inside an onupgradeneeded handler. This method is the single
583
- * entry point for all such changes.
584
- */
585
- private upgradeDatabase;
586
- /**
587
- * Ensures a collection object store exists, creating it (and its indexes) if absent.
588
- * Triggers an upgrade only when the store does not yet exist; if it already exists,
589
- * this is a fast no-op.
590
- *
591
- * @param collection - Name of the IDB object store.
592
- * @param keyPath - Primary key field (default: "$id").
593
- * @param indexes - Index definitions to create alongside the store.
594
- * These are only applied during the initial store creation.
595
- * Use createStoreIndex / dropStoreIndex for post-creation changes.
596
- */
597
- ensureStore(collection: string, keyPath?: string, indexes?: IndexDefinition[]): Promise<void>;
598
- /**
599
- * Adds a named index to an existing object store.
600
- * Triggers a database version upgrade.
601
- *
602
- * @param collection - The object store to add the index to.
603
- * @param definition - The index definition.
604
- */
605
- createStoreIndex(collection: string, definition: IndexDefinition): Promise<void>;
606
- /**
607
- * Removes a named index from an existing object store.
608
- * Triggers a database version upgrade.
609
- *
610
- * @param storeName - The object store to remove the index from.
611
- * @param indexName - The name of the index to remove.
612
- */
613
- dropStoreIndex(storeName: string, indexName: string): Promise<void>;
614
- /**
615
- * Retrieves or opens the active database connection.
616
- */
617
- getConnection: () => Promise<IDBDatabase>;
618
- /**
619
- * Opens a readwrite IDBTransaction spanning the given store names.
620
- *
621
- * This is the entry point for all cross-store atomic writes. The returned
622
- * transaction is NOT managed here — the caller (TransactionContext) owns the
623
- * commit/abort lifecycle by wiring oncomplete / onerror / onabort handlers.
624
- *
625
- * All named stores must already exist (i.e. ensureStore must have been called
626
- * for each before this point). Requesting a store that doesn't exist will cause
627
- * IDB to throw a DOMException synchronously when the transaction is opened.
628
- *
629
- * @param storeNames - Object store names to include in the transaction.
630
- * @param mode - IDB transaction mode (default: "readwrite").
631
- */
632
- openTransaction(storeNames: string[], mode?: IDBTransactionMode): Promise<IDBTransaction>;
633
- /**
634
- * Performs a version upgrade. Resets the internal initialiser so concurrent
635
- * callers wait for the upgraded connection rather than using the stale one.
636
- *
637
- * The callback receives both `db` (for creating new stores) and `tx`
638
- * (for accessing existing stores to add/remove indexes).
639
- */
640
- upgrade(upgradeLogic: (db: IDBDatabase, tx: IDBTransaction) => void): Promise<IDBDatabase>;
641
- /**
642
- * Closes the active connection and resets the initialiser.
643
- * Does not delete any persisted data.
644
- */
645
- close(): void;
646
- }
647
-
648
- declare class IndexedDBStore<T extends Record<string, any>> implements Store<T> {
649
- private readonly connectionManager;
650
- private readonly collection;
651
- private readonly keyPath;
652
- private readonly indexes;
653
- constructor(connectionManager: ConnectionManager, collection: string, keyPath?: string, indexes?: IndexDefinition[]);
654
- name(): string;
655
- /**
656
- * Internal escape hatch used by TransactionContext to obtain the shared
657
- * IDBDatabase so it can open a single multi-store IDBTransaction.
658
- *
659
- * Prefixed with underscore to signal that nothing outside of
660
- * TransactionContext should call this directly.
661
- */
662
- _getIDBConnection(): Promise<IDBDatabase>;
663
- /**
664
- * Ensures the underlying IDB object store (and its declared indexes) exist.
665
- * Safe to call multiple times — no-op if the store is already present.
666
- */
667
- open(): Promise<void>;
668
- /**
669
- * Adds a new index to the IDB object store. Triggers a database version upgrade.
670
- * Idempotent — no-op if the index already exists.
671
- */
672
- createIndex(definition: IndexDefinition): Promise<void>;
673
- /**
674
- * Removes a named index from the IDB object store. Triggers a version upgrade.
675
- * No-op if the index does not exist.
676
- */
677
- dropIndex(name: string): Promise<void>;
678
- /**
679
- * Returns the first record matching an exact index key.
680
- * Use for unique index point lookups.
681
- */
682
- getByIndex(indexName: string, key: any): Promise<T | undefined>;
683
- /**
684
- * Returns all records from a named index within an optional key range.
685
- */
686
- getByKeyRange(indexName: string, keyRange?: StoreKeyRange): Promise<T[]>;
687
- /**
688
- * Returns all records whose indexed value exactly matches `value`.
689
- * Unlike getByIndex, this uses index.getAll(IDBKeyRange.only(value)) so it
690
- * correctly returns multiple records for non-unique indexes.
691
- */
692
- findByIndex(indexName: string, value: any): Promise<T[]>;
693
- put(data: T): Promise<string | number>;
694
- add(data: T | T[]): Promise<string | number | (string | number)[]>;
695
- batch(operations: Array<{
696
- type: "add" | "put";
697
- data: T | T[];
698
- } | {
699
- type: "delete";
700
- data: string | number | (string | number)[];
701
- }>): Promise<void>;
702
- delete(id: string | number | (string | number)[]): Promise<void>;
703
- clear(): Promise<void>;
704
- /**
705
- * Executes buffered ops against a shared IDBTransaction opened by
706
- * TransactionContext. This method MUST NOT open, commit, or abort a
707
- * transaction — the caller owns the transaction lifecycle entirely.
708
- *
709
- * @param ops - Operations to apply.
710
- * @param sharedTx - The IDBTransaction shared across all participating stores.
711
- * Never null for IndexedDBStore.
712
- */
713
- executeInTransaction(ops: BufferedOperation<T>[], sharedTx: IDBTransaction | null): Promise<void>;
714
- getById(id: string | number): Promise<T | undefined>;
715
- getAll(): Promise<T[]>;
716
- count(): Promise<number>;
717
- cursor(callback: CursorCallback<T>, direction?: "forward" | "backward", keyRange?: StoreKeyRange): Promise<T | null>;
718
- private mapError;
719
- /**
720
- * Opens a fresh single-store IDB transaction for standalone (non-atomic)
721
- * operations. Not used by executeInTransaction — that receives an externally
722
- * managed shared transaction.
723
- */
724
- private withTx;
725
- private requestToPromise;
726
- }
727
-
728
- /**
729
- * Retrieves or creates an IndexedDB store instance.
730
- * * This function utilizes a synchronous execution path to retrieve the
731
- * ConnectionManager. If the connection is currently being initialized
732
- * asynchronously, or if the lock is contended, it throws a DatabaseError.
733
- *
734
- * @template T - The schema type for the collection.
735
- * @param config - The database and collection configuration.
736
- * @returns A functional Store instance.
737
- * @throws {DatabaseError} CONNECTION_FAILED if the manager is busy or fails to init.
738
- */
739
- declare const createIndexedDbStore: <T extends Record<string, any>>(config: StoreConfig) => Store<T>;
740
-
741
- type StoreFactory = <T extends Record<string, any>>(config: StoreConfig, indexes: IndexDefinition[]) => Store<T>;
742
- declare function DatabaseConnection(config: Omit<DatabaseConfig, "keyPath">, createStore: StoreFactory): Promise<Database>;
743
-
744
- interface MutexOptions {
745
- /**
746
- * Maximum number of pending requests allowed in the queue.
747
- * If exceeded, tryLock/lock will fail.
748
- * @default Infinity
749
- */
750
- capacity?: number;
751
- /**
752
- * Controls how lock handoff is scheduled when a waiter is unblocked.
753
- *
754
- * - `"macrotask"` (default): Uses setTimeout(fn, 0) to yield to the event
755
- * loop between handoffs. Prevents microtask starvation under heavy
756
- * contention — I/O, rendering, and other macrotasks can run between
757
- * lock acquisitions. Use for Serializer and other coarse-grained
758
- * serializers.
759
- *
760
- * - `"microtask"`: Uses queueMicrotask(fn) for handoff. Near-zero latency
761
- * between acquisitions — no macrotask delay. Safe when you need
762
- * back-to-back operations to complete as fast as possible and starvation
763
- * is not a concern (e.g. Once, Semaphore where builds are infrequent
764
- * and latency matters more than fairness).
765
- */
766
- yieldMode?: "macrotask" | "microtask";
767
- }
768
- /**
769
- * A mutual exclusion lock.
770
- * Allows only one execution context to access a resource at a time.
771
- *
772
- * Yield mode is configurable per instance:
773
- * - "macrotask" (default): yields between handoffs, preventing microtask starvation.
774
- * - "microtask": zero-delay handoff for latency-sensitive paths.
775
- */
776
- declare class Mutex {
777
- private _locked;
778
- private _capacity;
779
- private _yieldMode;
780
- private waiters;
781
- constructor(options?: MutexOptions);
782
- /**
783
- * Acquires the lock. If already held, waits until released or timeout reached.
784
- *
785
- * @param timeout - Optional maximum wait time in milliseconds.
786
- * @throws {TimeoutError} If the lock cannot be acquired within the timeout.
787
- * @throws {Error} If the wait queue is full (backpressure).
788
- */
789
- lock(timeout?: number): Promise<void>;
790
- /**
791
- * Attempts to acquire the lock without waiting.
792
- * @returns `true` if the lock was acquired, `false` otherwise.
793
- */
794
- tryLock(): boolean;
795
- /**
796
- * Releases the lock, scheduling the next waiter according to yieldMode.
797
- *
798
- * When a waiter exists, `_locked` intentionally remains `true` — ownership
799
- * transfers directly to the next waiter without ever clearing the flag.
800
- * Only when the queue is empty is `_locked` set to false.
801
- *
802
- * @throws {Error} If the mutex is not currently locked.
803
- */
804
- unlock(): void;
805
- /** Returns true if the mutex is currently locked. */
806
- locked(): boolean;
807
- /** Returns the number of operations waiting for the lock. */
808
- pending(): number;
809
- }
810
-
811
- /**
812
- * Interface defining the shape of the EventBus.
813
- * @template TEventMap - A record mapping event names to their respective payload types.
814
- */
815
- interface EventBus<TEventMap extends Record<string, any>> {
816
- /**
817
- * Subscribes to a specific event by name.
818
- * @param eventName - The name of the event to subscribe to.
819
- * @param callback - The function to call when the event is emitted.
820
- * @param options - Extra options to determine the behaviour of the
821
- * subscription
822
- * @returns A function to unsubscribe from the event.
823
- */
824
- subscribe<TEventName extends keyof TEventMap | "*">(eventName: TEventName, callback: TEventName extends "*" ? (payload: TEventMap[keyof TEventMap], event: keyof TEventMap) => void : (payload: TEventMap[TEventName]) => void, options?: SubscribeOptions): () => void;
825
- /**
826
- * Subscribes to an event and automatically unsubscribes after it fires once.
827
- * @param eventName - The name of the event to subscribe to.
828
- * @param callback - The function to call when the event is emitted.
829
- * @returns A function to cancel the one-shot subscription before it fires.
830
- */
831
- once<TEventName extends keyof TEventMap | "*">(eventName: TEventName, callback: TEventName extends "*" ? (payload: TEventMap[keyof TEventMap], event: keyof TEventMap) => void : (payload: TEventMap[TEventName]) => void, options?: SubscribeOptions): () => void;
832
- /**
833
- * Emits an event with a payload to all subscribed listeners.
834
- * @param event - An object containing the event name and payload.
835
- */
836
- emit: <TEventName extends keyof TEventMap>(event: {
837
- name: TEventName;
838
- payload: TEventMap[TEventName];
839
- }) => void;
840
- /**
841
- * Retrieves metrics about event bus usage.
842
- * @returns An object containing various metrics.
843
- */
844
- metrics: () => EventMetrics;
845
- /**
846
- * Clears all subscriptions and resets metrics.
847
- *
848
- * After calling `clear()`, the bus is fully reset and can be reused
849
- * cross-tab communication is re-established if it was previously enabled.
850
- *
851
- * @param options - Optional configuration object.
852
- * @param options.permanent - If `true`, the bus becomes permanently unusable after clearing.
853
- * Defaults to `false`.
854
- * @returns {void}
855
- */
856
- clear: (options?: {
857
- permanent?: boolean;
858
- }) => void;
859
- }
860
- /**
861
- * Interface defining the metrics tracked by the EventBus.
862
- */
863
- interface EventMetrics {
864
- /** Total number of events emitted (both sync and deferred paths). */
865
- totalEvents: number;
866
- /** Number of active subscriptions across all event names. */
867
- activeSubscriptions: number;
868
- /** Map of event names to their emission counts. */
869
- eventCounts: Map<string, number>;
870
- /** Average duration of event dispatch in milliseconds. */
871
- averageEmitDuration: number;
872
- }
873
- interface SubscribeOptions {
874
- /**
875
- * Debounce delay in milliseconds. When multiple events arrive in quick
876
- * succession, the callback runs only after the quiet period ends, using the
877
- * latest payload. Default = no debouncing.
878
- */
879
- debounce?: number;
880
- }
881
-
882
- interface MiddlewareContext {
883
- collection?: string;
884
- documentId?: string;
885
- operation: string;
886
- args: any[];
887
- eventBus?: EventBus<any>;
888
- }
889
- type MaybePromise<T> = T | Promise<T>;
890
- type MiddlewareNext = () => MaybePromise<any>;
891
- type Middleware = (ctx: MiddlewareContext, next: MiddlewareNext) => MaybePromise<any>;
892
- declare class Pipeline {
893
- private middlewares;
894
- use(middleware: Middleware): void;
895
- execute(ctx: MiddlewareContext, finalOperation: () => MaybePromise<any>): MaybePromise<any>;
896
- wrap<T extends object>(target: T, baseContext: Partial<MiddlewareContext>): T;
897
- }
898
-
899
- interface DocumentOptions<T extends Record<string, any>> {
900
- /**
901
- * The already-persisted initial state of the document.
902
- * createDocument does NOT call store.add — the caller is responsible
903
- * for having written this record before constructing the document proxy.
904
- */
905
- initial: Partial<T>;
906
- collection: string;
907
- schema: SchemaDefinition;
908
- validator?: StandardSchemaV1;
909
- store: Store<T>;
910
- bus: EventBus<Record<DocumentEventType | TelemetryEventType, DocumentEvent<T> | TelemetryEvent>>;
911
- pipeline: Pipeline;
912
- lockManager: Mutex;
913
- }
914
- /**
915
- * Constructs an in-memory Document proxy around an already-persisted record.
916
- *
917
- * Responsibility split:
918
- * - createDocument: validates, initialises in-memory state, wires operations. NO I/O.
919
- * - openCollection.create: owns the initial store.add (or tx.addOp for transactional creates).
920
- *
921
- * This separation ensures that transactional creates are correctly buffered:
922
- * the document object is available immediately in-memory, while the actual
923
- * store write is deferred until transaction commit.
924
- */
925
- declare function createDocument<T extends Record<string, any>>(opts: DocumentOptions<T>): Promise<Document<T & {
926
- $id: string | number;
927
- }>>;
928
- declare function openCollection<T extends Record<string, any>>({ collection: schemaName, validator, bus, store, pipeline, validate, schema: schemaDefinition, }: {
929
- store: Store<T>;
930
- collection: string;
931
- validator: StandardSchemaV1;
932
- bus: EventBus<any>;
933
- pipeline: Pipeline;
934
- validate: boolean;
935
- schema: SchemaDefinition;
936
- }): Promise<Collection<T>>;
937
-
938
- /**
939
- * Error types for Database operations.
940
- */
941
- declare enum DatabaseErrorType {
942
- /** The schema does not exist. */
943
- SCHEMA_NOT_FOUND = "SCHEMA_NOT_FOUND",
944
- /** The schema already exists. */
945
- SCHEMA_ALREADY_EXISTS = "SCHEMA_ALREADY_EXISTS",
946
- /** The schema name is invalid. */
947
- INVALID_SCHEMA_NAME = "INVALID_SCHEMA_NAME",
948
- /** The schema definition is invalid. */
949
- INVALID_SCHEMA_DEFINITION = "INVALID_SCHEMA_DEFINITION",
950
- /** The database subscription failed. */
951
- SUBSCRIPTION_FAILED = "SUBSCRIPTION_FAILED",
952
- /** The database operation failed due to an internal error. */
953
- INTERNAL_ERROR = "INTERNAL_ERROR",
954
- /** Data being entered into a collection does not satisfy its schema definition. */
955
- INVALID_DATA = "INVALID_DATA",
956
- /** Optimistic Concurrency Control version mismatches */
957
- CONFLICT = "CONFLICT",
958
- /** For retryable store locks */
959
- TRANSIENT_ERROR = "TRANSIENT_ERROR",
960
- TRANSACTION_FAILED = "TRANSACTION_FAILED",
961
- CONNECTION_FAILED = "CONNECTION_FAILED",
962
- INVALID_OPERATION = "INVALID_OPERATION"
963
- }
964
- /**
965
- * Error class for Database operations.
966
- */
967
- declare class DatabaseError extends Error {
968
- /**
969
- * The type of error that occurred.
970
- */
971
- type: DatabaseErrorType;
972
- /**
973
- * The schema associated with the error, if applicable.
974
- */
975
- schema?: SchemaDefinition;
976
- /**
977
- * Issues associated with the error
978
- */
979
- issues?: any[];
980
- /**
981
- * Constructs a new DatabaseErrorClass instance.
982
- * @param type - The type of error that occurred.
983
- * @param message - A human-readable message describing the error.
984
- * @param schema - The schema associated with the error, if applicable.
985
- */
986
- constructor(type: DatabaseErrorType, message: string, schema?: SchemaDefinition, cause?: unknown, issues?: any);
987
- }
988
-
989
- export { type BufferedOperation, type Collection, type CollectionEvent, type CollectionEventType, type CollectionMigrationOptions, ConnectionManager, type CursorCallback, type CursorCallbackResult, type CursorPaginationOptions, DEFAULT_KEYPATH, type Database, type DatabaseConfig, DatabaseConnection, DatabaseError, DatabaseErrorType, type DatabaseEvent, type DatabaseEventType, type Document, type DocumentEvent, type DocumentEventType, type DocumentMetadata, IndexedDBStore, type Store, type StoreConfig, type StoreFactory, type StoreKeyRange, type TelemetryEvent, type TelemetryEventType, createDocument, createEphemeralStore, createIndexedDbStore, openCollection };