@firtoz/drizzle-indexeddb 0.2.0 → 0.3.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,27 @@
1
1
  # @firtoz/drizzle-indexeddb
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`5e854a6`](https://github.com/firtoz/fullstack-toolkit/commit/5e854a62236a811918a47037a59df23329856614) Thanks [@firtoz](https://github.com/firtoz)! - ### Breaking Changes
8
+
9
+ - Removed `migrateIndexedDB` and `IndexedDBMigrationConfig` exports - use `migrateIndexedDBWithFunctions` instead
10
+ - Removed snapshot-based migration system in favor of function-based migrations
11
+
12
+ ### New Features
13
+
14
+ - Added `drizzle-indexeddb-generate` CLI tool to generate IndexedDB migration functions from Drizzle snapshots
15
+ - Added `generateIndexedDBMigrations` export for programmatic migration generation
16
+ - Added `./generate` export path
17
+
18
+ ### Migration Guide
19
+
20
+ Instead of importing snapshots directly and using `migrateIndexedDB`, you now:
21
+
22
+ 1. Run `bun drizzle-indexeddb-generate` (or `npx drizzle-indexeddb-generate`) after `drizzle-kit generate`
23
+ 2. Import the generated migrations and use `migrateIndexedDBWithFunctions`
24
+
3
25
  ## 0.2.0
4
26
 
5
27
  ### Minor Changes
@@ -41,18 +63,6 @@
41
63
  - Sync configuration for real-time updates
42
64
  - Works seamlessly with React hooks
43
65
 
44
- ### Snapshot-Based Migration
45
-
46
- **`migrateIndexedDB(dbName, config, debug?)`** - Automatically migrates IndexedDB databases using Drizzle snapshot files:
47
-
48
- - Reads Drizzle journal and snapshot files
49
- - Tracks applied migrations in `__drizzle_migrations` store
50
- - Creates/updates object stores and indexes based on schema changes
51
- - Handles table deletion, index changes, and schema evolution
52
- - Incremental migrations - only applies pending changes
53
- - Validates primary key structure changes (requires manual migration if keys change)
54
- - Performance logging when debug mode is enabled
55
-
56
66
  ### Function-Based Migration
57
67
 
58
68
  **`migrateIndexedDBWithFunctions(dbName, migrations, debug?)`** - Run migrations using custom migration functions:
@@ -87,19 +97,15 @@
87
97
  ## Example
88
98
 
89
99
  ```typescript
90
- import { migrateIndexedDB } from "@firtoz/drizzle-indexeddb";
91
- import journal from "./drizzle/journal.json";
92
- import * as snapshots from "./drizzle/snapshots";
100
+ import { migrateIndexedDBWithFunctions } from "@firtoz/drizzle-indexeddb";
101
+ import migrations from "./drizzle/indexeddb-migrations";
93
102
 
94
- // Migrate database using Drizzle snapshots
95
- const db = await migrateIndexedDB(
103
+ // Migrate database using function-based migrations
104
+ const db = await migrateIndexedDBWithFunctions(
96
105
  "my-app-db",
97
- {
98
- journal,
99
- snapshots,
100
- },
101
- true
102
- ); // debug mode
106
+ migrations,
107
+ true // debug mode
108
+ );
103
109
 
104
110
  // Use with TanStack DB
105
111
  import { createCollection } from "@tanstack/db";
@@ -136,9 +142,9 @@
136
142
 
137
143
  ## Migration Workflow
138
144
 
139
- 1. Generate Drizzle snapshots: `drizzle-kit generate`
140
- 2. Import journal and snapshots
141
- 3. Call `migrateIndexedDB()` on app startup
145
+ 1. Generate Drizzle migrations: `drizzle-kit generate`
146
+ 2. Generate IndexedDB migrations: `bun drizzle-indexeddb-generate`
147
+ 3. Import migrations and call `migrateIndexedDBWithFunctions()` on app startup
142
148
  4. Database automatically updates to latest schema
143
149
 
144
150
  ## Dependencies
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # @firtoz/drizzle-indexeddb
2
2
 
3
- TanStack DB collections backed by IndexedDB with automatic migrations powered by Drizzle ORM snapshots. Build reactive, type-safe IndexedDB applications with the power of Drizzle's schema management.
3
+ TanStack DB collections backed by IndexedDB with automatic migrations powered by Drizzle ORM. Build reactive, type-safe IndexedDB applications with the power of Drizzle's schema management.
4
4
 
5
5
  > **⚠️ Early WIP Notice:** This package is in very early development and is **not production-ready**. It is TypeScript-only and may have breaking changes. While I (the maintainer) have limited time, I'm open to PRs for features, bug fixes, or additional support (like JS builds). Please feel free to try it out and contribute! See [CONTRIBUTING.md](../../CONTRIBUTING.md) for details.
6
6
 
7
- > **Note:** This package currently builds on top of Drizzle's SQLite integration (using `drizzle-orm/sqlite-core` types and snapshots) until Drizzle adds native IndexedDB support. The migration system reads Drizzle's SQLite snapshots and translates them into IndexedDB object stores and indexes.
7
+ > **Note:** This package currently builds on top of Drizzle's SQLite integration (using `drizzle-orm/sqlite-core` types) until Drizzle adds native IndexedDB support. The migration system uses function-based migrations generated from Drizzle's SQLite migrations to create IndexedDB object stores and indexes.
8
8
 
9
9
  ## Installation
10
10
 
@@ -19,8 +19,7 @@ npm install @firtoz/drizzle-indexeddb @firtoz/drizzle-utils drizzle-orm @tanstac
19
19
  - 🔍 **Query optimization** - Leverage IndexedDB indexes for fast queries
20
20
  - 📦 **Soft deletes** - Built-in support for `deletedAt` column
21
21
  - ⚛️ **React hooks** - Provider and hooks for easy React integration
22
- - 🔄 **Snapshot-based migrations** - Use Drizzle's generated snapshots to migrate IndexedDB
23
- - 📝 **Function-based migrations** - Write custom migration functions for complex changes
22
+ - 📝 **Function-based migrations** - Generated migration functions from Drizzle schema changes
24
23
 
25
24
  ## Quick Start
26
25
 
@@ -40,22 +39,25 @@ export const todoTable = syncableTable("todos", {
40
39
  ### 2. Generate Migrations
41
40
 
42
41
  ```bash
43
- # Generate Drizzle snapshots
42
+ # Generate Drizzle migrations
44
43
  drizzle-kit generate
44
+
45
+ # Generate IndexedDB migration functions
46
+ bun drizzle-indexeddb-generate
45
47
  ```
46
48
 
47
49
  ### 3. Migrate IndexedDB
48
50
 
49
51
  ```typescript
50
52
  // db.ts
51
- import { migrateIndexedDB } from "@firtoz/drizzle-indexeddb";
52
- import journal from "./drizzle/meta/_journal.json";
53
- import * as snapshots from "./drizzle/snapshots";
54
-
55
- export const db = await migrateIndexedDB("my-app", {
56
- journal,
57
- snapshots,
58
- }, true); // Enable debug logging
53
+ import { migrateIndexedDBWithFunctions } from "@firtoz/drizzle-indexeddb";
54
+ import migrations from "./drizzle/indexeddb-migrations";
55
+
56
+ export const db = await migrateIndexedDBWithFunctions(
57
+ "my-app",
58
+ migrations,
59
+ true // Enable debug logging
60
+ );
59
61
  ```
60
62
 
61
63
  ### 4. Use with React
@@ -181,19 +183,19 @@ collection.find({
181
183
 
182
184
  ## Migration Methods
183
185
 
184
- ### Snapshot-Based Migration
186
+ ### Function-Based Migration
185
187
 
186
- Use Drizzle's snapshot files to automatically migrate your IndexedDB schema:
188
+ Use generated migration functions to migrate your IndexedDB schema:
187
189
 
188
190
  ```typescript
189
- import { migrateIndexedDB } from "@firtoz/drizzle-indexeddb";
190
- import journal from "./drizzle/meta/_journal.json";
191
- import * as snapshots from "./drizzle/snapshots";
191
+ import { migrateIndexedDBWithFunctions } from "@firtoz/drizzle-indexeddb";
192
+ import migrations from "./drizzle/indexeddb-migrations";
192
193
 
193
- const db = await migrateIndexedDB("my-app-db", {
194
- journal,
195
- snapshots,
196
- }, true); // debug flag
194
+ const db = await migrateIndexedDBWithFunctions(
195
+ "my-app-db",
196
+ migrations,
197
+ true // debug flag
198
+ );
197
199
 
198
200
  console.log("Database migrated successfully!");
199
201
  ```
@@ -219,40 +221,49 @@ interface MigrationRecord {
219
221
  }
220
222
  ```
221
223
 
222
- ### Function-Based Migration
224
+ ### Custom Migration Functions
223
225
 
224
- For complex migrations that require custom logic:
226
+ For complex migrations that require custom logic, you can write migration functions directly:
225
227
 
226
228
  ```typescript
227
229
  import { migrateIndexedDBWithFunctions } from "@firtoz/drizzle-indexeddb";
228
230
 
229
231
  const migrations = [
230
232
  // Migration 0: Initial schema
231
- async (db: IDBDatabase, transaction: IDBTransaction) => {
232
- const store = db.createObjectStore("todos", { keyPath: "id" });
233
- store.createIndex("title", "title", { unique: false });
233
+ {
234
+ tag: "0000_initial",
235
+ migrate: async (db: IDBDatabase, transaction: IDBTransaction) => {
236
+ const store = db.createObjectStore("todos", { keyPath: "id" });
237
+ store.createIndex("title", "title", { unique: false });
238
+ },
234
239
  },
235
240
 
236
241
  // Migration 1: Add completed index
237
- async (db: IDBDatabase, transaction: IDBTransaction) => {
238
- const store = transaction.objectStore("todos");
239
- store.createIndex("completed", "completed", { unique: false });
242
+ {
243
+ tag: "0001_add_completed",
244
+ migrate: async (db: IDBDatabase, transaction: IDBTransaction) => {
245
+ const store = transaction.objectStore("todos");
246
+ store.createIndex("completed", "completed", { unique: false });
247
+ },
240
248
  },
241
249
 
242
250
  // Migration 2: Transform data
243
- async (db: IDBDatabase, transaction: IDBTransaction) => {
244
- const store = transaction.objectStore("todos");
245
- const todos = await new Promise<any[]>((resolve, reject) => {
246
- const req = store.getAll();
247
- req.onsuccess = () => resolve(req.result);
248
- req.onerror = () => reject(req.error);
249
- });
250
-
251
- // Transform data
252
- for (const todo of todos) {
253
- todo.priority = todo.priority || "medium";
254
- store.put(todo);
255
- }
251
+ {
252
+ tag: "0002_add_priority",
253
+ migrate: async (db: IDBDatabase, transaction: IDBTransaction) => {
254
+ const store = transaction.objectStore("todos");
255
+ const todos = await new Promise<any[]>((resolve, reject) => {
256
+ const req = store.getAll();
257
+ req.onsuccess = () => resolve(req.result);
258
+ req.onerror = () => reject(req.error);
259
+ });
260
+
261
+ // Transform data
262
+ for (const todo of todos) {
263
+ todo.priority = todo.priority || "medium";
264
+ store.put(todo);
265
+ }
266
+ },
256
267
  },
257
268
  ];
258
269
 
@@ -338,6 +349,56 @@ Useful for:
338
349
  - Clearing user data on logout
339
350
  - Testing scenarios
340
351
 
352
+ ### generateIndexedDBMigrations
353
+
354
+ Generate IndexedDB migration files from Drizzle snapshots programmatically:
355
+
356
+ ```typescript
357
+ import { generateIndexedDBMigrations } from "@firtoz/drizzle-indexeddb";
358
+
359
+ generateIndexedDBMigrations({
360
+ drizzleDir: "./drizzle", // Path to Drizzle directory (default: ./drizzle)
361
+ outputDir: "./drizzle/indexeddb-migrations", // Output directory (default: ./drizzle/indexeddb-migrations)
362
+ });
363
+ ```
364
+
365
+ ## CLI
366
+
367
+ The package includes a CLI tool to generate IndexedDB migrations from Drizzle schema snapshots.
368
+
369
+ ### Usage
370
+
371
+ ```bash
372
+ # Generate migrations (run after drizzle-kit generate)
373
+ bun drizzle-indexeddb-generate
374
+
375
+ # With custom paths
376
+ bun drizzle-indexeddb-generate --drizzle-dir ./db/drizzle
377
+ bun drizzle-indexeddb-generate --output-dir ./src/migrations
378
+
379
+ # Show help
380
+ bun drizzle-indexeddb-generate --help
381
+ ```
382
+
383
+ ### Options
384
+
385
+ | Option | Description | Default |
386
+ |--------|-------------|---------|
387
+ | `--drizzle-dir <path>` | Path to Drizzle directory | `./drizzle` |
388
+ | `--output-dir <path>` | Path to output directory | `./drizzle/indexeddb-migrations` |
389
+
390
+ ### npm scripts
391
+
392
+ Add to your `package.json`:
393
+
394
+ ```json
395
+ {
396
+ "scripts": {
397
+ "db:generate": "bun drizzle-kit generate && bun drizzle-indexeddb-generate"
398
+ }
399
+ }
400
+ ```
401
+
341
402
  ## Advanced Usage
342
403
 
343
404
  ### Custom Sync Configuration
@@ -359,13 +420,13 @@ const collection = createCollection(
359
420
 
360
421
  ```typescript
361
422
  try {
362
- const db = await migrateIndexedDB("my-app", config, true);
423
+ const db = await migrateIndexedDBWithFunctions("my-app", migrations, true);
363
424
  } catch (error) {
364
425
  console.error("Migration failed:", error);
365
426
 
366
427
  // Option 1: Delete and start fresh
367
428
  await deleteIndexedDB("my-app");
368
- const db = await migrateIndexedDB("my-app", config, true);
429
+ const db = await migrateIndexedDBWithFunctions("my-app", migrations, true);
369
430
 
370
431
  // Option 2: Handle specific errors
371
432
  if (error.message.includes("Primary key structure changed")) {
@@ -378,10 +439,10 @@ try {
378
439
 
379
440
  ```typescript
380
441
  // Enable debug mode to see performance metrics
381
- const db = await migrateIndexedDB("my-app", config, true);
442
+ const db = await migrateIndexedDBWithFunctions("my-app", migrations, true);
382
443
 
383
444
  // Output shows:
384
- // [PERF] IndexedDB snapshot migrator start for my-app
445
+ // [PERF] IndexedDB function migrator start for my-app
385
446
  // [PERF] Latest applied migration index: 5 (checked 5 migrations)
386
447
  // [PERF] Found 2 pending migrations to apply: ["add_priority", "add_category"]
387
448
  // [PERF] Upgrade started: v5 → v7
@@ -432,9 +493,9 @@ const todoTable = syncableTable("todos", {
432
493
 
433
494
  ### Renaming a Column
434
495
 
435
- Drizzle snapshots don't track renames directly, but you can:
496
+ Drizzle migrations don't track renames directly, but you can:
436
497
 
437
- 1. Use function-based migrations to handle data transformation
498
+ 1. Modify the generated migration function to handle data transformation
438
499
  2. Or: Add new column, copy data, delete old column (3 separate migrations)
439
500
 
440
501
  ### Deleting a Table
@@ -455,8 +516,8 @@ This happens when you change the primary key of a table. IndexedDB doesn't suppo
455
516
 
456
517
  ### Migrations Not Applying
457
518
 
458
- - Check that journal and snapshots are correctly imported
459
- - Verify the snapshot files exist in `drizzle/snapshots/`
519
+ - Check that migrations are correctly imported from `drizzle/indexeddb-migrations/`
520
+ - Verify the migration files exist - run `bun drizzle-indexeddb-generate` to regenerate
460
521
  - Enable debug mode to see what's happening
461
522
  - Check browser DevTools → Application → IndexedDB
462
523
 
package/package.json CHANGED
@@ -1,17 +1,25 @@
1
1
  {
2
2
  "name": "@firtoz/drizzle-indexeddb",
3
- "version": "0.2.0",
4
- "description": "IndexedDB migrations powered by Drizzle ORM snapshots",
3
+ "version": "0.3.0",
4
+ "description": "IndexedDB migrations powered by Drizzle ORM",
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
7
7
  "types": "./src/index.ts",
8
8
  "type": "module",
9
+ "bin": {
10
+ "drizzle-indexeddb-generate": "./src/bin/generate-migrations.ts"
11
+ },
9
12
  "exports": {
10
13
  ".": {
11
14
  "types": "./src/index.ts",
12
15
  "import": "./src/index.ts",
13
16
  "require": "./src/index.ts"
14
17
  },
18
+ "./generate": {
19
+ "types": "./src/bin/generate-migrations.ts",
20
+ "import": "./src/bin/generate-migrations.ts",
21
+ "require": "./src/bin/generate-migrations.ts"
22
+ },
15
23
  "./*": {
16
24
  "types": "./src/*.ts",
17
25
  "import": "./src/*.ts",
@@ -57,15 +65,15 @@
57
65
  },
58
66
  "dependencies": {
59
67
  "@firtoz/drizzle-utils": "^0.2.0",
60
- "@tanstack/db": "^0.5.0",
68
+ "@tanstack/db": "^0.5.10",
61
69
  "drizzle-orm": "^0.44.7",
62
70
  "drizzle-valibot": "^0.4.2",
63
- "valibot": "^1.0.0"
71
+ "valibot": "^1.2.0"
64
72
  },
65
73
  "peerDependencies": {
66
74
  "react": "^19.2.0"
67
75
  },
68
76
  "devDependencies": {
69
- "@types/react": "^19.2.5"
77
+ "@types/react": "^19.2.7"
70
78
  }
71
79
  }
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI tool to generate IndexedDB migrations from Drizzle schema snapshots
4
+ * Run after `drizzle-kit generate` to create executable migration files
5
+ *
6
+ * Usage:
7
+ * bun drizzle-indexeddb-generate
8
+ */
9
+
10
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
11
+ import { join, resolve } from "node:path";
12
+ import type {
13
+ JournalEntry,
14
+ Journal,
15
+ Snapshot,
16
+ TableDefinition,
17
+ ColumnDefinition,
18
+ IndexDefinition,
19
+ } from "@firtoz/drizzle-utils";
20
+ import type { Migration, MigrationOperation } from "../function-migrator";
21
+
22
+ interface GenerateOptions {
23
+ drizzleDir?: string;
24
+ outputDir?: string;
25
+ }
26
+
27
+ function generateMigration(
28
+ _entry: JournalEntry,
29
+ snapshot: Snapshot,
30
+ prevSnapshot: Snapshot | null,
31
+ ): Migration {
32
+ const operations: MigrationOperation[] = [];
33
+
34
+ const currentTables: Record<string, TableDefinition> = snapshot.tables || {};
35
+ const previousTables: Record<string, TableDefinition> =
36
+ prevSnapshot?.tables || {};
37
+
38
+ // Find new tables
39
+ for (const [tableName, tableDef] of Object.entries(currentTables)) {
40
+ if (!previousTables[tableName]) {
41
+ // Find primary key
42
+ const pkColumn = Object.values(
43
+ tableDef.columns as Record<string, ColumnDefinition>,
44
+ ).find((col) => col.primaryKey);
45
+
46
+ const indexes: Array<{
47
+ name: string;
48
+ keyPath: string | string[];
49
+ unique?: boolean;
50
+ }> = [];
51
+
52
+ // Collect indexes
53
+ for (const [indexName, indexDef] of Object.entries(
54
+ tableDef.indexes || {},
55
+ )) {
56
+ indexes.push({
57
+ name: indexName,
58
+ keyPath:
59
+ indexDef.columns.length === 1
60
+ ? indexDef.columns[0]
61
+ : indexDef.columns,
62
+ unique: indexDef.isUnique,
63
+ });
64
+ }
65
+
66
+ operations.push({
67
+ type: "createTable",
68
+ name: tableName,
69
+ keyPath: pkColumn?.name,
70
+ autoIncrement: pkColumn?.autoincrement ?? false,
71
+ indexes: indexes.length > 0 ? indexes : undefined,
72
+ });
73
+ } else {
74
+ // Table exists, check for index changes
75
+ const prevTableDef: TableDefinition = previousTables[tableName];
76
+ const newIndexes: Record<string, IndexDefinition> =
77
+ tableDef.indexes || {};
78
+ const oldIndexes: Record<string, IndexDefinition> =
79
+ prevTableDef.indexes || {};
80
+
81
+ // Add new indexes
82
+ for (const [indexName, indexDef] of Object.entries(newIndexes)) {
83
+ if (!oldIndexes[indexName]) {
84
+ operations.push({
85
+ type: "createIndex",
86
+ tableName,
87
+ indexName,
88
+ keyPath:
89
+ indexDef.columns.length === 1
90
+ ? indexDef.columns[0]
91
+ : indexDef.columns,
92
+ unique: indexDef.isUnique,
93
+ });
94
+ }
95
+ }
96
+
97
+ // Delete removed indexes
98
+ for (const indexName of Object.keys(oldIndexes)) {
99
+ if (!newIndexes[indexName]) {
100
+ operations.push({
101
+ type: "deleteIndex",
102
+ tableName,
103
+ indexName,
104
+ });
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+ // Find deleted tables
111
+ for (const tableName of Object.keys(previousTables)) {
112
+ if (!currentTables[tableName]) {
113
+ operations.push({
114
+ type: "deleteTable",
115
+ name: tableName,
116
+ });
117
+ }
118
+ }
119
+
120
+ return operations;
121
+ }
122
+
123
+ function migrationToCode(
124
+ migration: Migration,
125
+ idx: number,
126
+ tag: string,
127
+ ): string {
128
+ const migrationName = tag.replace(/^\d+_/, "").replace(/_/g, " ");
129
+ const lines: string[] = [];
130
+
131
+ lines.push(
132
+ `import type { Migration } from "@firtoz/drizzle-indexeddb";`,
133
+ ``,
134
+ `/**`,
135
+ ` * Migration: ${migrationName}`,
136
+ ` * Generated from: ${tag}`,
137
+ ` */`,
138
+ `export const migrate_${idx.toString().padStart(4, "0")}: Migration = ${JSON.stringify(migration, null, "\t")};`,
139
+ );
140
+
141
+ return lines.join("\n");
142
+ }
143
+
144
+ export function generateIndexedDBMigrations(
145
+ options: GenerateOptions = {},
146
+ ): void {
147
+ const cwd = process.cwd();
148
+ const drizzleDir = resolve(cwd, options.drizzleDir || "./drizzle");
149
+ const metaDir = join(drizzleDir, "meta");
150
+ const journalPath = join(metaDir, "_journal.json");
151
+ const outputDir = resolve(
152
+ cwd,
153
+ options.outputDir || join(drizzleDir, "indexeddb-migrations"),
154
+ );
155
+
156
+ const startTime = performance.now();
157
+ console.log(`[drizzle-indexeddb] Starting migration generation...`);
158
+
159
+ // Read the journal
160
+ if (!existsSync(journalPath)) {
161
+ console.error(
162
+ `[drizzle-indexeddb] Error: Journal not found at ${journalPath}`,
163
+ );
164
+ console.error(
165
+ `[drizzle-indexeddb] Make sure to run 'drizzle-kit generate' first`,
166
+ );
167
+ process.exit(1);
168
+ }
169
+
170
+ const journalContent = readFileSync(journalPath, "utf-8");
171
+ const journal: Journal = JSON.parse(journalContent);
172
+
173
+ console.log(`[drizzle-indexeddb] Found ${journal.entries.length} migrations`);
174
+
175
+ // Create output directory
176
+ if (!existsSync(outputDir)) {
177
+ mkdirSync(outputDir, { recursive: true });
178
+ console.log(`[drizzle-indexeddb] Created output directory: ${outputDir}`);
179
+ }
180
+
181
+ const migrationImports: string[] = [];
182
+ const migrationNames: string[] = [];
183
+
184
+ // Load all snapshots and generate migrations
185
+ const snapshots: Snapshot[] = [];
186
+
187
+ for (const entry of journal.entries) {
188
+ const fileName = `${entry.idx.toString().padStart(4, "0")}_snapshot.json`;
189
+ const snapshotPath = join(metaDir, fileName);
190
+
191
+ // Load snapshot
192
+ if (!existsSync(snapshotPath)) {
193
+ console.error(
194
+ `[drizzle-indexeddb] Error: Snapshot not found at ${snapshotPath}`,
195
+ );
196
+ process.exit(1);
197
+ }
198
+
199
+ const snapshotContent = readFileSync(snapshotPath, "utf-8");
200
+ const snapshot: Snapshot = JSON.parse(snapshotContent);
201
+ snapshots.push(snapshot);
202
+
203
+ // Generate migration
204
+ const prevSnapshot = entry.idx > 0 ? snapshots[entry.idx - 1] : null;
205
+ const migration = generateMigration(entry, snapshot, prevSnapshot);
206
+ const migrationCode = migrationToCode(migration, entry.idx, entry.tag);
207
+
208
+ const migrationFileName = `${entry.tag}.ts`;
209
+ const migrationPath = join(outputDir, migrationFileName);
210
+ writeFileSync(migrationPath, migrationCode, "utf-8");
211
+
212
+ // Add to index imports
213
+ const migrationName = `migrate_${entry.idx.toString().padStart(4, "0")}`;
214
+ migrationImports.push(`import { ${migrationName} } from './${entry.tag}';`);
215
+ migrationNames.push(migrationName);
216
+ }
217
+
218
+ // Generate index.ts for migrations
219
+ const indexContent = `import type { Migration } from "@firtoz/drizzle-indexeddb";
220
+
221
+ ${migrationImports.join("\n")}
222
+
223
+ export const migrations: Migration[] = [
224
+ \t${migrationNames.join(",\n\t")}
225
+ ];
226
+
227
+ export default migrations;
228
+ `;
229
+
230
+ writeFileSync(join(outputDir, "index.ts"), indexContent, "utf-8");
231
+ console.log(`[drizzle-indexeddb] ✓ Generated ${join(outputDir, "index.ts")}`);
232
+
233
+ const endTime = performance.now();
234
+ const totalTime = endTime - startTime;
235
+
236
+ console.log(`[drizzle-indexeddb] Migrations: ${migrationNames.join(", ")}`);
237
+ console.log(
238
+ `[drizzle-indexeddb] ✓ Complete! Generated ${journal.entries.length} migrations in ${totalTime.toFixed(2)}ms`,
239
+ );
240
+ }
241
+
242
+ // 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
+ }
284
+
285
+ // Only run CLI when executed directly (not when imported)
286
+ if (import.meta.main) {
287
+ main();
288
+ }