@firtoz/drizzle-sqlite-wasm 0.1.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/README.md ADDED
@@ -0,0 +1,602 @@
1
+ # @firtoz/drizzle-sqlite-wasm
2
+
3
+ TanStack DB collections backed by SQLite WASM running in Web Workers, with full Drizzle ORM integration. Build reactive, type-safe SQLite applications in the browser with non-blocking database operations.
4
+
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
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @firtoz/drizzle-sqlite-wasm @firtoz/drizzle-utils drizzle-orm @tanstack/db
11
+ ```
12
+
13
+ ## Features
14
+
15
+ - 📦 **TanStack DB collections** - Reactive collections with type safety (primary feature)
16
+ - 🔄 **Web Worker support** - Non-blocking SQLite in a dedicated worker
17
+ - ⚡ **Drizzle ORM** - Full type-safe query builder
18
+ - 🎯 **Type-safe** - Full TypeScript support with automatic type inference
19
+ - 🔍 **Query optimization** - Leverage SQLite indexes for fast queries
20
+ - ⚛️ **React hooks** - Provider and hooks for easy integration
21
+ - 🔄 **Migrations** - Automatic schema migrations with Drizzle snapshots
22
+ - 🔌 **Bundler agnostic** - Works with Vite, Webpack, Parcel, and more
23
+
24
+ ## Quick Start
25
+
26
+ ### 1. Define Your Schema
27
+
28
+ ```typescript
29
+ // schema.ts
30
+ import { syncableTable } from "@firtoz/drizzle-utils";
31
+ import { text, integer } from "drizzle-orm/sqlite-core";
32
+
33
+ export const todoTable = syncableTable("todos", {
34
+ title: text("title").notNull(),
35
+ completed: integer("completed", { mode: "boolean" }).notNull().default(false),
36
+ description: text("description"),
37
+ });
38
+
39
+ export const schema = { todoTable };
40
+ ```
41
+
42
+ ### 2. Generate Migrations
43
+
44
+ ```bash
45
+ # Generate Drizzle migrations
46
+ drizzle-kit generate
47
+ ```
48
+
49
+ ### 3. Setup Worker (Vite Example)
50
+
51
+ ```typescript
52
+ // App.tsx
53
+ import { DrizzleSqliteProvider } from "@firtoz/drizzle-sqlite-wasm";
54
+ import SqliteWorker from "@firtoz/drizzle-sqlite-wasm/worker/sqlite.worker?worker";
55
+ import * as schema from "./schema";
56
+ import migrations from "./migrations";
57
+
58
+ function App() {
59
+ return (
60
+ <DrizzleSqliteProvider
61
+ worker={SqliteWorker}
62
+ dbName="my-app"
63
+ schema={schema}
64
+ migrations={migrations}
65
+ >
66
+ <TodoApp />
67
+ </DrizzleSqliteProvider>
68
+ );
69
+ }
70
+ ```
71
+
72
+ ### 4. Use in Components
73
+
74
+ ```typescript
75
+ // TodoList.tsx
76
+ import { useDrizzleSqlite, useSqliteCollection } from "@firtoz/drizzle-sqlite-wasm";
77
+ import { todoTable } from "./schema";
78
+
79
+ function TodoList() {
80
+ const { drizzle } = useDrizzleSqlite();
81
+
82
+ // Option 1: Use Drizzle ORM directly
83
+ const loadTodos = async () => {
84
+ const todos = await drizzle.select().from(todoTable).all();
85
+ return todos;
86
+ };
87
+
88
+ // Option 2: Use TanStack DB collection
89
+ const collection = useSqliteCollection("todos");
90
+ const [todos] = collection.useStore();
91
+
92
+ return (
93
+ <ul>
94
+ {todos.map(todo => (
95
+ <li key={todo.id}>{todo.title}</li>
96
+ ))}
97
+ </ul>
98
+ );
99
+ }
100
+ ```
101
+
102
+ ## Bundler Support
103
+
104
+ ### Vite
105
+
106
+ Vite has built-in support for Web Workers with the `?worker` suffix:
107
+
108
+ ```typescript
109
+ import SqliteWorker from "@firtoz/drizzle-sqlite-wasm/worker/sqlite.worker?worker";
110
+
111
+ const { drizzle } = useDrizzleSqliteDb(SqliteWorker, "mydb", schema, migrations);
112
+ ```
113
+
114
+ ### Webpack 5+
115
+
116
+ Use `new URL()` with `import.meta.url`:
117
+
118
+ ```typescript
119
+ const SqliteWorker = class extends Worker {
120
+ constructor() {
121
+ super(
122
+ new URL("@firtoz/drizzle-sqlite-wasm/worker/sqlite.worker", import.meta.url),
123
+ { type: "module" }
124
+ );
125
+ }
126
+ };
127
+
128
+ const { drizzle } = useDrizzleSqliteDb(SqliteWorker, "mydb", schema, migrations);
129
+ ```
130
+
131
+ ### Parcel 2+
132
+
133
+ Similar to Webpack:
134
+
135
+ ```typescript
136
+ const SqliteWorker = class extends Worker {
137
+ constructor() {
138
+ super(
139
+ new URL("@firtoz/drizzle-sqlite-wasm/worker/sqlite.worker", import.meta.url)
140
+ );
141
+ }
142
+ };
143
+
144
+ const { drizzle } = useDrizzleSqliteDb(SqliteWorker, "mydb", schema, migrations);
145
+ ```
146
+
147
+ ## TanStack DB Collections
148
+
149
+ The primary feature of this package: Create reactive, type-safe collections backed by SQLite WASM.
150
+
151
+ ### Basic Usage
152
+
153
+ Create TanStack DB collections backed by SQLite:
154
+
155
+ ```typescript
156
+ import { createCollection } from "@tanstack/db";
157
+ import { drizzleCollectionOptions } from "@firtoz/drizzle-sqlite-wasm/drizzleCollectionOptions";
158
+
159
+ const collection = createCollection(
160
+ drizzleCollectionOptions({
161
+ drizzle,
162
+ tableName: "todos",
163
+ readyPromise,
164
+ syncMode: "on-demand", // or "realtime"
165
+ })
166
+ );
167
+
168
+ // CRUD operations
169
+ await collection.insert({ title: "Buy milk", completed: false });
170
+ await collection.update(todoId, { completed: true });
171
+ await collection.delete(todoId); // Soft delete (sets deletedAt)
172
+
173
+ // Query with filters
174
+ const completed = await collection.find({
175
+ where: { completed: true },
176
+ orderBy: { createdAt: "desc" },
177
+ });
178
+
179
+ // Subscribe to changes
180
+ collection.subscribe((todos) => {
181
+ console.log("Todos updated:", todos);
182
+ });
183
+ ```
184
+
185
+ ### Collection Options
186
+
187
+ **Config:**
188
+ - `drizzle: DrizzleDB` - Drizzle instance
189
+ - `tableName: string` - Table name
190
+ - `readyPromise: Promise<void>` - Database ready promise
191
+ - `syncMode?: "on-demand" | "realtime"` - Sync mode
192
+
193
+ ## API Reference
194
+
195
+ ### React Hooks
196
+
197
+ #### `DrizzleSqliteProvider`
198
+
199
+ Context provider for SQLite WASM:
200
+
201
+ ```typescript
202
+ <DrizzleSqliteProvider
203
+ worker={SqliteWorker} // Worker constructor
204
+ dbName="my-app" // Database name
205
+ schema={schema} // Drizzle schema
206
+ migrations={migrations} // Migration config
207
+ >
208
+ {children}
209
+ </DrizzleSqliteProvider>
210
+ ```
211
+
212
+ **Props:**
213
+ - `worker: new () => Worker` - Worker constructor (bundler-specific)
214
+ - `dbName: string` - Name of the SQLite database
215
+ - `schema: TSchema` - Drizzle schema object
216
+ - `migrations: DurableSqliteMigrationConfig` - Migration configuration
217
+
218
+ #### `useDrizzleSqliteDb(worker, dbName, schema, migrations)`
219
+
220
+ Hook to create a Drizzle instance with Web Worker:
221
+
222
+ ```typescript
223
+ function MyComponent() {
224
+ const { drizzle, readyPromise } = useDrizzleSqliteDb(
225
+ SqliteWorker,
226
+ "my-app",
227
+ schema,
228
+ migrations
229
+ );
230
+
231
+ useEffect(() => {
232
+ readyPromise.then(() => {
233
+ console.log("Database ready!");
234
+ });
235
+ }, [readyPromise]);
236
+
237
+ // Use drizzle...
238
+ }
239
+ ```
240
+
241
+ **Returns:**
242
+ - `drizzle: DrizzleDB` - Drizzle ORM instance
243
+ - `readyPromise: Promise<void>` - Resolves when database is ready
244
+
245
+ #### `useDrizzleSqlite()`
246
+
247
+ Access the Drizzle context:
248
+
249
+ ```typescript
250
+ function MyComponent() {
251
+ const { drizzle, getCollection } = useDrizzleSqlite();
252
+
253
+ // Use drizzle or get collections...
254
+ }
255
+ ```
256
+
257
+ **Returns:**
258
+ - `drizzle: DrizzleDB` - Drizzle ORM instance
259
+ - `getCollection: (tableName) => Collection` - Get TanStack DB collection
260
+
261
+ #### `useSqliteCollection(tableName)`
262
+
263
+ Hook for a specific collection with automatic ref counting:
264
+
265
+ ```typescript
266
+ function TodoList() {
267
+ const collection = useSqliteCollection("todos");
268
+ const [todos] = collection.useStore();
269
+
270
+ // Collection is automatically cleaned up on unmount
271
+ }
272
+ ```
273
+
274
+ ### Worker API
275
+
276
+ #### `SqliteWorkerManager`
277
+
278
+ Manages multiple SQLite databases in a single worker:
279
+
280
+ ```typescript
281
+ import { getSqliteWorkerManager, initializeSqliteWorker } from "@firtoz/drizzle-sqlite-wasm";
282
+
283
+ // Initialize global worker
284
+ await initializeSqliteWorker(SqliteWorker, true);
285
+
286
+ // Get manager
287
+ const manager = getSqliteWorkerManager();
288
+
289
+ // Get database instance
290
+ const db = manager.getDatabase("mydb");
291
+ ```
292
+
293
+ **Global Functions:**
294
+ - `initializeSqliteWorker(Worker, debug?)` - Initialize the global worker
295
+ - `getSqliteWorkerManager()` - Get the global manager instance
296
+ - `isSqliteWorkerInitialized()` - Check if worker is initialized
297
+ - `resetSqliteWorkerManager()` - Reset the global manager
298
+
299
+ ### Direct SQLite (Non-Worker)
300
+
301
+ #### `drizzleSqliteWasm(sqliteDb, config, debug?)`
302
+
303
+ Use SQLite WASM directly in the main thread (for testing or synchronous contexts):
304
+
305
+ ```typescript
306
+ import { drizzleSqliteWasm } from "@firtoz/drizzle-sqlite-wasm";
307
+ import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
308
+
309
+ const sqlite3 = await sqlite3InitModule();
310
+ const sqliteDb = new sqlite3.oo1.DB("/mydb.db", "ct");
311
+
312
+ const drizzle = drizzleSqliteWasm(sqliteDb, { schema }, true);
313
+
314
+ // Use drizzle normally
315
+ const todos = await drizzle.select().from(todoTable).all();
316
+ ```
317
+
318
+ ### Migrations
319
+
320
+ #### `customSqliteMigrate(config)`
321
+
322
+ Run custom SQLite migrations:
323
+
324
+ ```typescript
325
+ import { customSqliteMigrate } from "@firtoz/drizzle-sqlite-wasm/sqlite-wasm-migrator";
326
+
327
+ await customSqliteMigrate({
328
+ database: sqliteDb,
329
+ journal: journalData,
330
+ migrations: {
331
+ "0000_init": "CREATE TABLE todos (id TEXT PRIMARY KEY, title TEXT NOT NULL);",
332
+ "0001_add_completed": "ALTER TABLE todos ADD COLUMN completed INTEGER DEFAULT 0;",
333
+ },
334
+ debug: true,
335
+ });
336
+ ```
337
+
338
+ **Config:**
339
+ - `database: Database` - SQLite WASM database instance
340
+ - `journal: Journal` - Drizzle journal
341
+ - `migrations: Record<string, string>` - SQL migration strings
342
+ - `debug?: boolean` - Enable debug logging
343
+
344
+ ## Advanced Usage
345
+
346
+ ### Multiple Databases
347
+
348
+ ```typescript
349
+ function App() {
350
+ return (
351
+ <>
352
+ <DrizzleSqliteProvider
353
+ worker={SqliteWorker}
354
+ dbName="app-data"
355
+ schema={appSchema}
356
+ migrations={appMigrations}
357
+ >
358
+ <AppContent />
359
+ </DrizzleSqliteProvider>
360
+
361
+ <DrizzleSqliteProvider
362
+ worker={SqliteWorker}
363
+ dbName="cache-data"
364
+ schema={cacheSchema}
365
+ migrations={cacheMigrations}
366
+ >
367
+ <CacheManager />
368
+ </DrizzleSqliteProvider>
369
+ </>
370
+ );
371
+ }
372
+ ```
373
+
374
+ ### Custom Worker Configuration
375
+
376
+ ```typescript
377
+ import { initializeSqliteWorker, getSqliteWorkerManager } from "@firtoz/drizzle-sqlite-wasm";
378
+
379
+ // Initialize with custom worker
380
+ await initializeSqliteWorker(MyCustomWorker, true);
381
+
382
+ // Get manager for manual control
383
+ const manager = getSqliteWorkerManager();
384
+
385
+ // Access databases
386
+ const db1 = manager.getDatabase("app-data");
387
+ const db2 = manager.getDatabase("cache-data");
388
+ ```
389
+
390
+ ### Complex Queries with Drizzle
391
+
392
+ ```typescript
393
+ import { eq, and, or, gt, like, desc } from "drizzle-orm";
394
+ import { todoTable } from "./schema";
395
+
396
+ function TodoComponent() {
397
+ const { drizzle } = useDrizzleSqlite();
398
+
399
+ const searchTodos = async (searchTerm: string) => {
400
+ return await drizzle
401
+ .select()
402
+ .from(todoTable)
403
+ .where(
404
+ and(
405
+ like(todoTable.title, `%${searchTerm}%`),
406
+ eq(todoTable.completed, false),
407
+ gt(todoTable.createdAt, yesterday)
408
+ )
409
+ )
410
+ .orderBy(desc(todoTable.createdAt))
411
+ .limit(10)
412
+ .all();
413
+ };
414
+ }
415
+ ```
416
+
417
+ ### Transaction Support
418
+
419
+ ```typescript
420
+ function TodoComponent() {
421
+ const { drizzle } = useDrizzleSqlite();
422
+
423
+ const createTodoWithCategory = async (title: string, category: string) => {
424
+ return await drizzle.transaction(async (tx) => {
425
+ // Insert category if it doesn't exist
426
+ const [existingCategory] = await tx
427
+ .select()
428
+ .from(categoryTable)
429
+ .where(eq(categoryTable.name, category))
430
+ .limit(1)
431
+ .all();
432
+
433
+ let categoryId = existingCategory?.id;
434
+
435
+ if (!categoryId) {
436
+ const [newCategory] = await tx
437
+ .insert(categoryTable)
438
+ .values({ name: category })
439
+ .returning();
440
+ categoryId = newCategory.id;
441
+ }
442
+
443
+ // Insert todo
444
+ const [todo] = await tx
445
+ .insert(todoTable)
446
+ .values({ title, categoryId })
447
+ .returning();
448
+
449
+ return todo;
450
+ });
451
+ };
452
+ }
453
+ ```
454
+
455
+ ## Performance Best Practices
456
+
457
+ ### 1. Use Indexes
458
+
459
+ ```typescript
460
+ const todoTable = syncableTable("todos", {
461
+ title: text("title").notNull(),
462
+ completed: integer("completed", { mode: "boolean" }).notNull(),
463
+ userId: text("userId").notNull(),
464
+ }, (table) => [
465
+ index("completed_idx").on(table.completed),
466
+ index("user_id_idx").on(table.userId),
467
+ index("user_completed_idx").on(table.userId, table.completed),
468
+ ]);
469
+ ```
470
+
471
+ ### 2. Use Web Workers
472
+
473
+ Always use the Worker mode for production to keep the UI responsive:
474
+
475
+ ```typescript
476
+ // ✅ Good - Non-blocking
477
+ const { drizzle } = useDrizzleSqliteDb(SqliteWorker, "mydb", schema, migrations);
478
+
479
+ // ❌ Bad - Blocks UI thread
480
+ const drizzle = drizzleSqliteWasm(sqliteDb, { schema });
481
+ ```
482
+
483
+ ### 3. Batch Operations
484
+
485
+ ```typescript
486
+ // ✅ Good - Single transaction
487
+ await drizzle.insert(todoTable).values([
488
+ { title: "Todo 1" },
489
+ { title: "Todo 2" },
490
+ { title: "Todo 3" },
491
+ ]);
492
+
493
+ // ❌ Bad - Multiple transactions
494
+ for (const title of titles) {
495
+ await drizzle.insert(todoTable).values({ title });
496
+ }
497
+ ```
498
+
499
+ ### 4. Use Collections for Reactive Data
500
+
501
+ ```typescript
502
+ // ✅ Good - Reactive updates
503
+ const collection = useSqliteCollection("todos");
504
+ const [todos] = collection.useStore(); // Automatically updates
505
+
506
+ // ❌ Bad - Manual polling
507
+ useEffect(() => {
508
+ const interval = setInterval(loadTodos, 1000);
509
+ return () => clearInterval(interval);
510
+ }, []);
511
+ ```
512
+
513
+ ## Troubleshooting
514
+
515
+ ### Worker Not Loading
516
+
517
+ **Vite:** Make sure you're using the `?worker` suffix:
518
+ ```typescript
519
+ import SqliteWorker from "path/to/sqlite.worker?worker";
520
+ ```
521
+
522
+ **Webpack:** Check that worker-loader is configured or use `new URL()` pattern
523
+
524
+ ### Database Not Ready
525
+
526
+ Always wait for the ready promise:
527
+
528
+ ```typescript
529
+ const { drizzle, readyPromise } = useDrizzleSqliteDb(/* ... */);
530
+
531
+ useEffect(() => {
532
+ readyPromise.then(() => {
533
+ // Now safe to query
534
+ loadData();
535
+ });
536
+ }, [readyPromise]);
537
+ ```
538
+
539
+ ### Performance Issues
540
+
541
+ - Enable debug mode to see timing: `useDrizzleSqliteDb(Worker, dbName, schema, migrations, true)`
542
+ - Check Performance tab in DevTools
543
+ - Add indexes to frequently queried columns
544
+
545
+ ### Migration Errors
546
+
547
+ Check the console for specific errors. Common issues:
548
+ - Missing migration files
549
+ - Invalid SQL in migrations
550
+ - Schema conflicts
551
+
552
+ Enable debug mode for detailed logs:
553
+ ```typescript
554
+ <DrizzleSqliteProvider
555
+ worker={SqliteWorker}
556
+ dbName="mydb"
557
+ schema={schema}
558
+ migrations={migrations}
559
+ debug={true} // Enable debug logging
560
+ >
561
+ ```
562
+
563
+ ## Type Utilities
564
+
565
+ Re-exported from `@firtoz/drizzle-utils`:
566
+
567
+ ```typescript
568
+ import {
569
+ syncableTable,
570
+ makeId,
571
+ type IdOf,
572
+ type TableId,
573
+ type Branded,
574
+ type SelectSchema,
575
+ type InsertSchema,
576
+ } from "@firtoz/drizzle-sqlite-wasm";
577
+ ```
578
+
579
+ ## Examples
580
+
581
+ Check out the test playground for complete examples:
582
+ - `tests/test-playground/e2e/` - E2E tests
583
+
584
+ ## Dependencies
585
+
586
+ - `@firtoz/drizzle-utils`
587
+ - `@firtoz/maybe-error`
588
+ - `@firtoz/worker-helper`
589
+ - `@sqlite.org/sqlite-wasm`
590
+ - `drizzle-orm`
591
+ - `@tanstack/db`
592
+ - `react`
593
+ - `zod`
594
+
595
+ ## License
596
+
597
+ MIT
598
+
599
+ ## Author
600
+
601
+ Firtina Ozbalikchi <firtoz@github.com>
602
+
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@firtoz/drizzle-sqlite-wasm",
3
+ "version": "0.1.0",
4
+ "description": "Drizzle SQLite WASM bindings",
5
+ "main": "./src/index.ts",
6
+ "module": "./src/index.ts",
7
+ "types": "./src/index.ts",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./src/index.ts",
12
+ "import": "./src/index.ts",
13
+ "require": "./src/index.ts"
14
+ },
15
+ "./drizzle-sqlite-wasm-worker": {
16
+ "types": "./src/drizzle/worker.ts",
17
+ "import": "./src/drizzle/worker.ts",
18
+ "require": "./src/drizzle/worker.ts"
19
+ },
20
+ "./sqlite-wasm-migrator": {
21
+ "types": "./src/migration/migrator.ts",
22
+ "import": "./src/migration/migrator.ts",
23
+ "require": "./src/migration/migrator.ts"
24
+ },
25
+ "./drizzleCollectionOptions": {
26
+ "types": "./src/collections/sqlite-collection.ts",
27
+ "import": "./src/collections/sqlite-collection.ts",
28
+ "require": "./src/collections/sqlite-collection.ts"
29
+ },
30
+ "./*": {
31
+ "types": "./src/*.ts",
32
+ "import": "./src/*.ts",
33
+ "require": "./src/*.ts"
34
+ }
35
+ },
36
+ "files": [
37
+ "src/**/*.ts",
38
+ "!src/**/*.test.ts",
39
+ "README.md",
40
+ "CHANGELOG.md"
41
+ ],
42
+ "scripts": {
43
+ "typecheck": "tsc --noEmit -p ./tsconfig.json",
44
+ "lint": "biome check --write src",
45
+ "lint:ci": "biome ci src",
46
+ "format": "biome format src --write",
47
+ "test": "bun test --pass-with-no-tests",
48
+ "test:watch": "bun test --watch"
49
+ },
50
+ "keywords": [
51
+ "typescript",
52
+ "sqlite",
53
+ "wasm",
54
+ "drizzle"
55
+ ],
56
+ "author": "Firtina Ozbalikchi <firtoz@github.com>",
57
+ "license": "MIT",
58
+ "homepage": "https://github.com/firtoz/fullstack-toolkit#readme",
59
+ "repository": {
60
+ "type": "git",
61
+ "url": "https://github.com/firtoz/fullstack-toolkit.git",
62
+ "directory": "packages/drizzle-sqlite-wasm"
63
+ },
64
+ "bugs": {
65
+ "url": "https://github.com/firtoz/fullstack-toolkit/issues"
66
+ },
67
+ "engines": {
68
+ "node": ">=18.0.0"
69
+ },
70
+ "publishConfig": {
71
+ "access": "public"
72
+ },
73
+ "dependencies": {
74
+ "@firtoz/drizzle-utils": "^0.1.0",
75
+ "@firtoz/maybe-error": "^1.5.1",
76
+ "@firtoz/worker-helper": "^1.0.0",
77
+ "@sqlite.org/sqlite-wasm": "^3.51.1-build1",
78
+ "@tanstack/db": "^0.5.0",
79
+ "drizzle-orm": "^0.44.7",
80
+ "drizzle-valibot": "^0.4.2",
81
+ "react": "^19.2.0",
82
+ "valibot": "^1.1.0",
83
+ "zod": "^4.1.12"
84
+ },
85
+ "devDependencies": {
86
+ "@standard-schema/spec": "^1.0.0",
87
+ "@types/react": "^19.2.5"
88
+ }
89
+ }