@firtoz/drizzle-indexeddb 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # @firtoz/drizzle-indexeddb
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`9e532bb`](https://github.com/firtoz/fullstack-toolkit/commit/9e532bbd83bc671c62fd1333ae25fd9829112464) Thanks [@firtoz](https://github.com/firtoz)! - Add `createStandaloneCollection` utility for using IndexedDB collections outside of React context.
8
+
9
+ Features:
10
+
11
+ - Simple API for standalone usage without React providers
12
+ - Async mutation methods (`insert`, `update`, `delete`, `truncate`) that return Promises
13
+ - Sync accessors (`getAll`, `get`, `isReady`)
14
+ - Full access to collection utils (`truncate`, `pushExternalSync`)
15
+ - Automatic database initialization with migration support
16
+
17
+ Also:
18
+
19
+ - Update `IndexedDbCollection` type to use `CollectionUtils` instead of generic `UtilsRecord` for proper typing of `truncate` and `pushExternalSync`
20
+ - Export `IndexedDbCollection` type from package
21
+
22
+ ### Patch Changes
23
+
24
+ - [`c772c2c`](https://github.com/firtoz/fullstack-toolkit/commit/c772c2cf74af560dc04080933591ccd3014f85a1) Thanks [@firtoz](https://github.com/firtoz)! - Improve types returned and simplify internal logic
25
+
26
+ ## 0.4.3
27
+
28
+ ### Patch Changes
29
+
30
+ - [`ed6dfce`](https://github.com/firtoz/fullstack-toolkit/commit/ed6dfce75be95d5349381ab43c6c22b25b164414) Thanks [@firtoz](https://github.com/firtoz)! - Enable drizzle and output dir configuration in drizzle-indexeddb-generate
31
+
3
32
  ## 0.4.2
4
33
 
5
34
  ### Patch Changes
package/README.md CHANGED
@@ -385,8 +385,8 @@ bun drizzle-indexeddb-generate --help
385
385
 
386
386
  | Option | Description | Default |
387
387
  |--------|-------------|---------|
388
- | `--drizzle-dir <path>` | Path to Drizzle directory | `./drizzle` |
389
- | `--output-dir <path>` | Path to output directory | `./drizzle/indexeddb-migrations` |
388
+ | `--drizzle-dir <path>`, `-d` | Path to Drizzle directory | `./drizzle` |
389
+ | `--output-dir <path>`, `-o` | Path to output directory | `<drizzle-dir>/indexeddb-migrations` |
390
390
 
391
391
  ### npm scripts
392
392
 
@@ -395,11 +395,13 @@ Add to your `package.json`:
395
395
  ```json
396
396
  {
397
397
  "scripts": {
398
- "db:generate": "bun drizzle-kit generate && bun drizzle-indexeddb-generate"
398
+ "db:generate": "bun --bun drizzle-kit generate && bun drizzle-indexeddb-generate"
399
399
  }
400
400
  }
401
401
  ```
402
402
 
403
+ > **Note:** The `--bun` flag forces bun's runtime instead of Node, which is needed because this package exports raw TypeScript. See [Troubleshooting](#err_unsupported_node_modules_type_stripping-error) if you encounter type stripping errors.
404
+
403
405
  ## Advanced Usage
404
406
 
405
407
  ### Custom Sync Configuration
@@ -666,6 +668,38 @@ Remove from schema and regenerate - the migrator will delete the object store.
666
668
 
667
669
  ## Troubleshooting
668
670
 
671
+ ### "ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING" Error
672
+
673
+ If you see this error when running `drizzle-kit generate`:
674
+
675
+ ```
676
+ Error [ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING]: Stripping types is currently unsupported for files under node_modules
677
+ ```
678
+
679
+ This happens because this package exports raw TypeScript files, and Node's built-in type stripping doesn't work inside `node_modules`.
680
+
681
+ **Solution:** Use `bun --bun` to force bun's runtime instead of Node:
682
+
683
+ ```bash
684
+ bun --bun drizzle-kit generate
685
+ ```
686
+
687
+ Or in your `package.json`:
688
+
689
+ ```json
690
+ {
691
+ "scripts": {
692
+ "db:generate": "bun --bun drizzle-kit generate && bun drizzle-indexeddb-generate"
693
+ }
694
+ }
695
+ ```
696
+
697
+ **Alternative:** If you're not using bun, use `tsx`:
698
+
699
+ ```bash
700
+ npx tsx node_modules/drizzle-kit/bin.cjs generate --config ./drizzle.config.ts
701
+ ```
702
+
669
703
  ### "Primary key structure changed" Error
670
704
 
671
705
  This happens when you change the primary key of a table. IndexedDB doesn't support changing keyPath after creation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firtoz/drizzle-indexeddb",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "IndexedDB migrations powered by Drizzle ORM",
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -83,5 +83,8 @@
83
83
  "drizzle-valibot": "^0.4.2",
84
84
  "react": "^19.2.1",
85
85
  "valibot": "^1.2.0"
86
+ },
87
+ "dependencies": {
88
+ "citty": "^0.1.6"
86
89
  }
87
90
  }
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
11
11
  import { join, resolve } from "node:path";
12
+ import { defineCommand, runMain } from "citty";
12
13
  import type {
13
14
  JournalEntry,
14
15
  Journal,
@@ -240,49 +241,34 @@ export default migrations;
240
241
  }
241
242
 
242
243
  // CLI entry point
243
- function main(): void {
244
- const args = process.argv.slice(2);
245
- const command = args[0];
246
-
247
- if (command === "generate" || command === undefined) {
248
- // Parse options
249
- const options: GenerateOptions = {};
250
-
251
- for (let i = 1; i < args.length; i++) {
252
- const arg = args[i];
253
- if (arg === "--drizzle-dir" && args[i + 1]) {
254
- options.drizzleDir = args[++i];
255
- } else if (arg === "--output-dir" && args[i + 1]) {
256
- options.outputDir = args[++i];
257
- }
258
- }
259
-
260
- generateIndexedDBMigrations(options);
261
- } else if (command === "--help" || command === "-h") {
262
- console.log(`
263
- drizzle-indexeddb-generate - Generate IndexedDB migrations from Drizzle schema
264
-
265
- Usage:
266
- bun drizzle-indexeddb-generate [options]
267
-
268
- Options:
269
- --drizzle-dir <path> Path to Drizzle directory (default: ./drizzle)
270
- --output-dir <path> Path to output directory (default: ./drizzle/indexeddb-migrations)
271
- -h, --help Show this help message
272
-
273
- Examples:
274
- bun drizzle-indexeddb-generate
275
- bun drizzle-indexeddb-generate --drizzle-dir ./db/drizzle
276
- bun drizzle-indexeddb-generate --output-dir ./src/migrations
277
- `);
278
- } else {
279
- console.error(`Unknown command: ${command}`);
280
- console.error(`Run 'bun drizzle-indexeddb-generate --help' for usage`);
281
- process.exit(1);
282
- }
283
- }
244
+ const main = defineCommand({
245
+ meta: {
246
+ name: "drizzle-indexeddb-generate",
247
+ description: "Generate IndexedDB migrations from Drizzle schema",
248
+ },
249
+ args: {
250
+ drizzleDir: {
251
+ type: "string",
252
+ alias: "d",
253
+ default: "./drizzle",
254
+ description: "Path to Drizzle directory",
255
+ },
256
+ outputDir: {
257
+ type: "string",
258
+ alias: "o",
259
+ description:
260
+ "Path to output directory (default: <drizzle-dir>/indexeddb-migrations)",
261
+ },
262
+ },
263
+ run({ args }) {
264
+ generateIndexedDBMigrations({
265
+ drizzleDir: args.drizzleDir,
266
+ outputDir: args.outputDir,
267
+ });
268
+ },
269
+ });
284
270
 
285
271
  // Only run CLI when executed directly (not when imported)
286
272
  if (import.meta.main) {
287
- main();
273
+ runMain(main);
288
274
  }
@@ -35,10 +35,6 @@ export interface IndexedDBCollectionConfig<TTable extends Table> {
35
35
  * Ref to the IndexedDB database instance
36
36
  */
37
37
  indexedDBRef: React.RefObject<IDBDatabaseLike | null>;
38
- /**
39
- * The database name (for perf markers)
40
- */
41
- dbName: string;
42
38
  /**
43
39
  * The Drizzle table definition (used for schema and type inference only)
44
40
  */
@@ -10,7 +10,6 @@ import {
10
10
  import {
11
11
  createCollection,
12
12
  type InferSchemaInput,
13
- type UtilsRecord,
14
13
  type Collection,
15
14
  type InferSchemaOutput,
16
15
  type SyncMode,
@@ -26,7 +25,7 @@ import type {
26
25
  SelectSchema,
27
26
  GetTableFromSchema,
28
27
  InferCollectionFromTable,
29
- ExternalSyncHandler,
28
+ CollectionUtils,
30
29
  } from "@firtoz/drizzle-utils";
31
30
  import {
32
31
  type Migration,
@@ -38,21 +37,21 @@ import type { IDBProxySyncMessage } from "../proxy/idb-proxy-types";
38
37
 
39
38
  interface CollectionCacheEntry {
40
39
  // biome-ignore lint/suspicious/noExplicitAny: Cache needs to store collections of various types
41
- collection: Collection<any, string>;
40
+ collection: Collection<any, string, CollectionUtils<any>, any, any>;
42
41
  refCount: number;
43
- // biome-ignore lint/suspicious/noExplicitAny: External sync needs to accept any item type
44
- pushExternalSync: ExternalSyncHandler<any>;
45
42
  }
46
43
 
47
44
  // Type for migration functions (generated by Drizzle)
48
45
 
49
- type IndexedDbCollection<
46
+ export type IndexedDbCollection<
50
47
  TSchema extends Record<string, unknown>,
51
48
  TTableName extends keyof TSchema & string,
52
49
  > = Collection<
53
50
  InferSchemaOutput<SelectSchema<GetTableFromSchema<TSchema, TTableName>>>,
54
51
  IdOf<GetTableFromSchema<TSchema, TTableName>>,
55
- UtilsRecord,
52
+ CollectionUtils<
53
+ InferSchemaOutput<SelectSchema<GetTableFromSchema<TSchema, TTableName>>>
54
+ >,
56
55
  SelectSchema<GetTableFromSchema<TSchema, TTableName>>,
57
56
  InferSchemaInput<InsertSchema<GetTableFromSchema<TSchema, TTableName>>>
58
57
  >;
@@ -195,7 +194,6 @@ export function DrizzleIndexedDBProvider<
195
194
  // Create collection options
196
195
  const collectionConfig = indexedDBCollectionOptions({
197
196
  indexedDBRef,
198
- dbName,
199
197
  table,
200
198
  storeName: actualTableName,
201
199
  readyPromise: readyPromise.promise,
@@ -205,12 +203,14 @@ export function DrizzleIndexedDBProvider<
205
203
 
206
204
  // Create new collection and cache it with ref count 0
207
205
  // The collection will wait for readyPromise before accessing the database
208
- const collection = createCollection(collectionConfig);
206
+ // Cast is safe: our collectionConfig provides CollectionUtils, but createCollection types it as UtilsRecord
207
+ const collection = createCollection(
208
+ collectionConfig,
209
+ ) as CollectionCacheEntry["collection"];
209
210
 
210
211
  collections.set(cacheKey, {
211
212
  collection,
212
213
  refCount: 0,
213
- pushExternalSync: collectionConfig.utils.pushExternalSync,
214
214
  });
215
215
  }
216
216
 
@@ -218,7 +218,7 @@ export function DrizzleIndexedDBProvider<
218
218
  return collections.get(cacheKey)!
219
219
  .collection as unknown as IndexedDbCollection<TSchema, TTableName>;
220
220
  },
221
- [collections, schema, readyPromise.promise, debug, dbName, syncMode],
221
+ [collections, schema, readyPromise.promise, debug, syncMode],
222
222
  );
223
223
 
224
224
  const incrementRefCount: DrizzleIndexedDBContextValue<TSchema>["incrementRefCount"] =
@@ -257,30 +257,31 @@ export function DrizzleIndexedDBProvider<
257
257
  const actualStoreName = getTableName(table as Table);
258
258
  if (actualStoreName === message.storeName) {
259
259
  const entry = collections.get(tableName);
260
- if (entry?.pushExternalSync) {
260
+ if (entry) {
261
+ const { pushExternalSync } = entry.collection.utils;
261
262
  // Route sync message to collection
262
263
  switch (message.type) {
263
264
  case "sync:add":
264
- entry.pushExternalSync({
265
+ pushExternalSync({
265
266
  type: "insert",
266
267
  items: message.items,
267
268
  });
268
269
  break;
269
270
  case "sync:put":
270
- entry.pushExternalSync({
271
+ pushExternalSync({
271
272
  type: "update",
272
273
  items: message.items,
273
274
  });
274
275
  break;
275
276
  case "sync:delete":
276
277
  // For delete, construct items with id
277
- entry.pushExternalSync({
278
+ pushExternalSync({
278
279
  type: "delete",
279
280
  items: message.keys.map((key) => ({ id: key })),
280
281
  });
281
282
  break;
282
283
  case "sync:clear":
283
- entry.pushExternalSync({
284
+ pushExternalSync({
284
285
  type: "truncate",
285
286
  });
286
287
  break;
package/src/index.ts CHANGED
@@ -39,12 +39,20 @@ export {
39
39
  type IndexedDBSyncItem,
40
40
  } from "./collections/indexeddb-collection";
41
41
 
42
+ // Standalone Collection (for use outside React)
43
+ export {
44
+ createStandaloneCollection,
45
+ type StandaloneCollection,
46
+ type StandaloneCollectionConfig,
47
+ } from "./standalone-collection";
48
+
42
49
  // IndexedDB Provider
43
50
  export {
44
51
  DrizzleIndexedDBProvider,
45
52
  DrizzleIndexedDBContext,
46
53
  useIndexedDBCollection,
47
54
  type DrizzleIndexedDBContextValue,
55
+ type IndexedDbCollection,
48
56
  } from "./context/DrizzleIndexedDBProvider";
49
57
 
50
58
  export {
@@ -0,0 +1,372 @@
1
+ import {
2
+ createCollection,
3
+ type Collection,
4
+ type InferSchemaInput,
5
+ type InferSchemaOutput,
6
+ type SyncMode,
7
+ type Transaction,
8
+ type WritableDeep,
9
+ } from "@tanstack/db";
10
+ import type { Table } from "drizzle-orm";
11
+ import type {
12
+ IdOf,
13
+ InsertSchema,
14
+ SelectSchema,
15
+ CollectionUtils,
16
+ } from "@firtoz/drizzle-utils";
17
+ import {
18
+ indexedDBCollectionOptions,
19
+ type IndexedDBCollectionConfig,
20
+ } from "./collections/indexeddb-collection";
21
+ import {
22
+ migrateIndexedDBWithFunctions,
23
+ type Migration,
24
+ } from "./function-migrator";
25
+ import { openIndexedDb } from "./idb-operations";
26
+ import type { IDBCreator, IDBDatabaseLike } from "./idb-types";
27
+
28
+ /**
29
+ * Configuration for creating a standalone IndexedDB collection
30
+ */
31
+ export interface StandaloneCollectionConfig<TTable extends Table> {
32
+ /**
33
+ * Name of the IndexedDB database
34
+ */
35
+ dbName: string;
36
+ /**
37
+ * The Drizzle table definition
38
+ */
39
+ table: TTable;
40
+ /**
41
+ * The name of the IndexedDB object store (defaults to table name)
42
+ */
43
+ storeName?: string;
44
+ /**
45
+ * Migrations to apply (optional)
46
+ */
47
+ migrations?: Migration[];
48
+ /**
49
+ * Custom database creator (for testing/mocking)
50
+ */
51
+ dbCreator?: IDBCreator;
52
+ /**
53
+ * Enable debug logging
54
+ */
55
+ debug?: boolean;
56
+ /**
57
+ * Sync mode: 'eager' (immediate) or 'lazy' (on-demand)
58
+ */
59
+ syncMode?: SyncMode;
60
+ }
61
+
62
+ /**
63
+ * Type for the underlying collection
64
+ */
65
+ type InternalCollection<TTable extends Table> = Collection<
66
+ InferSchemaOutput<SelectSchema<TTable>>,
67
+ IdOf<TTable>,
68
+ CollectionUtils<InferSchemaOutput<SelectSchema<TTable>>>,
69
+ SelectSchema<TTable>,
70
+ InferSchemaInput<InsertSchema<TTable>>
71
+ >;
72
+
73
+ /**
74
+ * Transaction type for mutations
75
+ */
76
+ type MutationTransaction<TTable extends Table> = Transaction<
77
+ InferSchemaOutput<SelectSchema<TTable>>
78
+ >;
79
+
80
+ /**
81
+ * Insert input type (what you pass to insert)
82
+ */
83
+ type InsertInput<TTable extends Table> = InferSchemaInput<InsertSchema<TTable>>;
84
+
85
+ /**
86
+ * Item type (what you get back from getAll, etc.)
87
+ */
88
+ type ItemType<TTable extends Table> = InferSchemaOutput<SelectSchema<TTable>>;
89
+
90
+ /**
91
+ * Writable draft type for update callbacks
92
+ */
93
+ type DraftType<TTable extends Table> = WritableDeep<InsertInput<TTable>>;
94
+
95
+ /**
96
+ * Standalone IndexedDB collection API
97
+ */
98
+ export interface StandaloneCollection<TTable extends Table> {
99
+ /**
100
+ * Promise that resolves when the collection is ready
101
+ */
102
+ ready: Promise<void>;
103
+
104
+ /**
105
+ * Check if the collection is ready (sync)
106
+ */
107
+ isReady(): boolean;
108
+
109
+ /**
110
+ * Get all items (sync - returns current state)
111
+ */
112
+ getAll(): ItemType<TTable>[];
113
+
114
+ /**
115
+ * Get an item by key (sync)
116
+ */
117
+ get(key: IdOf<TTable>): ItemType<TTable> | undefined;
118
+
119
+ /**
120
+ * Insert item(s)
121
+ * @returns Promise that resolves when persisted
122
+ */
123
+ insert(
124
+ data: InsertInput<TTable> | InsertInput<TTable>[],
125
+ callback?: (transaction: MutationTransaction<TTable>) => void,
126
+ ): Promise<MutationTransaction<TTable>>;
127
+
128
+ /**
129
+ * Update an item by key using a callback that receives a draft
130
+ * @returns Promise that resolves when persisted
131
+ */
132
+ update(
133
+ key: IdOf<TTable>,
134
+ updater: (draft: DraftType<TTable>) => void,
135
+ callback?: (transaction: MutationTransaction<TTable>) => void,
136
+ ): Promise<MutationTransaction<TTable>>;
137
+
138
+ /**
139
+ * Delete item(s) by key
140
+ * @returns Promise that resolves when persisted
141
+ */
142
+ delete(
143
+ key: IdOf<TTable> | IdOf<TTable>[],
144
+ callback?: (transaction: MutationTransaction<TTable>) => void,
145
+ ): Promise<MutationTransaction<TTable>>;
146
+
147
+ /**
148
+ * Clear all items from the store
149
+ * @returns Promise that resolves when truncate is complete
150
+ */
151
+ truncate(): Promise<void>;
152
+
153
+ /**
154
+ * Access to collection utils (truncate, pushExternalSync)
155
+ */
156
+ utils: CollectionUtils<ItemType<TTable>>;
157
+
158
+ /**
159
+ * The underlying TanStack DB collection (for advanced usage)
160
+ */
161
+ collection: InternalCollection<TTable>;
162
+
163
+ /**
164
+ * The IndexedDB database instance (available after ready)
165
+ */
166
+ db: IDBDatabaseLike | null;
167
+
168
+ /**
169
+ * Close the database connection
170
+ */
171
+ close(): void;
172
+ }
173
+
174
+ /**
175
+ * Create a standalone IndexedDB collection for use outside of React.
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * const db = await createStandaloneCollection({
180
+ * dbName: "myapp.db",
181
+ * table: schema.todos,
182
+ * migrations,
183
+ * });
184
+ *
185
+ * // Wait for ready
186
+ * await db.ready;
187
+ *
188
+ * // Get all items
189
+ * const items = db.getAll();
190
+ *
191
+ * // Insert
192
+ * await db.insert({ title: "New todo" });
193
+ *
194
+ * // Update
195
+ * await db.update(itemId, { title: "Updated" });
196
+ *
197
+ * // Delete
198
+ * await db.delete(itemId);
199
+ *
200
+ * // Truncate
201
+ * await db.truncate();
202
+ *
203
+ * // Clean up
204
+ * db.close();
205
+ * ```
206
+ */
207
+ export function createStandaloneCollection<TTable extends Table>(
208
+ config: StandaloneCollectionConfig<TTable>,
209
+ ): StandaloneCollection<TTable> {
210
+ const {
211
+ dbName,
212
+ table,
213
+ storeName = (table as unknown as { _: { name: string } })._.name,
214
+ migrations = [],
215
+ dbCreator,
216
+ debug = false,
217
+ syncMode = "eager",
218
+ } = config;
219
+
220
+ // Create ready promise
221
+ let resolveReady: () => void;
222
+ const readyPromise = new Promise<void>((resolve) => {
223
+ resolveReady = resolve;
224
+ });
225
+
226
+ // Database ref
227
+ const indexedDBRef: { current: IDBDatabaseLike | null } = { current: null };
228
+
229
+ // Initialize database
230
+ const initDB = async () => {
231
+ try {
232
+ if (migrations.length === 0) {
233
+ if (debug) {
234
+ console.log(
235
+ `[StandaloneCollection] Opening database "${dbName}" directly`,
236
+ );
237
+ }
238
+ indexedDBRef.current = await openIndexedDb(dbName, dbCreator);
239
+ } else {
240
+ if (debug) {
241
+ console.log(`[StandaloneCollection] Migrating database "${dbName}"`);
242
+ }
243
+ indexedDBRef.current = await migrateIndexedDBWithFunctions(
244
+ dbName,
245
+ migrations,
246
+ debug,
247
+ dbCreator,
248
+ );
249
+ }
250
+
251
+ if (debug) {
252
+ console.log(`[StandaloneCollection] Database "${dbName}" initialized`);
253
+ }
254
+ // biome-ignore lint/style/noNonNullAssertion: resolveReady is set in promise constructor
255
+ resolveReady!();
256
+ } catch (error) {
257
+ console.error(
258
+ `[StandaloneCollection] Failed to initialize database "${dbName}":`,
259
+ error,
260
+ );
261
+ throw error;
262
+ }
263
+ };
264
+
265
+ // Start initialization
266
+ initDB();
267
+
268
+ // Create collection config
269
+ const collectionConfig = indexedDBCollectionOptions({
270
+ indexedDBRef,
271
+ table,
272
+ storeName,
273
+ readyPromise,
274
+ debug,
275
+ syncMode,
276
+ } as IndexedDBCollectionConfig<TTable>);
277
+
278
+ // Create the collection
279
+ const collection = createCollection(
280
+ collectionConfig,
281
+ ) as unknown as InternalCollection<TTable>;
282
+
283
+ // Wait for collection to be ready
284
+ const collectionReady = new Promise<void>((resolve) => {
285
+ if (collection.isReady()) {
286
+ resolve();
287
+ return;
288
+ }
289
+ collection.preload();
290
+ collection.onFirstReady(() => resolve());
291
+ });
292
+
293
+ // Combined ready promise
294
+ const ready = Promise.all([readyPromise, collectionReady]).then(() => {});
295
+
296
+ // Helper to wait for transaction to persist
297
+ const waitForPersist = (
298
+ // biome-ignore lint/suspicious/noExplicitAny: Transaction types are complex, runtime is correct
299
+ transaction: any,
300
+ // biome-ignore lint/suspicious/noExplicitAny: Transaction types are complex, runtime is correct
301
+ callback?: (transaction: any) => void,
302
+ ): Promise<MutationTransaction<TTable>> => {
303
+ if (callback) {
304
+ callback(transaction);
305
+ }
306
+ return transaction.isPersisted.promise.then(() => transaction);
307
+ };
308
+
309
+ return {
310
+ ready,
311
+
312
+ isReady(): boolean {
313
+ return collection.isReady();
314
+ },
315
+
316
+ getAll(): ItemType<TTable>[] {
317
+ return collection.toArray;
318
+ },
319
+
320
+ get(key: IdOf<TTable>): ItemType<TTable> | undefined {
321
+ return collection.state.get(key);
322
+ },
323
+
324
+ insert(
325
+ data: InsertInput<TTable> | InsertInput<TTable>[],
326
+ callback?: (transaction: MutationTransaction<TTable>) => void,
327
+ ): Promise<MutationTransaction<TTable>> {
328
+ const items = Array.isArray(data) ? data : [data];
329
+ // @ts-expect-error - Type inference is complex here but runtime is correct
330
+ const transaction = collection.insert(...items);
331
+ return waitForPersist(transaction, callback);
332
+ },
333
+
334
+ update(
335
+ key: IdOf<TTable>,
336
+ updater: (draft: DraftType<TTable>) => void,
337
+ callback?: (transaction: MutationTransaction<TTable>) => void,
338
+ ): Promise<MutationTransaction<TTable>> {
339
+ const transaction = collection.update(
340
+ key,
341
+ updater as (draft: any) => void,
342
+ );
343
+ return waitForPersist(transaction, callback);
344
+ },
345
+
346
+ delete(
347
+ key: IdOf<TTable> | IdOf<TTable>[],
348
+ callback?: (transaction: MutationTransaction<TTable>) => void,
349
+ ): Promise<MutationTransaction<TTable>> {
350
+ const keys = Array.isArray(key) ? key : [key];
351
+ const transaction = collection.delete(keys);
352
+ return waitForPersist(transaction, callback);
353
+ },
354
+
355
+ truncate(): Promise<void> {
356
+ return collection.utils.truncate();
357
+ },
358
+
359
+ utils: collection.utils,
360
+
361
+ collection,
362
+
363
+ get db(): IDBDatabaseLike | null {
364
+ return indexedDBRef.current;
365
+ },
366
+
367
+ close(): void {
368
+ indexedDBRef.current?.close();
369
+ indexedDBRef.current = null;
370
+ },
371
+ };
372
+ }