@firtoz/drizzle-indexeddb 0.3.0 → 0.4.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # @firtoz/drizzle-indexeddb
2
2
 
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`904019f`](https://github.com/firtoz/fullstack-toolkit/commit/904019f4d04bc02521206fbe0feaeecb67e38f87) Thanks [@firtoz](https://github.com/firtoz)! - Fix tsx exporting
8
+
9
+ ## 0.4.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [`46059a2`](https://github.com/firtoz/fullstack-toolkit/commit/46059a28bd0135414b9ed022ffe162a2292adae3) Thanks [@firtoz](https://github.com/firtoz)! - Add IDB Proxy system for multi-client IndexedDB sync over messaging layers:
14
+
15
+ **New Proxy Module** (`@firtoz/drizzle-indexeddb/proxy`):
16
+
17
+ - **`IDBProxyServer`** - Server that manages database lifecycle, migrations, and broadcasts mutations to connected clients
18
+ - **`IDBProxyClient`** - Client implementing `IDBDatabaseLike`, routing operations through a transport layer
19
+ - **`createMultiClientTransport()`** - In-memory transport for testing N clients connected to one server
20
+ - **`createProxyDbCreator()`** - Factory to create `dbCreator` for `DrizzleIndexedDBProvider`
21
+ - **`createCollectionSyncHandler()`** - Adapter connecting proxy sync messages to collection's external sync
22
+
23
+ **Real-time Multi-Client Sync**:
24
+
25
+ - Server broadcasts `sync:add`, `sync:put`, `sync:delete`, `sync:clear` messages to all clients (excluding initiator)
26
+ - All mutations automatically sync across connected clients
27
+
28
+ **Provider Enhancements**:
29
+
30
+ - New `onSyncReady` prop for wiring up external sync handlers
31
+ - `handleProxySync` method routes sync messages to the appropriate collection
32
+
33
+ **Collection Truncate**:
34
+
35
+ - `collection.utils.truncate()` clears all data and syncs to other clients
36
+ - `handleTruncate` implemented in IndexedDB backend
37
+
38
+ **Bug Fixes**:
39
+
40
+ - Server handles concurrent database initialization requests (race condition fix)
41
+
42
+ ### Patch Changes
43
+
44
+ - Updated dependencies [[`46059a2`](https://github.com/firtoz/fullstack-toolkit/commit/46059a28bd0135414b9ed022ffe162a2292adae3)]:
45
+ - @firtoz/drizzle-utils@0.3.0
46
+
3
47
  ## 0.3.0
4
48
 
5
49
  ### Minor Changes
package/README.md CHANGED
@@ -20,6 +20,7 @@ npm install @firtoz/drizzle-indexeddb @firtoz/drizzle-utils drizzle-orm @tanstac
20
20
  - šŸ“¦ **Soft deletes** - Built-in support for `deletedAt` column
21
21
  - āš›ļø **React hooks** - Provider and hooks for easy React integration
22
22
  - šŸ“ **Function-based migrations** - Generated migration functions from Drizzle schema changes
23
+ - šŸ”„ **Multi-client sync** - IDB Proxy system for real-time sync across multiple clients (Chrome extensions, etc.)
23
24
 
24
25
  ## Quick Start
25
26
 
@@ -416,6 +417,167 @@ const collection = createCollection(
416
417
  );
417
418
  ```
418
419
 
420
+ ### Collection Truncate
421
+
422
+ Clear all data from a collection:
423
+
424
+ ```typescript
425
+ // Clear all todos
426
+ await todoCollection.utils.truncate();
427
+ ```
428
+
429
+ This clears the IndexedDB store and updates the local reactive store.
430
+
431
+ ## IDB Proxy System
432
+
433
+ For scenarios where IndexedDB needs to be accessed over a messaging layer (e.g., Chrome extensions, WebSockets), the proxy system enables multi-client sync:
434
+
435
+ ### Overview
436
+
437
+ ```
438
+ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
439
+ │ Client 1│ │ Client 2│ │ Client N│
440
+ ā””ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”˜
441
+ │ │ │
442
+ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
443
+ │
444
+ ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”
445
+ │ Server │
446
+ │ (manages │
447
+ │ IndexedDB) │
448
+ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
449
+ ```
450
+
451
+ - **Server** manages database lifecycle, migrations, and broadcasts mutations
452
+ - **Clients** connect via a transport layer and receive real-time sync updates
453
+ - All insert/update/delete/truncate operations sync to all connected clients
454
+
455
+ ### Basic Setup
456
+
457
+ ```typescript
458
+ import {
459
+ createMultiClientTransport,
460
+ createProxyServer,
461
+ createProxyDbCreator,
462
+ migrateIndexedDBWithFunctions,
463
+ DrizzleIndexedDBProvider,
464
+ } from "@firtoz/drizzle-indexeddb";
465
+
466
+ // Create transport (in-memory for testing, or custom for production)
467
+ const { createClientTransport, serverTransport } = createMultiClientTransport();
468
+
469
+ // Create server with migrations
470
+ const server = createProxyServer({
471
+ transport: serverTransport,
472
+ dbCreator: async (dbName) => {
473
+ return await migrateIndexedDBWithFunctions(dbName, migrations);
474
+ },
475
+ });
476
+
477
+ // Create client
478
+ const clientTransport = createClientTransport();
479
+ const dbCreator = createProxyDbCreator(clientTransport);
480
+
481
+ // Use with React provider
482
+ function App() {
483
+ const handleSyncReady = useCallback((handleSync) => {
484
+ clientTransport.onSync(handleSync);
485
+ }, []);
486
+
487
+ return (
488
+ <DrizzleIndexedDBProvider
489
+ dbName="my-app.db"
490
+ schema={schema}
491
+ dbCreator={dbCreator}
492
+ onSyncReady={handleSyncReady}
493
+ >
494
+ <YourApp />
495
+ </DrizzleIndexedDBProvider>
496
+ );
497
+ }
498
+ ```
499
+
500
+ ### Multiple Clients
501
+
502
+ ```typescript
503
+ // Server setup (once)
504
+ const { createClientTransport, serverTransport } = createMultiClientTransport();
505
+ const server = createProxyServer({ transport: serverTransport, ... });
506
+
507
+ // Each client gets its own transport
508
+ const client1Transport = createClientTransport();
509
+ const client2Transport = createClientTransport();
510
+ const client3Transport = createClientTransport();
511
+
512
+ // All clients share the same data and receive real-time sync
513
+ ```
514
+
515
+ ### Sync Operations
516
+
517
+ All standard collection operations automatically sync:
518
+
519
+ ```typescript
520
+ // Client 1 inserts
521
+ await todoCollection.insert({ title: "Buy milk", completed: false });
522
+ // → Client 2, 3, N receive the new todo instantly
523
+
524
+ // Client 2 updates
525
+ await todoCollection.update(todoId, (draft) => {
526
+ draft.completed = true;
527
+ });
528
+ // → Client 1, 3, N see the update instantly
529
+
530
+ // Client 3 deletes
531
+ await todoCollection.delete(todoId);
532
+ // → Client 1, 2, N see the deletion instantly
533
+
534
+ // Client N truncates
535
+ await todoCollection.utils.truncate();
536
+ // → All clients are cleared instantly
537
+ ```
538
+
539
+ ### Custom Transport
540
+
541
+ For production use (Chrome extension, WebSocket, etc.), implement the transport interface:
542
+
543
+ ```typescript
544
+ import type { IDBProxyClientTransport, IDBProxyServerTransport } from "@firtoz/drizzle-indexeddb";
545
+
546
+ // Client transport (e.g., in content script)
547
+ const clientTransport: IDBProxyClientTransport = {
548
+ sendRequest: async (request) => {
549
+ // Send to background script and wait for response
550
+ return await chrome.runtime.sendMessage(request);
551
+ },
552
+ onSync: (handler) => {
553
+ // Listen for sync broadcasts
554
+ chrome.runtime.onMessage.addListener((msg) => {
555
+ if (msg.type?.startsWith("sync:")) handler(msg);
556
+ });
557
+ },
558
+ };
559
+
560
+ // Server transport (e.g., in background script)
561
+ const serverTransport: IDBProxyServerTransport = {
562
+ onRequest: (handler) => {
563
+ chrome.runtime.onMessage.addListener(async (msg, sender, sendResponse) => {
564
+ const response = await handler(msg);
565
+ sendResponse(response);
566
+ });
567
+ },
568
+ broadcast: (message, excludeClientId) => {
569
+ // Broadcast to all connected tabs except sender
570
+ chrome.tabs.query({}, (tabs) => {
571
+ for (const tab of tabs) {
572
+ if (tab.id !== excludeClientId) {
573
+ chrome.tabs.sendMessage(tab.id, message);
574
+ }
575
+ }
576
+ });
577
+ },
578
+ };
579
+ ```
580
+
419
581
  ### Handling Migration Errors
420
582
 
421
583
  ```typescript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firtoz/drizzle-indexeddb",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "IndexedDB migrations powered by Drizzle ORM",
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -24,10 +24,16 @@
24
24
  "types": "./src/*.ts",
25
25
  "import": "./src/*.ts",
26
26
  "require": "./src/*.ts"
27
+ },
28
+ "./*.tsx": {
29
+ "types": "./src/*.tsx",
30
+ "import": "./src/*.tsx",
31
+ "require": "./src/*.tsx"
27
32
  }
28
33
  },
29
34
  "files": [
30
35
  "src/**/*.ts",
36
+ "src/**/*.tsx",
31
37
  "!src/**/*.test.ts",
32
38
  "README.md",
33
39
  "CHANGELOG.md"
@@ -64,14 +70,14 @@
64
70
  "access": "public"
65
71
  },
66
72
  "dependencies": {
67
- "@firtoz/drizzle-utils": "^0.2.0",
68
- "@tanstack/db": "^0.5.10",
69
- "drizzle-orm": "^0.44.7",
73
+ "@firtoz/drizzle-utils": "^0.3.0",
74
+ "@tanstack/db": "^0.5.11",
75
+ "drizzle-orm": "^0.45.0",
70
76
  "drizzle-valibot": "^0.4.2",
71
77
  "valibot": "^1.2.0"
72
78
  },
73
79
  "peerDependencies": {
74
- "react": "^19.2.0"
80
+ "react": "^19.2.1"
75
81
  },
76
82
  "devDependencies": {
77
83
  "@types/react": "^19.2.7"
@@ -14,7 +14,7 @@ import {
14
14
  createCollectionConfig,
15
15
  } from "@firtoz/drizzle-utils";
16
16
 
17
- import type { IDBDatabaseLike, KeyRangeSpec } from "../utils";
17
+ import type { IDBDatabaseLike, KeyRangeSpec } from "../idb-types";
18
18
 
19
19
  // biome-ignore lint/suspicious/noExplicitAny: intentional
20
20
  type AnyId = IdOf<any>;
@@ -30,73 +30,6 @@ export type IndexedDBSyncItem = {
30
30
  [key: string]: unknown;
31
31
  };
32
32
 
33
- /**
34
- * Operation tracking for IndexedDB queries
35
- * Useful for testing and debugging to verify what operations are actually performed
36
- *
37
- * Uses discriminated unions for type safety - TypeScript can narrow the type based on the 'type' field
38
- */
39
- export type IDBOperation =
40
- | {
41
- type: "getAll";
42
- storeName: string;
43
- itemsReturned: unknown[];
44
- itemCount: number;
45
- context: string;
46
- timestamp: number;
47
- }
48
- | {
49
- type: "index-getAll";
50
- storeName: string;
51
- indexName: string;
52
- keyRange?: IDBKeyRange;
53
- itemsReturned: unknown[];
54
- itemCount: number;
55
- context: string;
56
- timestamp: number;
57
- }
58
- | {
59
- type: "write";
60
- storeName: string;
61
- itemsWritten: unknown[];
62
- writeCount: number;
63
- context: string;
64
- timestamp: number;
65
- }
66
- | {
67
- type: "get";
68
- storeName: string;
69
- key: IDBValidKey;
70
- itemReturned?: unknown;
71
- timestamp: number;
72
- }
73
- | {
74
- type: "put";
75
- storeName: string;
76
- item: unknown;
77
- timestamp: number;
78
- }
79
- | {
80
- type: "delete";
81
- storeName: string;
82
- key: IDBValidKey;
83
- timestamp: number;
84
- }
85
- | {
86
- type: "clear";
87
- storeName: string;
88
- timestamp: number;
89
- };
90
-
91
- /**
92
- * Interceptor interface for tracking IndexedDB operations
93
- * Allows tests and debugging tools to observe what operations are performed
94
- */
95
- export interface IDBInterceptor {
96
- /** Called when any IndexedDB operation is performed */
97
- onOperation?: (operation: IDBOperation) => void;
98
- }
99
-
100
33
  export interface IndexedDBCollectionConfig<TTable extends Table> {
101
34
  /**
102
35
  * Ref to the IndexedDB database instance
@@ -126,10 +59,6 @@ export interface IndexedDBCollectionConfig<TTable extends Table> {
126
59
  * Enable debug logging for index discovery and query optimization
127
60
  */
128
61
  debug?: boolean;
129
- /**
130
- * Optional interceptor for tracking IndexedDB operations (for testing/debugging)
131
- */
132
- interceptor?: IDBInterceptor;
133
62
  }
134
63
 
135
64
  /**
@@ -259,68 +188,6 @@ export function getExpressionValue(
259
188
  throw new Error(`Cannot get value from expression type: ${expression.type}`);
260
189
  }
261
190
 
262
- /**
263
- * Reads all items from an IndexedDB object store
264
- */
265
- async function getAllFromStore(
266
- db: IDBDatabaseLike,
267
- storeName: string,
268
- interceptor?: IDBInterceptor,
269
- ): Promise<IndexedDBSyncItem[]> {
270
- const items = await db.getAll<IndexedDBSyncItem>(storeName);
271
-
272
- // Log operation after executing with results
273
- if (interceptor?.onOperation) {
274
- interceptor.onOperation({
275
- type: "getAll",
276
- storeName,
277
- itemsReturned: items,
278
- itemCount: items.length,
279
- context: "Full table scan",
280
- timestamp: Date.now(),
281
- });
282
- }
283
-
284
- return items;
285
- }
286
-
287
- /**
288
- * Reads items from an IndexedDB index with an optional key range
289
- * Note: Index existence is validated at collection creation time
290
- */
291
- async function getAllFromIndex(
292
- db: IDBDatabaseLike,
293
- storeName: string,
294
- indexName: string,
295
- keyRange?: KeyRangeSpec,
296
- interceptor?: IDBInterceptor,
297
- ): Promise<IndexedDBSyncItem[]> {
298
- const items = await db.getAllByIndex<IndexedDBSyncItem>(
299
- storeName,
300
- indexName,
301
- keyRange,
302
- );
303
-
304
- // Log operation after executing with results
305
- if (interceptor?.onOperation) {
306
- const rangeDesc = keyRange
307
- ? `[${keyRange.lower ?? keyRange.value ?? ""}..${keyRange.upper ?? keyRange.value ?? ""}]`
308
- : "all";
309
- interceptor.onOperation({
310
- type: "index-getAll",
311
- storeName,
312
- indexName,
313
- keyRange: keyRange as unknown as IDBKeyRange | undefined,
314
- itemsReturned: items,
315
- itemCount: items.length,
316
- context: `Index query on ${indexName}, range: ${rangeDesc}`,
317
- timestamp: Date.now(),
318
- });
319
- }
320
-
321
- return items;
322
- }
323
-
324
191
  /**
325
192
  * Attempts to extract a simple indexed query from an IR expression
326
193
  * Returns the field name and key range if the query can be optimized
@@ -475,12 +342,13 @@ export function indexedDBCollectionOptions<const TTable extends Table>(
475
342
  const discoverIndexesOnce = async () => {
476
343
  await config.readyPromise;
477
344
 
345
+ const db = config.indexedDBRef.current;
346
+ if (!db) {
347
+ throw new Error("Database not ready");
348
+ }
349
+
478
350
  if (!indexesDiscovered) {
479
- discoveredIndexes = discoverIndexes(
480
- // biome-ignore lint/style/noNonNullAssertion: DB is guaranteed to be ready after readyPromise resolves
481
- config.indexedDBRef.current!,
482
- config.storeName,
483
- );
351
+ discoveredIndexes = discoverIndexes(db, config.storeName);
484
352
 
485
353
  indexesDiscovered = true;
486
354
  }
@@ -489,26 +357,14 @@ export function indexedDBCollectionOptions<const TTable extends Table>(
489
357
  // Create backend-specific implementation
490
358
  const backend: SyncBackend<TTable> = {
491
359
  initialLoad: async (write) => {
360
+ const db = config.indexedDBRef.current;
361
+ if (!db) {
362
+ throw new Error("Database not ready");
363
+ }
364
+
492
365
  await discoverIndexesOnce();
493
366
 
494
- const items = await getAllFromStore(
495
- // biome-ignore lint/style/noNonNullAssertion: DB is guaranteed to be ready after readyPromise resolves
496
- config.indexedDBRef.current!,
497
- config.storeName,
498
- config.interceptor,
499
- );
500
-
501
- // Log what's being written to the collection
502
- if (config.interceptor?.onOperation) {
503
- config.interceptor.onOperation({
504
- type: "write",
505
- storeName: config.storeName,
506
- itemsWritten: items,
507
- writeCount: items.length,
508
- context: "Initial load (eager mode)",
509
- timestamp: Date.now(),
510
- });
511
- }
367
+ const items = await db.getAll<IndexedDBSyncItem>(config.storeName);
512
368
 
513
369
  for (const item of items) {
514
370
  write(item as unknown as InferSchemaOutput<SelectSchema<TTable>>);
@@ -516,13 +372,14 @@ export function indexedDBCollectionOptions<const TTable extends Table>(
516
372
  },
517
373
 
518
374
  loadSubset: async (options, write) => {
375
+ const db = config.indexedDBRef.current;
376
+ if (!db) {
377
+ throw new Error("Database not ready");
378
+ }
379
+
519
380
  // Ensure indexes are discovered before we try to use them
520
381
  if (!indexesDiscovered) {
521
- discoveredIndexes = discoverIndexes(
522
- // biome-ignore lint/style/noNonNullAssertion: DB is guaranteed to be ready after readyPromise resolves
523
- config.indexedDBRef.current!,
524
- config.storeName,
525
- );
382
+ discoveredIndexes = discoverIndexes(db, config.storeName);
526
383
  indexesDiscovered = true;
527
384
  }
528
385
 
@@ -535,22 +392,15 @@ export function indexedDBCollectionOptions<const TTable extends Table>(
535
392
 
536
393
  if (indexedQuery) {
537
394
  // Use indexed query for better performance
538
- items = await getAllFromIndex(
539
- // biome-ignore lint/style/noNonNullAssertion: DB is guaranteed to be ready after readyPromise resolves
540
- config.indexedDBRef.current!,
395
+
396
+ items = await db.getAllByIndex<IndexedDBSyncItem>(
541
397
  config.storeName,
542
398
  indexedQuery.indexName,
543
399
  indexedQuery.keyRange,
544
- config.interceptor,
545
400
  );
546
401
  } else {
547
402
  // Fall back to getting all items
548
- items = await getAllFromStore(
549
- // biome-ignore lint/style/noNonNullAssertion: DB is guaranteed to be ready after readyPromise resolves
550
- config.indexedDBRef.current!,
551
- config.storeName,
552
- config.interceptor,
553
- );
403
+ items = await db.getAll<IndexedDBSyncItem>(config.storeName);
554
404
 
555
405
  // Apply where filter in memory
556
406
  if (options.where) {
@@ -595,37 +445,17 @@ export function indexedDBCollectionOptions<const TTable extends Table>(
595
445
  items = items.slice(0, options.limit);
596
446
  }
597
447
 
598
- // Log what's being written to the collection
599
- if (config.interceptor?.onOperation) {
600
- const contextParts: string[] = ["On-demand load"];
601
- if (indexedQuery) {
602
- contextParts.push(`via index ${indexedQuery.indexName}`);
603
- } else if (options.where) {
604
- contextParts.push("via full scan + in-memory filter");
605
- }
606
- if (options.orderBy) {
607
- contextParts.push("with sorting");
608
- }
609
- if (options.limit !== undefined) {
610
- contextParts.push(`limit ${options.limit}`);
611
- }
612
-
613
- config.interceptor.onOperation({
614
- type: "write",
615
- storeName: config.storeName,
616
- itemsWritten: items,
617
- writeCount: items.length,
618
- context: contextParts.join(", "),
619
- timestamp: Date.now(),
620
- });
621
- }
622
-
623
448
  for (const item of items) {
624
449
  write(item as unknown as InferSchemaOutput<SelectSchema<TTable>>);
625
450
  }
626
451
  },
627
452
 
628
453
  handleInsert: async (mutations) => {
454
+ const db = config.indexedDBRef.current;
455
+ if (!db) {
456
+ throw new Error("Database not ready");
457
+ }
458
+
629
459
  const results: Array<InferSchemaOutput<SelectSchema<TTable>>> = [];
630
460
 
631
461
  try {
@@ -638,8 +468,7 @@ export function indexedDBCollectionOptions<const TTable extends Table>(
638
468
  }
639
469
 
640
470
  // Add all items in a single batch operation
641
- // biome-ignore lint/style/noNonNullAssertion: DB is guaranteed to be ready after readyPromise resolves
642
- await config.indexedDBRef.current!.add(config.storeName, itemsToInsert);
471
+ await db.add(config.storeName, itemsToInsert);
643
472
  } catch (error) {
644
473
  // Clear results on error so nothing gets written to reactive store
645
474
  results.length = 0;
@@ -650,12 +479,15 @@ export function indexedDBCollectionOptions<const TTable extends Table>(
650
479
  },
651
480
 
652
481
  handleUpdate: async (mutations) => {
482
+ const db = config.indexedDBRef.current;
483
+
484
+ if (!db) {
485
+ throw new Error("Database not ready");
486
+ }
487
+
653
488
  const results: Array<InferSchemaOutput<SelectSchema<TTable>>> = [];
654
489
  const itemsToUpdate: IndexedDBSyncItem[] = [];
655
490
 
656
- // biome-ignore lint/style/noNonNullAssertion: DB is guaranteed to be ready after readyPromise resolves
657
- const db = config.indexedDBRef.current!;
658
-
659
491
  for (const mutation of mutations) {
660
492
  const existing = await db.get<IndexedDBSyncItem>(
661
493
  config.storeName,
@@ -689,11 +521,27 @@ export function indexedDBCollectionOptions<const TTable extends Table>(
689
521
  },
690
522
 
691
523
  handleDelete: async (mutations) => {
524
+ const db = config.indexedDBRef.current;
525
+
526
+ if (!db) {
527
+ throw new Error("Database not ready");
528
+ }
529
+
692
530
  const keysToDelete: IDBValidKey[] = mutations.map((m) => m.key);
693
531
 
694
532
  // Delete all items in a single batch operation
695
- // biome-ignore lint/style/noNonNullAssertion: DB is guaranteed to be ready after readyPromise resolves
696
- await config.indexedDBRef.current!.delete(config.storeName, keysToDelete);
533
+ await db.delete(config.storeName, keysToDelete);
534
+ },
535
+
536
+ handleTruncate: async () => {
537
+ const db = config.indexedDBRef.current;
538
+
539
+ if (!db) {
540
+ throw new Error("Database not ready");
541
+ }
542
+
543
+ // Clear all items from the store
544
+ await db.clear(config.storeName);
697
545
  },
698
546
  };
699
547