@firtoz/drizzle-indexeddb 0.4.1 → 0.4.3

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,17 @@
1
1
  # @firtoz/drizzle-indexeddb
2
2
 
3
+ ## 0.4.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [`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
8
+
9
+ ## 0.4.2
10
+
11
+ ### Patch Changes
12
+
13
+ - [`58afa0a`](https://github.com/firtoz/fullstack-toolkit/commit/58afa0a5365f55f536e50194a73f847293102e7f) Thanks [@firtoz](https://github.com/firtoz)! - Hopefully this should work
14
+
3
15
  ## 0.4.1
4
16
 
5
17
  ### 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.1",
3
+ "version": "0.4.3",
4
4
  "description": "IndexedDB migrations powered by Drizzle ORM",
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -42,9 +42,7 @@
42
42
  "typecheck": "tsc --noEmit -p ./tsconfig.json",
43
43
  "lint": "biome check --write src",
44
44
  "lint:ci": "biome ci src",
45
- "format": "biome format src --write",
46
- "test": "bun test --pass-with-no-tests",
47
- "test:watch": "bun test --watch"
45
+ "format": "biome format src --write"
48
46
  },
49
47
  "keywords": [
50
48
  "typescript",
@@ -69,17 +67,24 @@
69
67
  "publishConfig": {
70
68
  "access": "public"
71
69
  },
72
- "dependencies": {
70
+ "peerDependencies": {
71
+ "@firtoz/drizzle-utils": ">=0.3.0",
72
+ "@tanstack/db": ">=0.5.0",
73
+ "drizzle-orm": ">=0.44.0",
74
+ "drizzle-valibot": ">=0.4.0",
75
+ "react": ">=18.0.0",
76
+ "valibot": ">=1.0.0"
77
+ },
78
+ "devDependencies": {
73
79
  "@firtoz/drizzle-utils": "^0.3.0",
74
80
  "@tanstack/db": "^0.5.11",
81
+ "@types/react": "^19.2.7",
75
82
  "drizzle-orm": "^0.45.0",
76
83
  "drizzle-valibot": "^0.4.2",
84
+ "react": "^19.2.1",
77
85
  "valibot": "^1.2.0"
78
86
  },
79
- "peerDependencies": {
80
- "react": "^19.2.1"
81
- },
82
- "devDependencies": {
83
- "@types/react": "^19.2.7"
87
+ "dependencies": {
88
+ "citty": "^0.1.6"
84
89
  }
85
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
  }
@@ -134,10 +134,9 @@ export function DrizzleIndexedDBProvider<
134
134
  });
135
135
 
136
136
  useEffect(() => {
137
+ let db: IDBDatabaseLike | null = null;
137
138
  const initDB = async () => {
138
139
  try {
139
- let db: IDBDatabaseLike;
140
-
141
140
  if (migrations.length === 0) {
142
141
  // Open database directly without migration logic
143
142
  db = await openIndexedDb(dbName, dbCreator);
@@ -161,11 +160,11 @@ export function DrizzleIndexedDBProvider<
161
160
 
162
161
  // Cleanup on unmount
163
162
  return () => {
164
- if (indexedDB) {
165
- indexedDB.close();
163
+ if (db) {
164
+ db.close();
166
165
  }
167
166
  };
168
- }, [dbName, migrations, migrateFunction, debug, readyPromise]);
167
+ }, [dbName, migrations, migrateFunction, debug, readyPromise, dbCreator]);
169
168
 
170
169
  // Collection cache with ref counting
171
170
  const collections = useMemo<Map<string, CollectionCacheEntry>>(
@@ -219,15 +218,7 @@ export function DrizzleIndexedDBProvider<
219
218
  return collections.get(cacheKey)!
220
219
  .collection as unknown as IndexedDbCollection<TSchema, TTableName>;
221
220
  },
222
- [
223
- indexedDBRef,
224
- collections,
225
- schema,
226
- readyPromise.promise,
227
- debug,
228
- dbName,
229
- syncMode,
230
- ],
221
+ [collections, schema, readyPromise.promise, debug, dbName, syncMode],
231
222
  );
232
223
 
233
224
  const incrementRefCount: DrizzleIndexedDBContextValue<TSchema>["incrementRefCount"] =
@@ -1,4 +1,4 @@
1
- import { useContext } from "react";
1
+ import { useCallback, useContext } from "react";
2
2
  import {
3
3
  DrizzleIndexedDBContext,
4
4
  useIndexedDBCollection,
@@ -28,10 +28,15 @@ export function useDrizzleIndexedDB<
28
28
  );
29
29
  }
30
30
 
31
+ const useCollection = useCallback(
32
+ <TTableName extends keyof TSchema & string>(tableName: TTableName) =>
33
+ // biome-ignore lint/correctness/useHookAtTopLevel: This is on purpose.
34
+ useIndexedDBCollection(context, tableName),
35
+ [context],
36
+ );
37
+
31
38
  return {
32
- useCollection: <TTableName extends keyof TSchema & string>(
33
- tableName: TTableName,
34
- ) => useIndexedDBCollection(context, tableName),
39
+ useCollection,
35
40
  indexedDB: context.indexedDB,
36
41
  };
37
42
  }
@@ -23,9 +23,14 @@ export async function openIndexedDb(
23
23
  */
24
24
  const defaultIDBDeleter: IDBDeleter = (name: string): Promise<void> => {
25
25
  return new Promise((resolve, reject) => {
26
- const request = indexedDB.deleteDatabase(name);
27
- request.onerror = () => reject(request.error);
28
- request.onsuccess = () => resolve();
26
+ try {
27
+ const request = indexedDB.deleteDatabase(name);
28
+ request.onerror = () => reject(request.error);
29
+ request.onsuccess = () => resolve();
30
+ } catch (error) {
31
+ console.error("Error deleting database", error);
32
+ reject(error);
33
+ }
29
34
  });
30
35
  };
31
36
 
@@ -104,12 +104,17 @@ class NativeIDBDatabase implements IDBDatabaseLike {
104
104
  }
105
105
 
106
106
  return new Promise((resolve, reject) => {
107
- const transaction = this.db.transaction(storeName, "readonly");
108
- const store = transaction.objectStore(storeName);
109
- const request = store.getAll();
110
-
111
- request.onsuccess = () => resolve(request.result as T[]);
112
- request.onerror = () => reject(request.error);
107
+ try {
108
+ const transaction = this.db.transaction(storeName, "readonly");
109
+ const store = transaction.objectStore(storeName);
110
+ const request = store.getAll();
111
+
112
+ request.onsuccess = () => resolve(request.result as T[]);
113
+ request.onerror = () => reject(request.error);
114
+ } catch (error) {
115
+ console.error("Error getting all items", error);
116
+ reject(error);
117
+ }
113
118
  });
114
119
  }
115
120
 
@@ -119,14 +124,19 @@ class NativeIDBDatabase implements IDBDatabaseLike {
119
124
  keyRange?: KeyRangeSpec,
120
125
  ): Promise<T[]> {
121
126
  return new Promise((resolve, reject) => {
122
- const transaction = this.db.transaction(storeName, "readonly");
123
- const store = transaction.objectStore(storeName);
124
- const index = store.index(indexName);
125
- const range = keyRange ? createKeyRange(keyRange) : undefined;
126
- const request = index.getAll(range);
127
-
128
- request.onsuccess = () => resolve(request.result as T[]);
129
- request.onerror = () => reject(request.error);
127
+ try {
128
+ const transaction = this.db.transaction(storeName, "readonly");
129
+ const store = transaction.objectStore(storeName);
130
+ const index = store.index(indexName);
131
+ const range = keyRange ? createKeyRange(keyRange) : undefined;
132
+ const request = index.getAll(range);
133
+
134
+ request.onsuccess = () => resolve(request.result as T[]);
135
+ request.onerror = () => reject(request.error);
136
+ } catch (error) {
137
+ console.error("Error getting all items by index", error);
138
+ reject(error);
139
+ }
130
140
  });
131
141
  }
132
142
 
@@ -135,72 +145,98 @@ class NativeIDBDatabase implements IDBDatabaseLike {
135
145
  key: IDBValidKey,
136
146
  ): Promise<T | undefined> {
137
147
  return new Promise((resolve, reject) => {
138
- const transaction = this.db.transaction(storeName, "readonly");
139
- const store = transaction.objectStore(storeName);
140
- const request = store.get(key);
141
-
142
- request.onsuccess = () => resolve(request.result as T | undefined);
143
- request.onerror = () => reject(request.error);
148
+ try {
149
+ const transaction = this.db.transaction(storeName, "readonly");
150
+ const store = transaction.objectStore(storeName);
151
+ const request = store.get(key);
152
+
153
+ request.onsuccess = () => resolve(request.result as T | undefined);
154
+ request.onerror = () => reject(request.error);
155
+ } catch (error) {
156
+ console.error("Error getting item", error);
157
+ reject(error);
158
+ }
144
159
  });
145
160
  }
146
161
 
147
162
  async add(storeName: string, items: unknown[]): Promise<void> {
148
163
  return new Promise((resolve, reject) => {
149
- const transaction = this.db.transaction(storeName, "readwrite");
150
- const store = transaction.objectStore(storeName);
164
+ try {
165
+ const transaction = this.db.transaction(storeName, "readwrite");
166
+ const store = transaction.objectStore(storeName);
151
167
 
152
- for (const item of items) {
153
- store.add(item);
154
- }
168
+ for (const item of items) {
169
+ store.add(item);
170
+ }
155
171
 
156
- transaction.oncomplete = () => resolve();
157
- transaction.onerror = () => reject(transaction.error);
158
- transaction.onabort = () => reject(new Error("Transaction aborted"));
172
+ transaction.oncomplete = () => resolve();
173
+ transaction.onerror = () => reject(transaction.error);
174
+ transaction.onabort = () => reject(new Error("Transaction aborted"));
175
+ } catch (error) {
176
+ console.error("Error adding items", error);
177
+ reject(error);
178
+ }
159
179
  });
160
180
  }
161
181
 
162
182
  async put(storeName: string, items: unknown[]): Promise<void> {
163
183
  return new Promise((resolve, reject) => {
164
- const transaction = this.db.transaction(storeName, "readwrite");
165
- const store = transaction.objectStore(storeName);
184
+ try {
185
+ const transaction = this.db.transaction(storeName, "readwrite");
186
+ const store = transaction.objectStore(storeName);
166
187
 
167
- for (const item of items) {
168
- store.put(item);
169
- }
188
+ for (const item of items) {
189
+ store.put(item);
190
+ }
170
191
 
171
- transaction.oncomplete = () => resolve();
172
- transaction.onerror = () => reject(transaction.error);
173
- transaction.onabort = () => reject(new Error("Transaction aborted"));
192
+ transaction.oncomplete = () => resolve();
193
+ transaction.onerror = () => reject(transaction.error);
194
+ transaction.onabort = () => reject(new Error("Transaction aborted"));
195
+ } catch (error) {
196
+ console.error("Error putting items", error);
197
+ reject(error);
198
+ }
174
199
  });
175
200
  }
176
201
 
177
202
  async delete(storeName: string, keys: IDBValidKey[]): Promise<void> {
178
203
  return new Promise((resolve, reject) => {
179
- const transaction = this.db.transaction(storeName, "readwrite");
180
- const store = transaction.objectStore(storeName);
204
+ try {
205
+ const transaction = this.db.transaction(storeName, "readwrite");
206
+ const store = transaction.objectStore(storeName);
181
207
 
182
- for (const key of keys) {
183
- store.delete(key);
184
- }
208
+ for (const key of keys) {
209
+ store.delete(key);
210
+ }
185
211
 
186
- transaction.oncomplete = () => resolve();
187
- transaction.onerror = () => reject(transaction.error);
188
- transaction.onabort = () => reject(new Error("Transaction aborted"));
212
+ transaction.oncomplete = () => resolve();
213
+ transaction.onerror = () => reject(transaction.error);
214
+ transaction.onabort = () => reject(new Error("Transaction aborted"));
215
+ } catch (error) {
216
+ console.error("Error deleting items", error);
217
+ reject(error);
218
+ }
189
219
  });
190
220
  }
191
221
 
192
222
  async clear(storeName: string): Promise<void> {
193
223
  return new Promise((resolve, reject) => {
194
- const transaction = this.db.transaction(storeName, "readwrite");
195
- const store = transaction.objectStore(storeName);
196
- const request = store.clear();
197
-
198
- request.onsuccess = () => resolve();
199
- request.onerror = () => reject(request.error);
224
+ try {
225
+ const transaction = this.db.transaction(storeName, "readwrite");
226
+ const store = transaction.objectStore(storeName);
227
+ const request = store.clear();
228
+
229
+ request.onsuccess = () => resolve();
230
+ request.onerror = () => reject(request.error);
231
+ } catch (error) {
232
+ console.error("Error clearing store", error);
233
+ reject(error);
234
+ }
200
235
  });
201
236
  }
202
237
 
203
238
  close(): void {
239
+ console.log("Closing database");
204
240
  this.db.close();
205
241
  }
206
242
  }
@@ -319,37 +355,42 @@ export const defaultIDBCreator: IDBCreator = (
319
355
  options?: IDBOpenOptions,
320
356
  ): Promise<IDBDatabaseLike> => {
321
357
  return new Promise((resolve, reject) => {
322
- const request = options?.version
323
- ? indexedDB.open(name, options.version)
324
- : indexedDB.open(name);
325
-
326
- request.onerror = () => reject(request.error);
358
+ try {
359
+ const request = options?.version
360
+ ? indexedDB.open(name, options.version)
361
+ : indexedDB.open(name);
327
362
 
328
- request.onblocked = () => {
329
- setTimeout(() => {
330
- reject(new Error("Database upgrade blocked - close other tabs"));
331
- }, 3000);
332
- };
363
+ request.onerror = () => reject(request.error);
333
364
 
334
- request.onupgradeneeded = (event) => {
335
- if (options?.onUpgrade) {
336
- const db = request.result;
337
- const transaction = (event.target as IDBOpenDBRequest).transaction;
338
- if (!transaction) {
339
- reject(new Error("No transaction during upgrade"));
340
- return;
365
+ request.onblocked = () => {
366
+ setTimeout(() => {
367
+ reject(new Error("Database upgrade blocked - close other tabs"));
368
+ }, 3000);
369
+ };
370
+
371
+ request.onupgradeneeded = (event) => {
372
+ if (options?.onUpgrade) {
373
+ const db = request.result;
374
+ const transaction = (event.target as IDBOpenDBRequest).transaction;
375
+ if (!transaction) {
376
+ reject(new Error("No transaction during upgrade"));
377
+ return;
378
+ }
379
+ // Create an upgrade-mode database wrapper
380
+ const upgradeDb = new UpgradeModeDatabase(db, transaction);
381
+ try {
382
+ options.onUpgrade(upgradeDb);
383
+ } catch (error) {
384
+ transaction.abort();
385
+ reject(error);
386
+ }
341
387
  }
342
- // Create an upgrade-mode database wrapper
343
- const upgradeDb = new UpgradeModeDatabase(db, transaction);
344
- try {
345
- options.onUpgrade(upgradeDb);
346
- } catch (error) {
347
- transaction.abort();
348
- reject(error);
349
- }
350
- }
351
- };
388
+ };
352
389
 
353
- request.onsuccess = () => resolve(new NativeIDBDatabase(request.result));
390
+ request.onsuccess = () => resolve(new NativeIDBDatabase(request.result));
391
+ } catch (error) {
392
+ console.error("Error creating database", error);
393
+ reject(error);
394
+ }
354
395
  });
355
396
  };