@firtoz/drizzle-sqlite-wasm 0.1.0 → 0.2.1

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,29 @@
1
1
  # @firtoz/drizzle-sqlite-wasm
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`46059a2`](https://github.com/firtoz/fullstack-toolkit/commit/46059a28bd0135414b9ed022ffe162a2292adae3)]:
8
+ - @firtoz/drizzle-utils@0.3.0
9
+
10
+ ## 0.2.0
11
+
12
+ ### Minor Changes
13
+
14
+ - [`58d2cba`](https://github.com/firtoz/fullstack-toolkit/commit/58d2cbac8ea4e540b5460b7088b6b62e50357558) Thanks [@firtoz](https://github.com/firtoz)! - Add sync mode functionality for IndexedDB and SQLite collections
15
+
16
+ - Introduced support for both eager and on-demand sync modes in Drizzle providers
17
+ - Implemented operation tracking via interceptors to monitor database operations during queries
18
+ - Enhanced DrizzleIndexedDBProvider and DrizzleSqliteProvider to accept interceptors for debugging and testing purposes
19
+ - Added createInsertSchemaWithDefaults and createInsertSchemaWithIdDefault utilities for better schema management
20
+ - Refactored collection utilities to improve data handling and consistency across collections
21
+
22
+ ### Patch Changes
23
+
24
+ - Updated dependencies [[`58d2cba`](https://github.com/firtoz/fullstack-toolkit/commit/58d2cbac8ea4e540b5460b7088b6b62e50357558)]:
25
+ - @firtoz/drizzle-utils@0.2.0
26
+
3
27
  ## 0.1.0
4
28
 
5
29
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firtoz/drizzle-sqlite-wasm",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Drizzle SQLite WASM bindings",
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -71,19 +71,19 @@
71
71
  "access": "public"
72
72
  },
73
73
  "dependencies": {
74
- "@firtoz/drizzle-utils": "^0.1.0",
74
+ "@firtoz/drizzle-utils": "^0.3.0",
75
75
  "@firtoz/maybe-error": "^1.5.1",
76
76
  "@firtoz/worker-helper": "^1.0.0",
77
- "@sqlite.org/sqlite-wasm": "^3.51.1-build1",
78
- "@tanstack/db": "^0.5.0",
77
+ "@sqlite.org/sqlite-wasm": "^3.51.1-build2",
78
+ "@tanstack/db": "^0.5.10",
79
79
  "drizzle-orm": "^0.44.7",
80
80
  "drizzle-valibot": "^0.4.2",
81
81
  "react": "^19.2.0",
82
- "valibot": "^1.1.0",
83
- "zod": "^4.1.12"
82
+ "valibot": "^1.2.0",
83
+ "zod": "^4.1.13"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@standard-schema/spec": "^1.0.0",
87
- "@types/react": "^19.2.5"
87
+ "@types/react": "^19.2.7"
88
88
  }
89
89
  }
@@ -1,15 +1,5 @@
1
- // const selectSchema = createSelectSchema(todoTable);
2
-
3
- import type {
4
- CollectionConfig,
5
- InferSchemaOutput,
6
- LoadSubsetOptions,
7
- SyncConfig,
8
- SyncConfigRes,
9
- SyncMode,
10
- } from "@tanstack/db";
1
+ import type { InferSchemaOutput, SyncMode } from "@tanstack/db";
11
2
  import type { IR } from "@tanstack/db";
12
- import { DeduplicatedLoadSubset } from "@tanstack/db";
13
3
  import {
14
4
  eq,
15
5
  sql,
@@ -29,25 +19,25 @@ import {
29
19
  asc,
30
20
  desc,
31
21
  type SQL,
32
- getTableColumns,
33
22
  } from "drizzle-orm";
34
- import type {
35
- SQLiteUpdateSetSource,
36
- BaseSQLiteDatabase,
37
- SQLiteInsertValue,
23
+ import {
24
+ type SQLiteUpdateSetSource,
25
+ type BaseSQLiteDatabase,
26
+ type SQLiteInsertValue,
27
+ SQLiteColumn,
38
28
  } from "drizzle-orm/sqlite-core";
39
- import { createInsertSchema } from "drizzle-valibot";
40
- import * as v from "valibot";
41
29
  import type {
42
30
  SelectSchema,
43
31
  TableWithRequiredFields,
32
+ BaseSyncConfig,
33
+ SyncBackend,
34
+ } from "@firtoz/drizzle-utils";
35
+ import {
36
+ createSyncFunction,
37
+ createInsertSchemaWithIdDefault,
38
+ createGetKeyFunction,
39
+ createCollectionConfig,
44
40
  } from "@firtoz/drizzle-utils";
45
-
46
- // WORKAROUND: DeduplicatedLoadSubset has a bug where toggling queries (e.g., isNull/isNotNull)
47
- // creates invalid expressions like not(or(isNull(...), not(isNull(...))))
48
- // See: https://github.com/TanStack/db/issues/828
49
- // TODO: Re-enable once the bug is fixed
50
- const useDedupe = false as boolean;
51
41
 
52
42
  export type AnyDrizzleDatabase = BaseSQLiteDatabase<
53
43
  "async",
@@ -59,6 +49,70 @@ export type AnyDrizzleDatabase = BaseSQLiteDatabase<
59
49
  export type DrizzleSchema<TDrizzle extends AnyDrizzleDatabase> =
60
50
  TDrizzle["_"]["fullSchema"];
61
51
 
52
+ /**
53
+ * Operation tracking for SQLite queries
54
+ * Useful for testing and debugging to verify what operations are actually performed
55
+ *
56
+ * Uses discriminated unions for type safety - TypeScript can narrow the type based on the 'type' field
57
+ */
58
+ export type SQLOperation =
59
+ | {
60
+ type: "select-all";
61
+ tableName: string;
62
+ itemsReturned: unknown[];
63
+ itemCount: number;
64
+ context: string;
65
+ sql?: string;
66
+ timestamp: number;
67
+ }
68
+ | {
69
+ type: "select-where";
70
+ tableName: string;
71
+ whereClause: string;
72
+ itemsReturned: unknown[];
73
+ itemCount: number;
74
+ context: string;
75
+ sql?: string;
76
+ timestamp: number;
77
+ }
78
+ | {
79
+ type: "write";
80
+ tableName: string;
81
+ itemsWritten: unknown[];
82
+ writeCount: number;
83
+ context: string;
84
+ timestamp: number;
85
+ }
86
+ | {
87
+ type: "insert";
88
+ tableName: string;
89
+ item: unknown;
90
+ sql?: string;
91
+ timestamp: number;
92
+ }
93
+ | {
94
+ type: "update";
95
+ tableName: string;
96
+ updates: unknown;
97
+ sql?: string;
98
+ timestamp: number;
99
+ }
100
+ | {
101
+ type: "delete";
102
+ tableName: string;
103
+ sql?: string;
104
+ timestamp: number;
105
+ };
106
+
107
+ /**
108
+ * Interceptor interface for tracking SQLite operations
109
+ * Allows tests and debugging tools to observe what operations are performed
110
+ */
111
+ export interface SQLInterceptor {
112
+ /** Called when any SQLite operation is performed */
113
+ onOperation?: (operation: SQLOperation) => void;
114
+ }
115
+
62
116
  export interface DrizzleCollectionConfig<
63
117
  TDrizzle extends AnyDrizzleDatabase,
64
118
  TTableName extends ValidTableNames<DrizzleSchema<TDrizzle>>,
@@ -80,6 +134,10 @@ export interface DrizzleCollectionConfig<
80
134
  * This ensures WAL is flushed to the main database file for OPFS persistence
81
135
  */
82
136
  checkpoint?: () => Promise<void>;
137
+ /**
138
+ * Optional interceptor for tracking SQLite operations (for testing/debugging)
139
+ */
140
+ interceptor?: SQLInterceptor;
83
141
  }
84
142
 
85
143
  export type ValidTableNames<TSchema extends Record<string, unknown>> = {
@@ -88,6 +146,12 @@ export type ValidTableNames<TSchema extends Record<string, unknown>> = {
88
146
 
89
147
  /**
90
148
  * Converts TanStack DB IR BasicExpression to Drizzle SQL expression
149
+ *
150
+ * Supported operators that TanStack DB pushes down to backend (SUPPORTED_COLLECTION_FUNCS):
151
+ * - eq, gt, lt, gte, lte, and, or, in, isNull, isUndefined, not
152
+ *
153
+ * Additional operators handled for completeness (won't be pushed down in on-demand mode):
154
+ * - ne, isNotNull, like
91
155
  */
92
156
  function convertBasicExpressionToDrizzle<TTable extends Table>(
93
157
  expression: IR.BasicExpression,
@@ -99,7 +163,13 @@ function convertBasicExpressionToDrizzle<TTable extends Table>(
99
163
  const columnName = propRef.path[propRef.path.length - 1];
100
164
  const column = table[columnName as keyof typeof table];
101
165
 
102
- if (!column || typeof column !== "object" || !("_" in column)) {
166
+ if (!column || !(column instanceof SQLiteColumn)) {
167
+ console.error("[SQLite Collection] Column lookup failed:", {
168
+ columnName,
169
+ column,
170
+ tableKeys: Object.keys(table),
171
+ hasColumn: columnName in table,
172
+ });
103
173
  throw new Error(`Column ${String(columnName)} not found in table`);
104
174
  }
105
175
 
@@ -193,22 +263,11 @@ export function sqliteCollectionOptions<
193
263
  const TTableName extends string & ValidTableNames<DrizzleSchema<TDrizzle>>,
194
264
  TTable extends DrizzleSchema<TDrizzle>[TTableName] & TableWithRequiredFields,
195
265
  >(config: DrizzleCollectionConfig<TDrizzle, TTableName>) {
196
- type CollectionType = CollectionConfig<
197
- InferSchemaOutput<SelectSchema<TTable>>,
198
- string,
199
- // biome-ignore lint/suspicious/noExplicitAny: Schema type parameter needs to be flexible
200
- any
201
- >;
202
-
203
266
  const tableName = config.tableName as string &
204
267
  ValidTableNames<DrizzleSchema<TDrizzle>>;
205
268
 
206
269
  const table = config.drizzle?._.fullSchema[tableName] as TTable;
207
270
 
208
- let insertListener: CollectionType["onInsert"] | null = null;
209
- let updateListener: CollectionType["onUpdate"] | null = null;
210
- let deleteListener: CollectionType["onDelete"] | null = null;
211
-
212
271
  // Transaction queue to serialize SQLite transactions (SQLite only supports one transaction at a time)
213
272
  // The queue ensures transactions run sequentially and continues even if one fails
214
273
  let transactionQueue = Promise.resolve();
@@ -225,52 +284,141 @@ export function sqliteCollectionOptions<
225
284
  return result;
226
285
  };
227
286
 
228
- const sync: SyncConfig<
229
- InferSchemaOutput<SelectSchema<TTable>>,
230
- string
231
- >["sync"] = (params) => {
232
- const { begin, write, commit, markReady } = params;
287
+ // Create backend-specific implementation
288
+ const backend: SyncBackend<TTable> = {
289
+ initialLoad: async (write) => {
290
+ const items = (await config.drizzle
291
+ .select()
292
+ .from(table)) as unknown as InferSchemaOutput<SelectSchema<TTable>>[];
293
+
294
+ // Log SQL operation
295
+ if (config.interceptor?.onOperation) {
296
+ config.interceptor.onOperation({
297
+ type: "select-all",
298
+ tableName: config.tableName as string,
299
+ itemsReturned: items,
300
+ itemCount: items.length,
301
+ context: "Initial load (eager mode)",
302
+ timestamp: Date.now(),
303
+ });
304
+ }
233
305
 
234
- const initialSync = async () => {
235
- await config.readyPromise;
306
+ // Log write operation
307
+ if (config.interceptor?.onOperation) {
308
+ config.interceptor.onOperation({
309
+ type: "write",
310
+ tableName: config.tableName as string,
311
+ itemsWritten: items,
312
+ writeCount: items.length,
313
+ context: "Initial load (eager mode)",
314
+ timestamp: Date.now(),
315
+ });
316
+ }
236
317
 
237
- try {
238
- begin();
318
+ for (const item of items) {
319
+ write(item);
320
+ }
321
+ },
239
322
 
240
- const items = (await config.drizzle
241
- .select()
242
- .from(table)) as unknown as InferSchemaOutput<SelectSchema<TTable>>[];
323
+ loadSubset: async (options, write) => {
324
+ // Build the query with optional where, orderBy, and limit
325
+ // Use $dynamic() to enable dynamic query building
326
+ let query = config.drizzle.select().from(table).$dynamic();
327
+
328
+ // Convert TanStack DB IR expressions to Drizzle expressions
329
+ let hasWhere = false;
330
+ if (options.where) {
331
+ const drizzleWhere = convertBasicExpressionToDrizzle(
332
+ options.where,
333
+ table,
334
+ );
335
+ query = query.where(drizzleWhere);
336
+ hasWhere = true;
337
+ }
243
338
 
244
- for (const item of items) {
245
- write({
246
- type: "insert",
247
- value: item,
339
+ if (options.orderBy) {
340
+ const drizzleOrderBy = convertOrderByToDrizzle(options.orderBy, table);
341
+ query = query.orderBy(...drizzleOrderBy);
342
+ }
343
+
344
+ if (options.limit !== undefined) {
345
+ query = query.limit(options.limit);
346
+ }
347
+
348
+ const items = (await query) as unknown as InferSchemaOutput<
349
+ SelectSchema<TTable>
350
+ >[];
351
+
352
+ // Log SQL operation
353
+ if (config.interceptor?.onOperation) {
354
+ const contextParts: string[] = ["On-demand load"];
355
+ if (options.orderBy) {
356
+ contextParts.push("with sorting");
357
+ }
358
+ if (options.limit !== undefined) {
359
+ contextParts.push(`limit ${options.limit}`);
360
+ }
361
+
362
+ if (hasWhere) {
363
+ config.interceptor.onOperation({
364
+ type: "select-where",
365
+ tableName: config.tableName as string,
366
+ whereClause: "WHERE clause applied",
367
+ itemsReturned: items,
368
+ itemCount: items.length,
369
+ context: contextParts.join(", "),
370
+ timestamp: Date.now(),
371
+ });
372
+ } else {
373
+ config.interceptor.onOperation({
374
+ type: "select-all",
375
+ tableName: config.tableName as string,
376
+ itemsReturned: items,
377
+ itemCount: items.length,
378
+ context: contextParts.join(", "),
379
+ timestamp: Date.now(),
248
380
  });
249
381
  }
382
+ }
250
383
 
251
- commit();
252
- } finally {
253
- markReady();
384
+ // Log write operation
385
+ if (config.interceptor?.onOperation) {
386
+ const contextParts: string[] = ["On-demand load"];
387
+ if (hasWhere) {
388
+ contextParts.push("with WHERE clause");
389
+ }
390
+ if (options.orderBy) {
391
+ contextParts.push("with sorting");
392
+ }
393
+ if (options.limit !== undefined) {
394
+ contextParts.push(`limit ${options.limit}`);
395
+ }
396
+
397
+ config.interceptor.onOperation({
398
+ type: "write",
399
+ tableName: config.tableName as string,
400
+ itemsWritten: items,
401
+ writeCount: items.length,
402
+ context: contextParts.join(", "),
403
+ timestamp: Date.now(),
404
+ });
254
405
  }
255
- };
256
406
 
257
- if (config.syncMode === "eager" || !config.syncMode) {
258
- initialSync();
259
- } else {
260
- markReady();
261
- }
407
+ for (const item of items) {
408
+ write(item);
409
+ }
410
+ },
262
411
 
263
- insertListener = async (params) => {
264
- // Store results to write after transaction succeeds
412
+ handleInsert: async (mutations) => {
265
413
  const results: Array<InferSchemaOutput<SelectSchema<TTable>>> = [];
266
414
 
267
415
  // Queue the transaction to serialize SQLite operations
268
416
  await queueTransaction(async () => {
269
417
  await config.drizzle.transaction(async (tx) => {
270
- for (const item of params.transaction.mutations) {
418
+ for (const mutation of mutations) {
271
419
  // TanStack DB applies schema transform (including ID default) before calling this listener
272
- // So item.modified already has the ID from insertSchemaWithIdDefault
273
- const itemToInsert = item.modified;
420
+ // So mutation.modified already has the ID from insertSchemaWithIdDefault
421
+ const itemToInsert = mutation.modified;
274
422
 
275
423
  if (config.debug) {
276
424
  console.log(
@@ -306,27 +454,20 @@ export function sqliteCollectionOptions<
306
454
  }
307
455
  });
308
456
 
309
- // Only update reactive store after transaction succeeds
310
- begin();
311
- for (const result of results) {
312
- write({
313
- type: "insert",
314
- value: result as unknown as InferSchemaOutput<SelectSchema<TTable>>,
315
- });
316
- }
317
- commit();
318
- };
457
+ return results;
458
+ },
319
459
 
320
- updateListener = async (params) => {
321
- // Queue the transaction to serialize SQLite operations
460
+ handleUpdate: async (mutations) => {
322
461
  const results: Array<InferSchemaOutput<SelectSchema<TTable>>> = [];
462
+
463
+ // Queue the transaction to serialize SQLite operations
323
464
  await queueTransaction(async () => {
324
465
  await config.drizzle.transaction(async (tx) => {
325
- for (const item of params.transaction.mutations) {
466
+ for (const mutation of mutations) {
326
467
  if (config.debug) {
327
468
  console.log(
328
469
  `[${new Date().toISOString()}] updateListener updating`,
329
- item,
470
+ mutation,
330
471
  );
331
472
  }
332
473
 
@@ -335,10 +476,11 @@ export function sqliteCollectionOptions<
335
476
  (await tx
336
477
  .update(table)
337
478
  .set({
338
- ...item.changes,
479
+ ...mutation.changes,
339
480
  updatedAt: updateTime,
340
481
  } as SQLiteUpdateSetSource<typeof table>)
341
- .where(eq(table.id, item.key))
482
+ // biome-ignore lint/suspicious/noExplicitAny: Key is string but table.id is branded type
483
+ .where(eq(table.id, mutation.key as any))
342
484
  .returning()) as Array<InferSchemaOutput<SelectSchema<TTable>>>;
343
485
 
344
486
  if (config.debug) {
@@ -359,24 +501,16 @@ export function sqliteCollectionOptions<
359
501
  }
360
502
  });
361
503
 
362
- // Update the reactive store with actual database results
363
- // This happens after checkpoint completes
364
- begin();
365
- for (const result of results) {
366
- write({
367
- type: "update",
368
- value: result,
369
- });
370
- }
371
- commit();
372
- };
504
+ return results;
505
+ },
373
506
 
374
- deleteListener = async (params) => {
507
+ handleDelete: async (mutations) => {
375
508
  // Queue the transaction to serialize SQLite operations
376
509
  await queueTransaction(async () => {
377
510
  await config.drizzle.transaction(async (tx) => {
378
- for (const item of params.transaction.mutations) {
379
- await tx.delete(table).where(eq(table.id, item.key));
511
+ for (const mutation of mutations) {
512
+ // biome-ignore lint/suspicious/noExplicitAny: Key is string but table.id is branded type
513
+ await tx.delete(table).where(eq(table.id, mutation.key as any));
380
514
  }
381
515
  });
382
516
 
@@ -384,149 +518,47 @@ export function sqliteCollectionOptions<
384
518
  if (config.checkpoint) {
385
519
  await config.checkpoint();
386
520
  }
387
-
388
- begin();
389
- for (const item of params.transaction.mutations) {
390
- if (config.debug) {
391
- console.log(
392
- `[${new Date().toISOString()}] deleteListener write`,
393
- item,
394
- );
395
- }
396
- write({
397
- type: "delete",
398
- value: item.modified,
399
- });
400
- }
401
- commit();
402
521
  });
403
- };
404
-
405
- const loadSubset = async (options: LoadSubsetOptions) => {
406
- await config.readyPromise;
407
-
408
- begin();
522
+ },
523
+ };
409
524
 
410
- try {
411
- // Build the query with optional where, orderBy, and limit
412
- // Use $dynamic() to enable dynamic query building
413
- let query = config.drizzle.select().from(table).$dynamic();
525
+ // Create sync function using shared utilities
526
+ const baseSyncConfig: BaseSyncConfig<TTable> = {
527
+ table,
528
+ readyPromise: config.readyPromise,
529
+ syncMode: config.syncMode,
530
+ debug: config.debug,
531
+ };
414
532
 
415
- // Convert TanStack DB IR expressions to Drizzle expressions
416
- if (options.where) {
417
- const drizzleWhere = convertBasicExpressionToDrizzle(
418
- options.where,
419
- table,
420
- );
421
- query = query.where(drizzleWhere);
422
- }
533
+ const syncResult = createSyncFunction(baseSyncConfig, backend);
423
534
 
424
- if (options.orderBy) {
425
- const drizzleOrderBy = convertOrderByToDrizzle(
426
- options.orderBy,
427
- table,
428
- );
429
- query = query.orderBy(...drizzleOrderBy);
535
+ // Create insert schema with ID default
536
+ // (Other defaults like createdAt/updatedAt are handled by SQLite)
537
+ const schema = createInsertSchemaWithIdDefault(table);
538
+
539
+ // Create collection config using shared utilities
540
+ const collectionConfig = createCollectionConfig({
541
+ schema,
542
+ getKey: createGetKeyFunction<TTable>(),
543
+ syncResult,
544
+ onInsert: config.debug
545
+ ? async (params) => {
546
+ console.log("onInsert", params);
430
547
  }
431
-
432
- if (options.limit !== undefined) {
433
- query = query.limit(options.limit);
548
+ : undefined,
549
+ onUpdate: config.debug
550
+ ? async (params) => {
551
+ console.log("onUpdate", params);
434
552
  }
435
-
436
- const items = (await query) as unknown as InferSchemaOutput<
437
- SelectSchema<TTable>
438
- >[];
439
-
440
- for (const item of items) {
441
- write({
442
- type: "insert",
443
- value: item,
444
- });
553
+ : undefined,
554
+ onDelete: config.debug
555
+ ? async (params) => {
556
+ console.log("onDelete", params);
445
557
  }
446
-
447
- commit();
448
- } catch (error) {
449
- // If there's an error, we should still commit to maintain consistency
450
- commit();
451
- throw error;
452
- }
453
- };
454
-
455
- // Create deduplicated loadSubset wrapper to avoid redundant queries
456
- let loadSubsetDedupe: DeduplicatedLoadSubset | null = null;
457
- if (useDedupe) {
458
- loadSubsetDedupe = new DeduplicatedLoadSubset({
459
- loadSubset,
460
- });
461
- }
462
-
463
- return {
464
- cleanup: () => {
465
- insertListener = null;
466
- updateListener = null;
467
- deleteListener = null;
468
- loadSubsetDedupe?.reset();
469
- },
470
- loadSubset: loadSubsetDedupe?.loadSubset ?? loadSubset,
471
- } satisfies SyncConfigRes;
472
- };
473
-
474
- // Create insert schema and augment it to apply ID default
475
- // (Other defaults like createdAt/updatedAt are handled by SQLite)
476
- const insertSchema = createInsertSchema(table);
477
- const columns = getTableColumns(table);
478
- const idColumn = columns.id;
479
-
480
- const insertSchemaWithIdDefault = v.pipe(
481
- insertSchema,
482
- v.transform((input) => {
483
- const result = { ...input } as Record<string, unknown>;
484
-
485
- // Apply ID default if missing
486
- if (result.id === undefined && idColumn?.defaultFn) {
487
- result.id = idColumn.defaultFn();
488
- }
489
-
490
- return result as typeof input;
491
- }),
492
- );
493
-
494
- return {
495
- schema: insertSchemaWithIdDefault,
496
- getKey: (item: InferSchemaOutput<SelectSchema<TTable>>) => {
497
- const id = (item as { id: string }).id;
498
- return id;
499
- },
500
- sync: {
501
- sync,
502
- },
503
- onInsert: async (
504
- params: Parameters<NonNullable<CollectionType["onInsert"]>>[0],
505
- ) => {
506
- if (config.debug) {
507
- console.log("onInsert", params);
508
- }
509
-
510
- await insertListener?.(params);
511
- },
512
- onUpdate: async (
513
- params: Parameters<NonNullable<CollectionType["onUpdate"]>>[0],
514
- ) => {
515
- if (config.debug) {
516
- console.log("onUpdate", params);
517
- }
518
-
519
- await updateListener?.(params);
520
- },
521
- onDelete: async (
522
- params: Parameters<NonNullable<CollectionType["onDelete"]>>[0],
523
- ) => {
524
- if (config.debug) {
525
- console.log("onDelete", params);
526
- }
527
-
528
- await deleteListener?.(params);
529
- },
558
+ : undefined,
530
559
  syncMode: config.syncMode,
531
- } satisfies CollectionType;
560
+ });
561
+
562
+ // biome-ignore lint/suspicious/noExplicitAny: Collection schema type needs to be flexible
563
+ return collectionConfig as any;
532
564
  }
@@ -8,6 +8,7 @@ import type { ValidTableNames } from "../collections/sqlite-collection";
8
8
 
9
9
  export type UseDrizzleSqliteReturn<TSchema extends Record<string, unknown>> = {
10
10
  drizzle: DrizzleSqliteContextValue<TSchema>["drizzle"];
11
+ readyPromise: DrizzleSqliteContextValue<TSchema>["readyPromise"];
11
12
  useCollection: <TTableName extends string & ValidTableNames<TSchema>>(
12
13
  tableName: TTableName,
13
14
  ) => ReturnType<typeof useSqliteCollection<TSchema, TTableName>>;
@@ -28,6 +29,7 @@ export function useDrizzleSqlite<
28
29
 
29
30
  return {
30
31
  drizzle: context.drizzle,
32
+ readyPromise: context.readyPromise,
31
33
  useCollection: <TTableName extends string & ValidTableNames<TSchema>>(
32
34
  tableName: TTableName,
33
35
  ) => useSqliteCollection(context, tableName),
package/src/index.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  export { drizzleSqliteWasm } from "./drizzle/direct";
2
- export { sqliteCollectionOptions as drizzleCollectionOptions } from "./collections/sqlite-collection";
2
+ export {
3
+ sqliteCollectionOptions as drizzleCollectionOptions,
4
+ type SQLOperation,
5
+ type SQLInterceptor,
6
+ } from "./collections/sqlite-collection";
3
7
  export { syncableTable } from "@firtoz/drizzle-utils";
4
8
  export { makeId } from "@firtoz/drizzle-utils";
5
9
  export type {