@firtoz/drizzle-indexeddb 0.2.0 → 0.4.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 +70 -26
- package/README.md +274 -51
- package/package.json +14 -6
- package/src/bin/generate-migrations.ts +288 -0
- package/src/collections/indexeddb-collection.ts +97 -397
- package/src/context/useDrizzleIndexedDB.ts +2 -1
- package/src/function-migrator.ts +191 -170
- package/src/idb-interceptor.ts +75 -0
- package/src/idb-operations.ts +41 -0
- package/src/idb-types.ts +135 -0
- package/src/index.ts +57 -9
- package/src/instrumented-idb-database.ts +188 -0
- package/src/native-idb-database.ts +355 -0
- package/src/proxy/idb-proxy-client.ts +345 -0
- package/src/proxy/idb-proxy-server.ts +313 -0
- package/src/proxy/idb-proxy-transport.ts +174 -0
- package/src/proxy/idb-proxy-types.ts +77 -0
- package/src/proxy/idb-sync-adapter.ts +95 -0
- package/src/proxy/index.ts +37 -0
- package/src/snapshot-migrator.ts +0 -420
- package/src/utils.ts +0 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
# @firtoz/drizzle-indexeddb
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`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:
|
|
8
|
+
|
|
9
|
+
**New Proxy Module** (`@firtoz/drizzle-indexeddb/proxy`):
|
|
10
|
+
|
|
11
|
+
- **`IDBProxyServer`** - Server that manages database lifecycle, migrations, and broadcasts mutations to connected clients
|
|
12
|
+
- **`IDBProxyClient`** - Client implementing `IDBDatabaseLike`, routing operations through a transport layer
|
|
13
|
+
- **`createMultiClientTransport()`** - In-memory transport for testing N clients connected to one server
|
|
14
|
+
- **`createProxyDbCreator()`** - Factory to create `dbCreator` for `DrizzleIndexedDBProvider`
|
|
15
|
+
- **`createCollectionSyncHandler()`** - Adapter connecting proxy sync messages to collection's external sync
|
|
16
|
+
|
|
17
|
+
**Real-time Multi-Client Sync**:
|
|
18
|
+
|
|
19
|
+
- Server broadcasts `sync:add`, `sync:put`, `sync:delete`, `sync:clear` messages to all clients (excluding initiator)
|
|
20
|
+
- All mutations automatically sync across connected clients
|
|
21
|
+
|
|
22
|
+
**Provider Enhancements**:
|
|
23
|
+
|
|
24
|
+
- New `onSyncReady` prop for wiring up external sync handlers
|
|
25
|
+
- `handleProxySync` method routes sync messages to the appropriate collection
|
|
26
|
+
|
|
27
|
+
**Collection Truncate**:
|
|
28
|
+
|
|
29
|
+
- `collection.utils.truncate()` clears all data and syncs to other clients
|
|
30
|
+
- `handleTruncate` implemented in IndexedDB backend
|
|
31
|
+
|
|
32
|
+
**Bug Fixes**:
|
|
33
|
+
|
|
34
|
+
- Server handles concurrent database initialization requests (race condition fix)
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- Updated dependencies [[`46059a2`](https://github.com/firtoz/fullstack-toolkit/commit/46059a28bd0135414b9ed022ffe162a2292adae3)]:
|
|
39
|
+
- @firtoz/drizzle-utils@0.3.0
|
|
40
|
+
|
|
41
|
+
## 0.3.0
|
|
42
|
+
|
|
43
|
+
### Minor Changes
|
|
44
|
+
|
|
45
|
+
- [`5e854a6`](https://github.com/firtoz/fullstack-toolkit/commit/5e854a62236a811918a47037a59df23329856614) Thanks [@firtoz](https://github.com/firtoz)! - ### Breaking Changes
|
|
46
|
+
|
|
47
|
+
- Removed `migrateIndexedDB` and `IndexedDBMigrationConfig` exports - use `migrateIndexedDBWithFunctions` instead
|
|
48
|
+
- Removed snapshot-based migration system in favor of function-based migrations
|
|
49
|
+
|
|
50
|
+
### New Features
|
|
51
|
+
|
|
52
|
+
- Added `drizzle-indexeddb-generate` CLI tool to generate IndexedDB migration functions from Drizzle snapshots
|
|
53
|
+
- Added `generateIndexedDBMigrations` export for programmatic migration generation
|
|
54
|
+
- Added `./generate` export path
|
|
55
|
+
|
|
56
|
+
### Migration Guide
|
|
57
|
+
|
|
58
|
+
Instead of importing snapshots directly and using `migrateIndexedDB`, you now:
|
|
59
|
+
|
|
60
|
+
1. Run `bun drizzle-indexeddb-generate` (or `npx drizzle-indexeddb-generate`) after `drizzle-kit generate`
|
|
61
|
+
2. Import the generated migrations and use `migrateIndexedDBWithFunctions`
|
|
62
|
+
|
|
3
63
|
## 0.2.0
|
|
4
64
|
|
|
5
65
|
### Minor Changes
|
|
@@ -41,18 +101,6 @@
|
|
|
41
101
|
- Sync configuration for real-time updates
|
|
42
102
|
- Works seamlessly with React hooks
|
|
43
103
|
|
|
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
104
|
### Function-Based Migration
|
|
57
105
|
|
|
58
106
|
**`migrateIndexedDBWithFunctions(dbName, migrations, debug?)`** - Run migrations using custom migration functions:
|
|
@@ -87,19 +135,15 @@
|
|
|
87
135
|
## Example
|
|
88
136
|
|
|
89
137
|
```typescript
|
|
90
|
-
import {
|
|
91
|
-
import
|
|
92
|
-
import * as snapshots from "./drizzle/snapshots";
|
|
138
|
+
import { migrateIndexedDBWithFunctions } from "@firtoz/drizzle-indexeddb";
|
|
139
|
+
import migrations from "./drizzle/indexeddb-migrations";
|
|
93
140
|
|
|
94
|
-
// Migrate database using
|
|
95
|
-
const db = await
|
|
141
|
+
// Migrate database using function-based migrations
|
|
142
|
+
const db = await migrateIndexedDBWithFunctions(
|
|
96
143
|
"my-app-db",
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
},
|
|
101
|
-
true
|
|
102
|
-
); // debug mode
|
|
144
|
+
migrations,
|
|
145
|
+
true // debug mode
|
|
146
|
+
);
|
|
103
147
|
|
|
104
148
|
// Use with TanStack DB
|
|
105
149
|
import { createCollection } from "@tanstack/db";
|
|
@@ -136,9 +180,9 @@
|
|
|
136
180
|
|
|
137
181
|
## Migration Workflow
|
|
138
182
|
|
|
139
|
-
1. Generate Drizzle
|
|
140
|
-
2.
|
|
141
|
-
3.
|
|
183
|
+
1. Generate Drizzle migrations: `drizzle-kit generate`
|
|
184
|
+
2. Generate IndexedDB migrations: `bun drizzle-indexeddb-generate`
|
|
185
|
+
3. Import migrations and call `migrateIndexedDBWithFunctions()` on app startup
|
|
142
186
|
4. Database automatically updates to latest schema
|
|
143
187
|
|
|
144
188
|
## 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
|
|
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
|
|
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,8 @@ 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
|
-
-
|
|
23
|
-
-
|
|
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.)
|
|
24
24
|
|
|
25
25
|
## Quick Start
|
|
26
26
|
|
|
@@ -40,22 +40,25 @@ export const todoTable = syncableTable("todos", {
|
|
|
40
40
|
### 2. Generate Migrations
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
-
# Generate Drizzle
|
|
43
|
+
# Generate Drizzle migrations
|
|
44
44
|
drizzle-kit generate
|
|
45
|
+
|
|
46
|
+
# Generate IndexedDB migration functions
|
|
47
|
+
bun drizzle-indexeddb-generate
|
|
45
48
|
```
|
|
46
49
|
|
|
47
50
|
### 3. Migrate IndexedDB
|
|
48
51
|
|
|
49
52
|
```typescript
|
|
50
53
|
// db.ts
|
|
51
|
-
import {
|
|
52
|
-
import
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
import { migrateIndexedDBWithFunctions } from "@firtoz/drizzle-indexeddb";
|
|
55
|
+
import migrations from "./drizzle/indexeddb-migrations";
|
|
56
|
+
|
|
57
|
+
export const db = await migrateIndexedDBWithFunctions(
|
|
58
|
+
"my-app",
|
|
59
|
+
migrations,
|
|
60
|
+
true // Enable debug logging
|
|
61
|
+
);
|
|
59
62
|
```
|
|
60
63
|
|
|
61
64
|
### 4. Use with React
|
|
@@ -181,19 +184,19 @@ collection.find({
|
|
|
181
184
|
|
|
182
185
|
## Migration Methods
|
|
183
186
|
|
|
184
|
-
###
|
|
187
|
+
### Function-Based Migration
|
|
185
188
|
|
|
186
|
-
Use
|
|
189
|
+
Use generated migration functions to migrate your IndexedDB schema:
|
|
187
190
|
|
|
188
191
|
```typescript
|
|
189
|
-
import {
|
|
190
|
-
import
|
|
191
|
-
import * as snapshots from "./drizzle/snapshots";
|
|
192
|
+
import { migrateIndexedDBWithFunctions } from "@firtoz/drizzle-indexeddb";
|
|
193
|
+
import migrations from "./drizzle/indexeddb-migrations";
|
|
192
194
|
|
|
193
|
-
const db = await
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
const db = await migrateIndexedDBWithFunctions(
|
|
196
|
+
"my-app-db",
|
|
197
|
+
migrations,
|
|
198
|
+
true // debug flag
|
|
199
|
+
);
|
|
197
200
|
|
|
198
201
|
console.log("Database migrated successfully!");
|
|
199
202
|
```
|
|
@@ -219,40 +222,49 @@ interface MigrationRecord {
|
|
|
219
222
|
}
|
|
220
223
|
```
|
|
221
224
|
|
|
222
|
-
###
|
|
225
|
+
### Custom Migration Functions
|
|
223
226
|
|
|
224
|
-
For complex migrations that require custom logic:
|
|
227
|
+
For complex migrations that require custom logic, you can write migration functions directly:
|
|
225
228
|
|
|
226
229
|
```typescript
|
|
227
230
|
import { migrateIndexedDBWithFunctions } from "@firtoz/drizzle-indexeddb";
|
|
228
231
|
|
|
229
232
|
const migrations = [
|
|
230
233
|
// Migration 0: Initial schema
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
+
{
|
|
235
|
+
tag: "0000_initial",
|
|
236
|
+
migrate: async (db: IDBDatabase, transaction: IDBTransaction) => {
|
|
237
|
+
const store = db.createObjectStore("todos", { keyPath: "id" });
|
|
238
|
+
store.createIndex("title", "title", { unique: false });
|
|
239
|
+
},
|
|
234
240
|
},
|
|
235
241
|
|
|
236
242
|
// Migration 1: Add completed index
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
243
|
+
{
|
|
244
|
+
tag: "0001_add_completed",
|
|
245
|
+
migrate: async (db: IDBDatabase, transaction: IDBTransaction) => {
|
|
246
|
+
const store = transaction.objectStore("todos");
|
|
247
|
+
store.createIndex("completed", "completed", { unique: false });
|
|
248
|
+
},
|
|
240
249
|
},
|
|
241
250
|
|
|
242
251
|
// Migration 2: Transform data
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
252
|
+
{
|
|
253
|
+
tag: "0002_add_priority",
|
|
254
|
+
migrate: async (db: IDBDatabase, transaction: IDBTransaction) => {
|
|
255
|
+
const store = transaction.objectStore("todos");
|
|
256
|
+
const todos = await new Promise<any[]>((resolve, reject) => {
|
|
257
|
+
const req = store.getAll();
|
|
258
|
+
req.onsuccess = () => resolve(req.result);
|
|
259
|
+
req.onerror = () => reject(req.error);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Transform data
|
|
263
|
+
for (const todo of todos) {
|
|
264
|
+
todo.priority = todo.priority || "medium";
|
|
265
|
+
store.put(todo);
|
|
266
|
+
}
|
|
267
|
+
},
|
|
256
268
|
},
|
|
257
269
|
];
|
|
258
270
|
|
|
@@ -338,6 +350,56 @@ Useful for:
|
|
|
338
350
|
- Clearing user data on logout
|
|
339
351
|
- Testing scenarios
|
|
340
352
|
|
|
353
|
+
### generateIndexedDBMigrations
|
|
354
|
+
|
|
355
|
+
Generate IndexedDB migration files from Drizzle snapshots programmatically:
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
import { generateIndexedDBMigrations } from "@firtoz/drizzle-indexeddb";
|
|
359
|
+
|
|
360
|
+
generateIndexedDBMigrations({
|
|
361
|
+
drizzleDir: "./drizzle", // Path to Drizzle directory (default: ./drizzle)
|
|
362
|
+
outputDir: "./drizzle/indexeddb-migrations", // Output directory (default: ./drizzle/indexeddb-migrations)
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## CLI
|
|
367
|
+
|
|
368
|
+
The package includes a CLI tool to generate IndexedDB migrations from Drizzle schema snapshots.
|
|
369
|
+
|
|
370
|
+
### Usage
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
# Generate migrations (run after drizzle-kit generate)
|
|
374
|
+
bun drizzle-indexeddb-generate
|
|
375
|
+
|
|
376
|
+
# With custom paths
|
|
377
|
+
bun drizzle-indexeddb-generate --drizzle-dir ./db/drizzle
|
|
378
|
+
bun drizzle-indexeddb-generate --output-dir ./src/migrations
|
|
379
|
+
|
|
380
|
+
# Show help
|
|
381
|
+
bun drizzle-indexeddb-generate --help
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Options
|
|
385
|
+
|
|
386
|
+
| Option | Description | Default |
|
|
387
|
+
|--------|-------------|---------|
|
|
388
|
+
| `--drizzle-dir <path>` | Path to Drizzle directory | `./drizzle` |
|
|
389
|
+
| `--output-dir <path>` | Path to output directory | `./drizzle/indexeddb-migrations` |
|
|
390
|
+
|
|
391
|
+
### npm scripts
|
|
392
|
+
|
|
393
|
+
Add to your `package.json`:
|
|
394
|
+
|
|
395
|
+
```json
|
|
396
|
+
{
|
|
397
|
+
"scripts": {
|
|
398
|
+
"db:generate": "bun drizzle-kit generate && bun drizzle-indexeddb-generate"
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
341
403
|
## Advanced Usage
|
|
342
404
|
|
|
343
405
|
### Custom Sync Configuration
|
|
@@ -355,17 +417,178 @@ const collection = createCollection(
|
|
|
355
417
|
);
|
|
356
418
|
```
|
|
357
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
|
+
|
|
358
581
|
### Handling Migration Errors
|
|
359
582
|
|
|
360
583
|
```typescript
|
|
361
584
|
try {
|
|
362
|
-
const db = await
|
|
585
|
+
const db = await migrateIndexedDBWithFunctions("my-app", migrations, true);
|
|
363
586
|
} catch (error) {
|
|
364
587
|
console.error("Migration failed:", error);
|
|
365
588
|
|
|
366
589
|
// Option 1: Delete and start fresh
|
|
367
590
|
await deleteIndexedDB("my-app");
|
|
368
|
-
const db = await
|
|
591
|
+
const db = await migrateIndexedDBWithFunctions("my-app", migrations, true);
|
|
369
592
|
|
|
370
593
|
// Option 2: Handle specific errors
|
|
371
594
|
if (error.message.includes("Primary key structure changed")) {
|
|
@@ -378,10 +601,10 @@ try {
|
|
|
378
601
|
|
|
379
602
|
```typescript
|
|
380
603
|
// Enable debug mode to see performance metrics
|
|
381
|
-
const db = await
|
|
604
|
+
const db = await migrateIndexedDBWithFunctions("my-app", migrations, true);
|
|
382
605
|
|
|
383
606
|
// Output shows:
|
|
384
|
-
// [PERF] IndexedDB
|
|
607
|
+
// [PERF] IndexedDB function migrator start for my-app
|
|
385
608
|
// [PERF] Latest applied migration index: 5 (checked 5 migrations)
|
|
386
609
|
// [PERF] Found 2 pending migrations to apply: ["add_priority", "add_category"]
|
|
387
610
|
// [PERF] Upgrade started: v5 → v7
|
|
@@ -432,9 +655,9 @@ const todoTable = syncableTable("todos", {
|
|
|
432
655
|
|
|
433
656
|
### Renaming a Column
|
|
434
657
|
|
|
435
|
-
Drizzle
|
|
658
|
+
Drizzle migrations don't track renames directly, but you can:
|
|
436
659
|
|
|
437
|
-
1.
|
|
660
|
+
1. Modify the generated migration function to handle data transformation
|
|
438
661
|
2. Or: Add new column, copy data, delete old column (3 separate migrations)
|
|
439
662
|
|
|
440
663
|
### Deleting a Table
|
|
@@ -455,8 +678,8 @@ This happens when you change the primary key of a table. IndexedDB doesn't suppo
|
|
|
455
678
|
|
|
456
679
|
### Migrations Not Applying
|
|
457
680
|
|
|
458
|
-
- Check that
|
|
459
|
-
- Verify the
|
|
681
|
+
- Check that migrations are correctly imported from `drizzle/indexeddb-migrations/`
|
|
682
|
+
- Verify the migration files exist - run `bun drizzle-indexeddb-generate` to regenerate
|
|
460
683
|
- Enable debug mode to see what's happening
|
|
461
684
|
- Check browser DevTools → Application → IndexedDB
|
|
462
685
|
|
package/package.json
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firtoz/drizzle-indexeddb",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "IndexedDB migrations powered by Drizzle ORM
|
|
3
|
+
"version": "0.4.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",
|
|
@@ -56,16 +64,16 @@
|
|
|
56
64
|
"access": "public"
|
|
57
65
|
},
|
|
58
66
|
"dependencies": {
|
|
59
|
-
"@firtoz/drizzle-utils": "^0.
|
|
60
|
-
"@tanstack/db": "^0.5.
|
|
67
|
+
"@firtoz/drizzle-utils": "^0.3.0",
|
|
68
|
+
"@tanstack/db": "^0.5.10",
|
|
61
69
|
"drizzle-orm": "^0.44.7",
|
|
62
70
|
"drizzle-valibot": "^0.4.2",
|
|
63
|
-
"valibot": "^1.
|
|
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.
|
|
77
|
+
"@types/react": "^19.2.7"
|
|
70
78
|
}
|
|
71
79
|
}
|