@firtoz/drizzle-sqlite-wasm 0.2.15 → 0.2.17
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 +15 -0
- package/README.md +9 -1
- package/package.json +2 -2
- package/src/collections/sqlite-collection.ts +36 -513
- package/src/index.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @firtoz/drizzle-sqlite-wasm
|
|
2
2
|
|
|
3
|
+
## 0.2.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`8b839f2`](https://github.com/firtoz/fullstack-toolkit/commit/8b839f2227f50409af649aab87178e039aad55dc) Thanks [@firtoz](https://github.com/firtoz)! - Export collection helper types for Drizzle-backed TanStack DB collections so users can declare collection variables with preserved select and insert inference from table schemas.
|
|
8
|
+
|
|
9
|
+
## 0.2.16
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`b714ebb`](https://github.com/firtoz/fullstack-toolkit/commit/b714ebbb62ec0e3c3aa56c4105e7499fac11d1e5) Thanks [@firtoz](https://github.com/firtoz)! - Extract shared SQLite TanStack sync backend (`createSqliteTableSyncBackend`, IR→Drizzle helpers, `SQLOperation` types) into `@firtoz/drizzle-utils`. Add `@firtoz/drizzle-durable-sqlite` for Durable Object SQLite collections (`durableSqliteCollectionOptions`). Refactor `@firtoz/drizzle-sqlite-wasm` to use the shared backend with `driverMode: "async"`. `durableSqliteCollectionOptions` accepts optional `readyPromise` (defaults to immediate readiness). README documents the class-field Hono pattern, `app.fetch(request, env)` for bindings, optional `on-demand` + `preload` vs eager + `onFirstReady`, and `honoDoFetcherWithName` without a separate exported app type. Restore JSDoc on `DrizzleSqliteCollectionConfig` (`debug`, `checkpoint`, `interceptor`) for editor tooltips. Align `createStandaloneCollection` generics with `InsertToSelectSchema` from `@firtoz/drizzle-utils`.
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [[`b714ebb`](https://github.com/firtoz/fullstack-toolkit/commit/b714ebbb62ec0e3c3aa56c4105e7499fac11d1e5)]:
|
|
16
|
+
- @firtoz/drizzle-utils@1.1.0
|
|
17
|
+
|
|
3
18
|
## 0.2.15
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -227,7 +227,11 @@ Create TanStack DB collections backed by SQLite:
|
|
|
227
227
|
|
|
228
228
|
```typescript
|
|
229
229
|
import { createCollection } from "@tanstack/db";
|
|
230
|
-
import {
|
|
230
|
+
import {
|
|
231
|
+
drizzleCollectionOptions,
|
|
232
|
+
type DrizzleSqliteCollection,
|
|
233
|
+
} from "@firtoz/drizzle-sqlite-wasm";
|
|
234
|
+
import * as schema from "./schema";
|
|
231
235
|
|
|
232
236
|
const collection = createCollection(
|
|
233
237
|
drizzleCollectionOptions({
|
|
@@ -249,12 +253,16 @@ const completed = await collection.find({
|
|
|
249
253
|
orderBy: { createdAt: "desc" },
|
|
250
254
|
});
|
|
251
255
|
|
|
256
|
+
type TodosCollection = DrizzleSqliteCollection<typeof schema.todoTable>;
|
|
257
|
+
|
|
252
258
|
// Subscribe to changes
|
|
253
259
|
collection.subscribe((todos) => {
|
|
254
260
|
console.log("Todos updated:", todos);
|
|
255
261
|
});
|
|
256
262
|
```
|
|
257
263
|
|
|
264
|
+
Use `DrizzleSqliteCollection<TTable>` when you want a reusable collection type alias that keeps inferred select/insert types from your table.
|
|
265
|
+
|
|
258
266
|
### Collection Options
|
|
259
267
|
|
|
260
268
|
**Config:**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firtoz/drizzle-sqlite-wasm",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"description": "Drizzle SQLite WASM bindings",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"@firtoz/db-helpers": "^2.0.0",
|
|
73
|
-
"@firtoz/drizzle-utils": "^1.0
|
|
73
|
+
"@firtoz/drizzle-utils": "^1.1.0",
|
|
74
74
|
"@firtoz/maybe-error": "^1.5.2",
|
|
75
75
|
"@firtoz/worker-helper": "^1.5.1",
|
|
76
76
|
"@sqlite.org/sqlite-wasm": "^3.51.2-build8",
|
|
@@ -1,50 +1,30 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
Collection,
|
|
3
|
+
InferSchemaInput,
|
|
2
4
|
InferSchemaOutput,
|
|
3
5
|
SyncMode,
|
|
4
6
|
CollectionConfig,
|
|
5
7
|
} from "@tanstack/db";
|
|
6
|
-
import type {
|
|
7
|
-
import {
|
|
8
|
-
eq,
|
|
9
|
-
sql,
|
|
10
|
-
type Table,
|
|
11
|
-
gt,
|
|
12
|
-
gte,
|
|
13
|
-
lt,
|
|
14
|
-
lte,
|
|
15
|
-
ne,
|
|
16
|
-
and,
|
|
17
|
-
or,
|
|
18
|
-
not,
|
|
19
|
-
isNull,
|
|
20
|
-
isNotNull,
|
|
21
|
-
like,
|
|
22
|
-
inArray,
|
|
23
|
-
asc,
|
|
24
|
-
desc,
|
|
25
|
-
type SQL,
|
|
26
|
-
} from "drizzle-orm";
|
|
27
|
-
import {
|
|
28
|
-
type SQLiteUpdateSetSource,
|
|
29
|
-
type BaseSQLiteDatabase,
|
|
30
|
-
type SQLiteInsertValue,
|
|
31
|
-
SQLiteColumn,
|
|
32
|
-
} from "drizzle-orm/sqlite-core";
|
|
8
|
+
import type { Table } from "drizzle-orm";
|
|
9
|
+
import type { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core";
|
|
33
10
|
import type { CollectionUtils } from "@firtoz/db-helpers";
|
|
34
11
|
import type {
|
|
35
12
|
SelectSchema,
|
|
13
|
+
InsertToSelectSchema,
|
|
36
14
|
TableWithRequiredFields,
|
|
37
15
|
BaseSyncConfig,
|
|
38
|
-
|
|
16
|
+
IdOf,
|
|
39
17
|
} from "@firtoz/drizzle-utils";
|
|
40
18
|
import {
|
|
41
19
|
createSyncFunction,
|
|
42
20
|
createInsertSchemaWithIdDefault,
|
|
43
21
|
createGetKeyFunction,
|
|
44
22
|
createCollectionConfig,
|
|
23
|
+
createSqliteTableSyncBackend,
|
|
24
|
+
type SQLOperation,
|
|
25
|
+
type SQLInterceptor,
|
|
45
26
|
} from "@firtoz/drizzle-utils";
|
|
46
|
-
|
|
47
|
-
import type * as v from "valibot";
|
|
27
|
+
export type { SQLOperation, SQLInterceptor };
|
|
48
28
|
|
|
49
29
|
export type AnyDrizzleDatabase = BaseSQLiteDatabase<
|
|
50
30
|
"async",
|
|
@@ -56,81 +36,7 @@ export type AnyDrizzleDatabase = BaseSQLiteDatabase<
|
|
|
56
36
|
export type DrizzleSchema<TDrizzle extends AnyDrizzleDatabase> =
|
|
57
37
|
TDrizzle["_"]["fullSchema"];
|
|
58
38
|
|
|
59
|
-
|
|
60
|
-
* Operation tracking for SQLite queries
|
|
61
|
-
* Useful for testing and debugging to verify what operations are actually performed
|
|
62
|
-
*
|
|
63
|
-
* Uses discriminated unions for type safety - TypeScript can narrow the type based on the 'type' field
|
|
64
|
-
*/
|
|
65
|
-
export type SQLOperation =
|
|
66
|
-
| {
|
|
67
|
-
type: "select-all";
|
|
68
|
-
tableName: string;
|
|
69
|
-
itemsReturned: unknown[];
|
|
70
|
-
itemCount: number;
|
|
71
|
-
context: string;
|
|
72
|
-
sql?: string;
|
|
73
|
-
timestamp: number;
|
|
74
|
-
}
|
|
75
|
-
| {
|
|
76
|
-
type: "select-where";
|
|
77
|
-
tableName: string;
|
|
78
|
-
whereClause: string;
|
|
79
|
-
itemsReturned: unknown[];
|
|
80
|
-
itemCount: number;
|
|
81
|
-
context: string;
|
|
82
|
-
sql?: string;
|
|
83
|
-
timestamp: number;
|
|
84
|
-
}
|
|
85
|
-
| {
|
|
86
|
-
type: "write";
|
|
87
|
-
tableName: string;
|
|
88
|
-
itemsWritten: unknown[];
|
|
89
|
-
writeCount: number;
|
|
90
|
-
context: string;
|
|
91
|
-
timestamp: number;
|
|
92
|
-
}
|
|
93
|
-
| {
|
|
94
|
-
type: "insert";
|
|
95
|
-
tableName: string;
|
|
96
|
-
item: unknown;
|
|
97
|
-
sql?: string;
|
|
98
|
-
timestamp: number;
|
|
99
|
-
}
|
|
100
|
-
| {
|
|
101
|
-
type: "update";
|
|
102
|
-
tableName: string;
|
|
103
|
-
updates: unknown;
|
|
104
|
-
sql?: string;
|
|
105
|
-
timestamp: number;
|
|
106
|
-
}
|
|
107
|
-
| {
|
|
108
|
-
type: "delete";
|
|
109
|
-
tableName: string;
|
|
110
|
-
sql?: string;
|
|
111
|
-
timestamp: number;
|
|
112
|
-
}
|
|
113
|
-
| {
|
|
114
|
-
/** Raw SQL query executed directly via Drizzle (not through collection) */
|
|
115
|
-
type: "raw-query";
|
|
116
|
-
sql: string;
|
|
117
|
-
params?: unknown[];
|
|
118
|
-
method: string;
|
|
119
|
-
rowCount: number;
|
|
120
|
-
context: string;
|
|
121
|
-
timestamp: number;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Interceptor interface for tracking SQLite operations
|
|
126
|
-
* Allows tests and debugging tools to observe what operations are performed
|
|
127
|
-
*/
|
|
128
|
-
export interface SQLInterceptor {
|
|
129
|
-
/** Called when any SQLite operation is performed */
|
|
130
|
-
onOperation?: (operation: SQLOperation) => void;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export interface DrizzleCollectionConfig<
|
|
39
|
+
export interface DrizzleSqliteCollectionConfig<
|
|
134
40
|
TDrizzle extends AnyDrizzleDatabase,
|
|
135
41
|
TTableName extends ValidTableNames<DrizzleSchema<TDrizzle>>,
|
|
136
42
|
> {
|
|
@@ -161,427 +67,50 @@ export type ValidTableNames<TSchema extends Record<string, unknown>> = {
|
|
|
161
67
|
[K in keyof TSchema]: TSchema[K] extends TableWithRequiredFields ? K : never;
|
|
162
68
|
}[keyof TSchema];
|
|
163
69
|
|
|
164
|
-
/**
|
|
165
|
-
* Return type for sqliteCollectionOptions - configuration object for creating SQLite collections
|
|
166
|
-
*
|
|
167
|
-
* Note: The third type parameter of CollectionConfig uses `any` to maintain compatibility with
|
|
168
|
-
* TanStack DB's type system, which expects different schema types in different contexts.
|
|
169
|
-
*/
|
|
170
70
|
export type SqliteCollectionConfig<TTable extends Table> = Omit<
|
|
171
71
|
CollectionConfig<
|
|
172
72
|
InferSchemaOutput<SelectSchema<TTable>>,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
73
|
+
IdOf<TTable>,
|
|
74
|
+
InsertToSelectSchema<TTable>,
|
|
75
|
+
CollectionUtils<InferSchemaOutput<SelectSchema<TTable>>>
|
|
176
76
|
>,
|
|
177
77
|
"utils"
|
|
178
78
|
> & {
|
|
179
|
-
schema:
|
|
79
|
+
schema: InsertToSelectSchema<TTable>;
|
|
180
80
|
utils: CollectionUtils<InferSchemaOutput<SelectSchema<TTable>>>;
|
|
181
81
|
};
|
|
182
82
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
*/
|
|
192
|
-
function convertBasicExpressionToDrizzle<TTable extends Table>(
|
|
193
|
-
expression: IR.BasicExpression,
|
|
194
|
-
table: TTable,
|
|
195
|
-
): SQL {
|
|
196
|
-
switch (expression.type) {
|
|
197
|
-
case "ref": {
|
|
198
|
-
const propRef = expression;
|
|
199
|
-
const columnName = propRef.path[propRef.path.length - 1];
|
|
200
|
-
const column = table[columnName as keyof typeof table];
|
|
201
|
-
|
|
202
|
-
if (!column || !(column instanceof SQLiteColumn)) {
|
|
203
|
-
console.error("[SQLite Collection] Column lookup failed:", {
|
|
204
|
-
columnName,
|
|
205
|
-
column,
|
|
206
|
-
tableKeys: Object.keys(table),
|
|
207
|
-
hasColumn: columnName in table,
|
|
208
|
-
});
|
|
209
|
-
throw new Error(`Column ${String(columnName)} not found in table`);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return column as unknown as SQL;
|
|
213
|
-
}
|
|
214
|
-
case "val": {
|
|
215
|
-
const value = expression;
|
|
216
|
-
return sql`${value.value}`;
|
|
217
|
-
}
|
|
218
|
-
case "func": {
|
|
219
|
-
const func = expression;
|
|
220
|
-
const args = func.args.map((arg) =>
|
|
221
|
-
convertBasicExpressionToDrizzle(arg, table),
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
switch (func.name) {
|
|
225
|
-
case "eq":
|
|
226
|
-
return eq(args[0], args[1]);
|
|
227
|
-
case "ne":
|
|
228
|
-
return ne(args[0], args[1]);
|
|
229
|
-
case "gt":
|
|
230
|
-
return gt(args[0], args[1]);
|
|
231
|
-
case "gte":
|
|
232
|
-
return gte(args[0], args[1]);
|
|
233
|
-
case "lt":
|
|
234
|
-
return lt(args[0], args[1]);
|
|
235
|
-
case "lte":
|
|
236
|
-
return lte(args[0], args[1]);
|
|
237
|
-
case "and": {
|
|
238
|
-
const result = and(...args);
|
|
239
|
-
if (!result) {
|
|
240
|
-
throw new Error("Invalid 'and' expression - no arguments provided");
|
|
241
|
-
}
|
|
242
|
-
return result;
|
|
243
|
-
}
|
|
244
|
-
case "or": {
|
|
245
|
-
const result = or(...args);
|
|
246
|
-
if (!result) {
|
|
247
|
-
throw new Error("Invalid 'or' expression - no arguments provided");
|
|
248
|
-
}
|
|
249
|
-
return result;
|
|
250
|
-
}
|
|
251
|
-
case "not":
|
|
252
|
-
return not(args[0]);
|
|
253
|
-
case "isNull":
|
|
254
|
-
return isNull(args[0]);
|
|
255
|
-
case "isNotNull":
|
|
256
|
-
return isNotNull(args[0]);
|
|
257
|
-
case "like":
|
|
258
|
-
return like(args[0], args[1]);
|
|
259
|
-
case "in":
|
|
260
|
-
return inArray(args[0], args[1]);
|
|
261
|
-
case "isUndefined":
|
|
262
|
-
// isUndefined is same as isNull in SQLite
|
|
263
|
-
return isNull(args[0]);
|
|
264
|
-
default:
|
|
265
|
-
throw new Error(`Unsupported function: ${func.name}`);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
default:
|
|
269
|
-
exhaustiveGuard(expression);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Converts TanStack DB OrderBy to Drizzle orderBy
|
|
275
|
-
*/
|
|
276
|
-
function convertOrderByToDrizzle<TTable extends Table>(
|
|
277
|
-
orderBy: IR.OrderBy,
|
|
278
|
-
table: TTable,
|
|
279
|
-
): SQL[] {
|
|
280
|
-
return orderBy.map((clause) => {
|
|
281
|
-
const expression = convertBasicExpressionToDrizzle(
|
|
282
|
-
clause.expression,
|
|
283
|
-
table,
|
|
284
|
-
);
|
|
285
|
-
const direction = clause.compareOptions.direction || "asc";
|
|
286
|
-
|
|
287
|
-
return direction === "asc" ? asc(expression) : desc(expression);
|
|
288
|
-
});
|
|
289
|
-
}
|
|
83
|
+
export type DrizzleSqliteCollection<TTable extends TableWithRequiredFields> =
|
|
84
|
+
Collection<
|
|
85
|
+
InferSchemaOutput<SelectSchema<TTable>>,
|
|
86
|
+
IdOf<TTable>,
|
|
87
|
+
CollectionUtils<InferSchemaOutput<SelectSchema<TTable>>>,
|
|
88
|
+
InsertToSelectSchema<TTable>,
|
|
89
|
+
InferSchemaInput<InsertToSelectSchema<TTable>>
|
|
90
|
+
>;
|
|
290
91
|
|
|
291
92
|
export function sqliteCollectionOptions<
|
|
292
93
|
const TDrizzle extends AnyDrizzleDatabase,
|
|
293
94
|
const TTableName extends string & ValidTableNames<DrizzleSchema<TDrizzle>>,
|
|
294
95
|
TTable extends DrizzleSchema<TDrizzle>[TTableName] & TableWithRequiredFields,
|
|
295
96
|
>(
|
|
296
|
-
config:
|
|
97
|
+
config: DrizzleSqliteCollectionConfig<TDrizzle, TTableName>,
|
|
297
98
|
): SqliteCollectionConfig<TTable> {
|
|
298
99
|
const tableName = config.tableName as string &
|
|
299
100
|
ValidTableNames<DrizzleSchema<TDrizzle>>;
|
|
300
101
|
|
|
301
102
|
const table = config.drizzle?._.fullSchema[tableName] as TTable;
|
|
302
103
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
() => {}, // Success handler - return undefined to reset queue
|
|
313
|
-
() => {}, // Error handler - return undefined to reset queue (queue continues)
|
|
314
|
-
);
|
|
315
|
-
// Return the actual result so errors propagate to the caller
|
|
316
|
-
return result;
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
// Create backend-specific implementation
|
|
320
|
-
const backend: SyncBackend<TTable> = {
|
|
321
|
-
initialLoad: async () => {
|
|
322
|
-
const items = (await config.drizzle
|
|
323
|
-
.select()
|
|
324
|
-
.from(table)) as unknown as InferSchemaOutput<SelectSchema<TTable>>[];
|
|
325
|
-
|
|
326
|
-
// Log SQL operation
|
|
327
|
-
if (config.interceptor?.onOperation) {
|
|
328
|
-
config.interceptor.onOperation({
|
|
329
|
-
type: "select-all",
|
|
330
|
-
tableName: config.tableName as string,
|
|
331
|
-
itemsReturned: items,
|
|
332
|
-
itemCount: items.length,
|
|
333
|
-
context: "Initial load (eager mode)",
|
|
334
|
-
timestamp: Date.now(),
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Log write operation
|
|
339
|
-
if (config.interceptor?.onOperation) {
|
|
340
|
-
config.interceptor.onOperation({
|
|
341
|
-
type: "write",
|
|
342
|
-
tableName: config.tableName as string,
|
|
343
|
-
itemsWritten: items,
|
|
344
|
-
writeCount: items.length,
|
|
345
|
-
context: "Initial load (eager mode)",
|
|
346
|
-
timestamp: Date.now(),
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return items as unknown as InferSchemaOutput<SelectSchema<TTable>>[];
|
|
351
|
-
},
|
|
352
|
-
|
|
353
|
-
loadSubset: async (options) => {
|
|
354
|
-
// Build the query with optional where, orderBy, limit, and offset
|
|
355
|
-
// Use $dynamic() to enable dynamic query building
|
|
356
|
-
let query = config.drizzle.select().from(table).$dynamic();
|
|
357
|
-
|
|
358
|
-
// Combine where with cursor expressions if present
|
|
359
|
-
// The cursor.whereFrom gives us rows after the cursor position
|
|
360
|
-
let hasWhere = false;
|
|
361
|
-
if (options.where || options.cursor?.whereFrom) {
|
|
362
|
-
let drizzleWhere: SQL | undefined;
|
|
363
|
-
|
|
364
|
-
if (options.where && options.cursor?.whereFrom) {
|
|
365
|
-
// Combine main where with cursor expression using AND
|
|
366
|
-
const mainWhere = convertBasicExpressionToDrizzle(
|
|
367
|
-
options.where,
|
|
368
|
-
table,
|
|
369
|
-
);
|
|
370
|
-
const cursorWhere = convertBasicExpressionToDrizzle(
|
|
371
|
-
options.cursor.whereFrom,
|
|
372
|
-
table,
|
|
373
|
-
);
|
|
374
|
-
drizzleWhere = and(mainWhere, cursorWhere);
|
|
375
|
-
} else if (options.where) {
|
|
376
|
-
drizzleWhere = convertBasicExpressionToDrizzle(options.where, table);
|
|
377
|
-
} else if (options.cursor?.whereFrom) {
|
|
378
|
-
drizzleWhere = convertBasicExpressionToDrizzle(
|
|
379
|
-
options.cursor.whereFrom,
|
|
380
|
-
table,
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (drizzleWhere) {
|
|
385
|
-
query = query.where(drizzleWhere);
|
|
386
|
-
hasWhere = true;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
if (options.orderBy) {
|
|
391
|
-
const drizzleOrderBy = convertOrderByToDrizzle(options.orderBy, table);
|
|
392
|
-
query = query.orderBy(...drizzleOrderBy);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (options.limit !== undefined) {
|
|
396
|
-
query = query.limit(options.limit);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Apply offset for offset-based pagination
|
|
400
|
-
if (options.offset !== undefined && options.offset > 0) {
|
|
401
|
-
query = query.offset(options.offset);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const items = (await query) as unknown as InferSchemaOutput<
|
|
405
|
-
SelectSchema<TTable>
|
|
406
|
-
>[];
|
|
407
|
-
|
|
408
|
-
// Log SQL operation
|
|
409
|
-
if (config.interceptor?.onOperation) {
|
|
410
|
-
const contextParts: string[] = ["On-demand load"];
|
|
411
|
-
if (options.orderBy) {
|
|
412
|
-
contextParts.push("with sorting");
|
|
413
|
-
}
|
|
414
|
-
if (options.limit !== undefined) {
|
|
415
|
-
contextParts.push(`limit ${options.limit}`);
|
|
416
|
-
}
|
|
417
|
-
if (options.offset !== undefined && options.offset > 0) {
|
|
418
|
-
contextParts.push(`offset ${options.offset}`);
|
|
419
|
-
}
|
|
420
|
-
if (options.cursor) {
|
|
421
|
-
contextParts.push("with cursor pagination");
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (hasWhere) {
|
|
425
|
-
config.interceptor.onOperation({
|
|
426
|
-
type: "select-where",
|
|
427
|
-
tableName: config.tableName as string,
|
|
428
|
-
whereClause: "WHERE clause applied",
|
|
429
|
-
itemsReturned: items,
|
|
430
|
-
itemCount: items.length,
|
|
431
|
-
context: contextParts.join(", "),
|
|
432
|
-
timestamp: Date.now(),
|
|
433
|
-
});
|
|
434
|
-
} else {
|
|
435
|
-
config.interceptor.onOperation({
|
|
436
|
-
type: "select-all",
|
|
437
|
-
tableName: config.tableName as string,
|
|
438
|
-
itemsReturned: items,
|
|
439
|
-
itemCount: items.length,
|
|
440
|
-
context: contextParts.join(", "),
|
|
441
|
-
timestamp: Date.now(),
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// Log write operation
|
|
447
|
-
if (config.interceptor?.onOperation) {
|
|
448
|
-
const contextParts: string[] = ["On-demand load"];
|
|
449
|
-
if (hasWhere) {
|
|
450
|
-
contextParts.push("with WHERE clause");
|
|
451
|
-
}
|
|
452
|
-
if (options.orderBy) {
|
|
453
|
-
contextParts.push("with sorting");
|
|
454
|
-
}
|
|
455
|
-
if (options.limit !== undefined) {
|
|
456
|
-
contextParts.push(`limit ${options.limit}`);
|
|
457
|
-
}
|
|
458
|
-
if (options.offset !== undefined && options.offset > 0) {
|
|
459
|
-
contextParts.push(`offset ${options.offset}`);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
config.interceptor.onOperation({
|
|
463
|
-
type: "write",
|
|
464
|
-
tableName: config.tableName as string,
|
|
465
|
-
itemsWritten: items,
|
|
466
|
-
writeCount: items.length,
|
|
467
|
-
context: contextParts.join(", "),
|
|
468
|
-
timestamp: Date.now(),
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
return items as unknown as InferSchemaOutput<SelectSchema<TTable>>[];
|
|
473
|
-
},
|
|
474
|
-
|
|
475
|
-
handleInsert: async (items) => {
|
|
476
|
-
const results: Array<InferSchemaOutput<SelectSchema<TTable>>> = [];
|
|
477
|
-
|
|
478
|
-
// Queue the transaction to serialize SQLite operations
|
|
479
|
-
await queueTransaction(async () => {
|
|
480
|
-
await config.drizzle.transaction(async (tx) => {
|
|
481
|
-
for (const itemToInsert of items) {
|
|
482
|
-
if (config.debug) {
|
|
483
|
-
console.log(
|
|
484
|
-
`[${new Date().toISOString()}] insertListener inserting`,
|
|
485
|
-
itemToInsert,
|
|
486
|
-
);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
const result: Array<InferSchemaOutput<SelectSchema<TTable>>> =
|
|
490
|
-
(await tx
|
|
491
|
-
.insert(table)
|
|
492
|
-
.values(
|
|
493
|
-
itemToInsert as unknown as SQLiteInsertValue<typeof table>,
|
|
494
|
-
)
|
|
495
|
-
.returning()) as Array<InferSchemaOutput<SelectSchema<TTable>>>;
|
|
496
|
-
|
|
497
|
-
if (config.debug) {
|
|
498
|
-
console.log(
|
|
499
|
-
`[${new Date().toISOString()}] insertListener result`,
|
|
500
|
-
result,
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
if (result.length > 0) {
|
|
505
|
-
results.push(result[0]);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
// Checkpoint to ensure WAL is flushed to main DB file
|
|
511
|
-
if (config.checkpoint) {
|
|
512
|
-
await config.checkpoint();
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
return results;
|
|
517
|
-
},
|
|
518
|
-
|
|
519
|
-
handleUpdate: async (mutations) => {
|
|
520
|
-
const results: Array<InferSchemaOutput<SelectSchema<TTable>>> = [];
|
|
521
|
-
|
|
522
|
-
// Queue the transaction to serialize SQLite operations
|
|
523
|
-
await queueTransaction(async () => {
|
|
524
|
-
await config.drizzle.transaction(async (tx) => {
|
|
525
|
-
for (const mutation of mutations) {
|
|
526
|
-
if (config.debug) {
|
|
527
|
-
console.log(
|
|
528
|
-
`[${new Date().toISOString()}] updateListener updating`,
|
|
529
|
-
mutation,
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
const updateTime = new Date();
|
|
534
|
-
const result: Array<InferSchemaOutput<SelectSchema<TTable>>> =
|
|
535
|
-
(await tx
|
|
536
|
-
.update(table)
|
|
537
|
-
.set({
|
|
538
|
-
...mutation.changes,
|
|
539
|
-
updatedAt: updateTime,
|
|
540
|
-
} as SQLiteUpdateSetSource<typeof table>)
|
|
541
|
-
// biome-ignore lint/suspicious/noExplicitAny: Key is string but table.id is branded type
|
|
542
|
-
.where(eq(table.id, mutation.key as any))
|
|
543
|
-
.returning()) as Array<InferSchemaOutput<SelectSchema<TTable>>>;
|
|
544
|
-
|
|
545
|
-
if (config.debug) {
|
|
546
|
-
console.log(
|
|
547
|
-
`[${new Date().toISOString()}] updateListener result`,
|
|
548
|
-
result,
|
|
549
|
-
);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
results.push(...result);
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
// Checkpoint to ensure WAL is flushed to main DB file BEFORE UI updates
|
|
557
|
-
// This ensures persistence before the updateListener completes
|
|
558
|
-
if (config.checkpoint) {
|
|
559
|
-
await config.checkpoint();
|
|
560
|
-
}
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
return results;
|
|
564
|
-
},
|
|
565
|
-
|
|
566
|
-
handleDelete: async (mutations) => {
|
|
567
|
-
// Queue the transaction to serialize SQLite operations
|
|
568
|
-
await queueTransaction(async () => {
|
|
569
|
-
await config.drizzle.transaction(async (tx) => {
|
|
570
|
-
for (const mutation of mutations) {
|
|
571
|
-
// biome-ignore lint/suspicious/noExplicitAny: Key is string but table.id is branded type
|
|
572
|
-
await tx.delete(table).where(eq(table.id, mutation.key as any));
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
// Checkpoint to ensure WAL is flushed to main DB file
|
|
577
|
-
if (config.checkpoint) {
|
|
578
|
-
await config.checkpoint();
|
|
579
|
-
}
|
|
580
|
-
});
|
|
581
|
-
},
|
|
582
|
-
};
|
|
104
|
+
const backend = createSqliteTableSyncBackend({
|
|
105
|
+
drizzle: config.drizzle,
|
|
106
|
+
table,
|
|
107
|
+
tableName: config.tableName as string,
|
|
108
|
+
debug: config.debug,
|
|
109
|
+
checkpoint: config.checkpoint,
|
|
110
|
+
interceptor: config.interceptor,
|
|
111
|
+
driverMode: "async",
|
|
112
|
+
});
|
|
583
113
|
|
|
584
|
-
// Create sync function using shared utilities
|
|
585
114
|
const baseSyncConfig: BaseSyncConfig<TTable> = {
|
|
586
115
|
table,
|
|
587
116
|
readyPromise: config.readyPromise,
|
|
@@ -591,11 +120,8 @@ export function sqliteCollectionOptions<
|
|
|
591
120
|
|
|
592
121
|
const syncResult = createSyncFunction(baseSyncConfig, backend);
|
|
593
122
|
|
|
594
|
-
// Create insert schema with ID default
|
|
595
|
-
// (Other defaults like createdAt/updatedAt are handled by SQLite)
|
|
596
123
|
const schema = createInsertSchemaWithIdDefault(table);
|
|
597
124
|
|
|
598
|
-
// Create collection config using shared utilities
|
|
599
125
|
const collectionConfig = createCollectionConfig({
|
|
600
126
|
schema,
|
|
601
127
|
getKey: createGetKeyFunction<TTable>(),
|
|
@@ -603,24 +129,21 @@ export function sqliteCollectionOptions<
|
|
|
603
129
|
onInsert: config.debug
|
|
604
130
|
? async (params) => {
|
|
605
131
|
console.log("onInsert", params);
|
|
606
|
-
//
|
|
607
|
-
// biome-ignore lint/style/noNonNullAssertion: onInsert is always defined in SyncFunctionResult
|
|
132
|
+
// biome-ignore lint/style/noNonNullAssertion: onInsert is always defined in createSyncFunction
|
|
608
133
|
await syncResult.onInsert!(params);
|
|
609
134
|
}
|
|
610
135
|
: undefined,
|
|
611
136
|
onUpdate: config.debug
|
|
612
137
|
? async (params) => {
|
|
613
138
|
console.log("onUpdate", params);
|
|
614
|
-
//
|
|
615
|
-
// biome-ignore lint/style/noNonNullAssertion: onUpdate is always defined in SyncFunctionResult
|
|
139
|
+
// biome-ignore lint/style/noNonNullAssertion: onUpdate is always defined in createSyncFunction
|
|
616
140
|
await syncResult.onUpdate!(params);
|
|
617
141
|
}
|
|
618
142
|
: undefined,
|
|
619
143
|
onDelete: config.debug
|
|
620
144
|
? async (params) => {
|
|
621
145
|
console.log("onDelete", params);
|
|
622
|
-
//
|
|
623
|
-
// biome-ignore lint/style/noNonNullAssertion: onDelete is always defined in SyncFunctionResult
|
|
146
|
+
// biome-ignore lint/style/noNonNullAssertion: onDelete is always defined in createSyncFunction
|
|
624
147
|
await syncResult.onDelete!(params);
|
|
625
148
|
}
|
|
626
149
|
: undefined,
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { drizzleSqliteWasm } from "./drizzle/direct";
|
|
2
2
|
export {
|
|
3
3
|
sqliteCollectionOptions as drizzleCollectionOptions,
|
|
4
|
+
type DrizzleSqliteCollection,
|
|
5
|
+
type SqliteCollectionConfig,
|
|
4
6
|
type SQLOperation,
|
|
5
7
|
type SQLInterceptor,
|
|
6
8
|
} from "./collections/sqlite-collection";
|