@firtoz/drizzle-sqlite-wasm 0.1.0 → 0.2.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 +17 -0
- package/package.json +2 -2
- package/src/collections/sqlite-collection.ts +265 -233
- package/src/context/useDrizzleSqlite.ts +2 -0
- package/src/index.ts +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @firtoz/drizzle-sqlite-wasm
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`58d2cba`](https://github.com/firtoz/fullstack-toolkit/commit/58d2cbac8ea4e540b5460b7088b6b62e50357558) Thanks [@firtoz](https://github.com/firtoz)! - Add sync mode functionality for IndexedDB and SQLite collections
|
|
8
|
+
|
|
9
|
+
- Introduced support for both eager and on-demand sync modes in Drizzle providers
|
|
10
|
+
- Implemented operation tracking via interceptors to monitor database operations during queries
|
|
11
|
+
- Enhanced DrizzleIndexedDBProvider and DrizzleSqliteProvider to accept interceptors for debugging and testing purposes
|
|
12
|
+
- Added createInsertSchemaWithDefaults and createInsertSchemaWithIdDefault utilities for better schema management
|
|
13
|
+
- Refactored collection utilities to improve data handling and consistency across collections
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [[`58d2cba`](https://github.com/firtoz/fullstack-toolkit/commit/58d2cbac8ea4e540b5460b7088b6b62e50357558)]:
|
|
18
|
+
- @firtoz/drizzle-utils@0.2.0
|
|
19
|
+
|
|
3
20
|
## 0.1.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firtoz/drizzle-sqlite-wasm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Drizzle SQLite WASM bindings",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"access": "public"
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
|
-
"@firtoz/drizzle-utils": "^0.
|
|
74
|
+
"@firtoz/drizzle-utils": "^0.2.0",
|
|
75
75
|
"@firtoz/maybe-error": "^1.5.1",
|
|
76
76
|
"@firtoz/worker-helper": "^1.0.0",
|
|
77
77
|
"@sqlite.org/sqlite-wasm": "^3.51.1-build1",
|
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
|
|
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
|
|
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 ||
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
235
|
-
|
|
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
|
-
|
|
238
|
-
|
|
318
|
+
for (const item of items) {
|
|
319
|
+
write(item);
|
|
320
|
+
}
|
|
321
|
+
},
|
|
239
322
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
407
|
+
for (const item of items) {
|
|
408
|
+
write(item);
|
|
409
|
+
}
|
|
410
|
+
},
|
|
262
411
|
|
|
263
|
-
|
|
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
|
|
418
|
+
for (const mutation of mutations) {
|
|
271
419
|
// TanStack DB applies schema transform (including ID default) before calling this listener
|
|
272
|
-
// So
|
|
273
|
-
const itemToInsert =
|
|
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
|
-
|
|
310
|
-
|
|
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
|
-
|
|
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
|
|
466
|
+
for (const mutation of mutations) {
|
|
326
467
|
if (config.debug) {
|
|
327
468
|
console.log(
|
|
328
469
|
`[${new Date().toISOString()}] updateListener updating`,
|
|
329
|
-
|
|
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
|
-
...
|
|
479
|
+
...mutation.changes,
|
|
339
480
|
updatedAt: updateTime,
|
|
340
481
|
} as SQLiteUpdateSetSource<typeof table>)
|
|
341
|
-
|
|
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
|
-
|
|
363
|
-
|
|
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
|
-
|
|
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
|
|
379
|
-
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
433
|
-
|
|
548
|
+
: undefined,
|
|
549
|
+
onUpdate: config.debug
|
|
550
|
+
? async (params) => {
|
|
551
|
+
console.log("onUpdate", params);
|
|
434
552
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
}
|
|
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 {
|
|
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 {
|