@firtoz/drizzle-sqlite-wasm 0.2.17 → 1.0.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/README.md +21 -7
- package/package.json +7 -6
- package/src/collections/sqlite-collection.ts +5 -12
- package/src/collections/synced-sqlite-collection.ts +47 -0
- package/src/hooks/useDrizzleSqliteDb.ts +8 -2
- package/src/index.ts +11 -1
- package/src/worker/manager.ts +6 -1
- package/src/worker/schema.ts +3 -0
- package/src/worker/sqlite-open-options.ts +41 -0
- package/src/worker/sqlite.worker.ts +30 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @firtoz/drizzle-sqlite-wasm
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- [#64](https://github.com/firtoz/fullstack-toolkit/pull/64) [`556555a`](https://github.com/firtoz/fullstack-toolkit/commit/556555a2e09030a8658be8c07b5881e72be64b2f) Thanks [@firtoz](https://github.com/firtoz)! - **Breaking:** Shared TanStack collection row type is `DrizzleSqliteTableCollection` from `@firtoz/drizzle-utils` — remove `DrizzleSqliteCollection` / `DurableSqliteCollection` type exports from the wasm and durable packages and import from `@firtoz/drizzle-utils` instead. Align bridge/session row types with `PartialSyncRowShape`.
|
|
8
|
+
|
|
9
|
+
**`@firtoz/drizzle-durable-sqlite`:** `SyncableDurableObject` / `QueryableDurableObject`, Drizzle partial sync store (`createDrizzlePartialSyncStore`) with scoped `changesSince`, `getRow`, visibility and `rangeReconcile` hooks, `PartialSyncMutationHandler`, `applyDurableMutationIntents`, queued WS message handling, optional `seedInBackground`, and integration with `PartialSyncServerBridge` / `SyncServerBridge` as documented in the package.
|
|
10
|
+
|
|
11
|
+
**`@firtoz/drizzle-sqlite-wasm`:** `createSyncedSqliteCollection`, optional `workerOpenOptions` for worker `Start` / provider hooks, table sync upsert on `id` for replayed inserts, and receive-sync persist key alignment with generic sync.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [[`afb1873`](https://github.com/firtoz/fullstack-toolkit/commit/afb187331bebb1f0231f6615c5b74989191cf30d), [`556555a`](https://github.com/firtoz/fullstack-toolkit/commit/556555a2e09030a8658be8c07b5881e72be64b2f), [`556555a`](https://github.com/firtoz/fullstack-toolkit/commit/556555a2e09030a8658be8c07b5881e72be64b2f), [`556555a`](https://github.com/firtoz/fullstack-toolkit/commit/556555a2e09030a8658be8c07b5881e72be64b2f)]:
|
|
16
|
+
- @firtoz/collection-sync@1.0.0
|
|
17
|
+
- @firtoz/db-helpers@2.1.0
|
|
18
|
+
- @firtoz/drizzle-utils@1.2.0
|
|
19
|
+
|
|
3
20
|
## 0.2.17
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -227,10 +227,8 @@ Create TanStack DB collections backed by SQLite:
|
|
|
227
227
|
|
|
228
228
|
```typescript
|
|
229
229
|
import { createCollection } from "@tanstack/db";
|
|
230
|
-
import {
|
|
231
|
-
|
|
232
|
-
type DrizzleSqliteCollection,
|
|
233
|
-
} from "@firtoz/drizzle-sqlite-wasm";
|
|
230
|
+
import type { DrizzleSqliteTableCollection } from "@firtoz/drizzle-utils";
|
|
231
|
+
import { drizzleCollectionOptions } from "@firtoz/drizzle-sqlite-wasm";
|
|
234
232
|
import * as schema from "./schema";
|
|
235
233
|
|
|
236
234
|
const collection = createCollection(
|
|
@@ -253,7 +251,7 @@ const completed = await collection.find({
|
|
|
253
251
|
orderBy: { createdAt: "desc" },
|
|
254
252
|
});
|
|
255
253
|
|
|
256
|
-
type TodosCollection =
|
|
254
|
+
type TodosCollection = DrizzleSqliteTableCollection<typeof schema.todoTable>;
|
|
257
255
|
|
|
258
256
|
// Subscribe to changes
|
|
259
257
|
collection.subscribe((todos) => {
|
|
@@ -261,7 +259,7 @@ collection.subscribe((todos) => {
|
|
|
261
259
|
});
|
|
262
260
|
```
|
|
263
261
|
|
|
264
|
-
Use `
|
|
262
|
+
Use `DrizzleSqliteTableCollection<TTable>` from `@firtoz/drizzle-utils` when you want a reusable collection type alias (shared with `@firtoz/drizzle-durable-sqlite`).
|
|
265
263
|
|
|
266
264
|
### Collection Options
|
|
267
265
|
|
|
@@ -295,11 +293,27 @@ Context provider for SQLite WASM:
|
|
|
295
293
|
- `dbName: string` - Name of the SQLite database
|
|
296
294
|
- `schema: TSchema` - Drizzle schema object
|
|
297
295
|
- `migrations: DurableSqliteMigrationConfig` - Migration configuration
|
|
296
|
+
- `workerOpenOptions?: SqliteWasmWorkerOpenOptions` - Optional `PRAGMA synchronous` / `journal_mode` on first DB open (see hook section below)
|
|
298
297
|
|
|
299
|
-
#### `useDrizzleSqliteDb(worker, dbName, schema, migrations)`
|
|
298
|
+
#### `useDrizzleSqliteDb(worker, dbName, schema, migrations, debug?, interceptor?, workerOpenOptions?)`
|
|
300
299
|
|
|
301
300
|
Hook to create a Drizzle instance with Web Worker:
|
|
302
301
|
|
|
302
|
+
Optional **`workerOpenOptions`** sets SQLite pragmas when the worker **first** opens that `dbName` (same global worker + same `dbName` ⇒ options from the first open win until the worker is reset):
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
import type { SqliteWasmWorkerOpenOptions } from "@firtoz/drizzle-sqlite-wasm";
|
|
306
|
+
|
|
307
|
+
const open: SqliteWasmWorkerOpenOptions = {
|
|
308
|
+
synchronous: "NORMAL", // default worker behavior if omitted: "FULL"
|
|
309
|
+
journalMode: "WAL", // default if omitted: "WAL"
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
useDrizzleSqliteDb(SqliteWorker, "my-app", schema, migrations, undefined, undefined, open);
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`DrizzleSqliteProvider` accepts the same shape as **`workerOpenOptions`**.
|
|
316
|
+
|
|
303
317
|
```typescript
|
|
304
318
|
function MyComponent() {
|
|
305
319
|
const { drizzle, readyPromise } = useDrizzleSqliteDb(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firtoz/drizzle-sqlite-wasm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Drizzle SQLite WASM bindings",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"CHANGELOG.md"
|
|
41
41
|
],
|
|
42
42
|
"scripts": {
|
|
43
|
-
"typecheck": "
|
|
43
|
+
"typecheck": "tsgo --noEmit -p ./tsconfig.json",
|
|
44
44
|
"lint": "biome check --write src",
|
|
45
45
|
"lint:ci": "biome ci src",
|
|
46
46
|
"format": "biome format src --write"
|
|
@@ -69,13 +69,14 @@
|
|
|
69
69
|
"access": "public"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@firtoz/
|
|
73
|
-
"@firtoz/
|
|
72
|
+
"@firtoz/collection-sync": "^1.0.0",
|
|
73
|
+
"@firtoz/db-helpers": "^2.1.0",
|
|
74
|
+
"@firtoz/drizzle-utils": "^1.2.0",
|
|
74
75
|
"@firtoz/maybe-error": "^1.5.2",
|
|
75
76
|
"@firtoz/worker-helper": "^1.5.1",
|
|
76
77
|
"@sqlite.org/sqlite-wasm": "^3.51.2-build8",
|
|
77
|
-
"@tanstack/db": "^0.
|
|
78
|
-
"drizzle-orm": "^0.45.
|
|
78
|
+
"@tanstack/db": "^0.6.1",
|
|
79
|
+
"drizzle-orm": "^0.45.2",
|
|
79
80
|
"drizzle-valibot": "^0.4.2",
|
|
80
81
|
"react": "^19.2.4",
|
|
81
82
|
"valibot": "^1.3.1",
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
Collection,
|
|
3
|
-
InferSchemaInput,
|
|
4
2
|
InferSchemaOutput,
|
|
5
3
|
SyncMode,
|
|
6
4
|
CollectionConfig,
|
|
@@ -80,15 +78,6 @@ export type SqliteCollectionConfig<TTable extends Table> = Omit<
|
|
|
80
78
|
utils: CollectionUtils<InferSchemaOutput<SelectSchema<TTable>>>;
|
|
81
79
|
};
|
|
82
80
|
|
|
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
|
-
>;
|
|
91
|
-
|
|
92
81
|
export function sqliteCollectionOptions<
|
|
93
82
|
const TDrizzle extends AnyDrizzleDatabase,
|
|
94
83
|
const TTableName extends string & ValidTableNames<DrizzleSchema<TDrizzle>>,
|
|
@@ -101,6 +90,9 @@ export function sqliteCollectionOptions<
|
|
|
101
90
|
|
|
102
91
|
const table = config.drizzle?._.fullSchema[tableName] as TTable;
|
|
103
92
|
|
|
93
|
+
type TItem = InferSchemaOutput<SelectSchema<TTable>>;
|
|
94
|
+
const getKey = createGetKeyFunction<TTable>();
|
|
95
|
+
|
|
104
96
|
const backend = createSqliteTableSyncBackend({
|
|
105
97
|
drizzle: config.drizzle,
|
|
106
98
|
table,
|
|
@@ -116,6 +108,7 @@ export function sqliteCollectionOptions<
|
|
|
116
108
|
readyPromise: config.readyPromise,
|
|
117
109
|
syncMode: config.syncMode,
|
|
118
110
|
debug: config.debug,
|
|
111
|
+
getSyncPersistKey: (item: TItem) => String(getKey(item)),
|
|
119
112
|
};
|
|
120
113
|
|
|
121
114
|
const syncResult = createSyncFunction(baseSyncConfig, backend);
|
|
@@ -124,7 +117,7 @@ export function sqliteCollectionOptions<
|
|
|
124
117
|
|
|
125
118
|
const collectionConfig = createCollectionConfig({
|
|
126
119
|
schema,
|
|
127
|
-
getKey
|
|
120
|
+
getKey,
|
|
128
121
|
syncResult,
|
|
129
122
|
onInsert: config.debug
|
|
130
123
|
? async (params) => {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSyncedCollection,
|
|
3
|
+
type SyncableCollectionItem,
|
|
4
|
+
type SyncClientBridge,
|
|
5
|
+
type SyncClientMessage,
|
|
6
|
+
type WithSyncOptions,
|
|
7
|
+
} from "@firtoz/collection-sync";
|
|
8
|
+
import type { Collection } from "@tanstack/db";
|
|
9
|
+
import type { TableWithRequiredFields } from "@firtoz/drizzle-utils";
|
|
10
|
+
import type { InferSelectModel } from "drizzle-orm";
|
|
11
|
+
import type {
|
|
12
|
+
AnyDrizzleDatabase,
|
|
13
|
+
DrizzleSchema,
|
|
14
|
+
DrizzleSqliteCollectionConfig,
|
|
15
|
+
ValidTableNames,
|
|
16
|
+
} from "./sqlite-collection";
|
|
17
|
+
import { sqliteCollectionOptions } from "./sqlite-collection";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Like {@link createSyncedCollection} from `@firtoz/collection-sync`, but row type uses Drizzle’s
|
|
21
|
+
* {@link InferSelectModel} so branded columns (e.g. ids) match `$inferSelect`, not Valibot schema output.
|
|
22
|
+
*/
|
|
23
|
+
export function createSyncedSqliteCollection<
|
|
24
|
+
const TDrizzle extends AnyDrizzleDatabase,
|
|
25
|
+
const TTableName extends string & ValidTableNames<DrizzleSchema<TDrizzle>>,
|
|
26
|
+
TTable extends DrizzleSchema<TDrizzle>[TTableName] & TableWithRequiredFields,
|
|
27
|
+
>(
|
|
28
|
+
config: DrizzleSqliteCollectionConfig<TDrizzle, TTableName>,
|
|
29
|
+
syncOptions?: WithSyncOptions,
|
|
30
|
+
): {
|
|
31
|
+
collection: Collection<InferSelectModel<TTable>>;
|
|
32
|
+
bridge: SyncClientBridge<InferSelectModel<TTable> & SyncableCollectionItem>;
|
|
33
|
+
setTransportSend: (send: (msg: SyncClientMessage) => void) => void;
|
|
34
|
+
} {
|
|
35
|
+
type TRow = InferSelectModel<TTable>;
|
|
36
|
+
type TBridgeItem = TRow & SyncableCollectionItem;
|
|
37
|
+
const options = sqliteCollectionOptions(config);
|
|
38
|
+
const { collection, bridge, setTransportSend } = createSyncedCollection(
|
|
39
|
+
options,
|
|
40
|
+
syncOptions,
|
|
41
|
+
);
|
|
42
|
+
return {
|
|
43
|
+
collection: collection as unknown as Collection<TRow>,
|
|
44
|
+
bridge: bridge as unknown as SyncClientBridge<TBridgeItem>,
|
|
45
|
+
setTransportSend,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
isSqliteWorkerInitialized,
|
|
14
14
|
} from "../worker/global-manager";
|
|
15
15
|
import type { SQLInterceptor } from "../collections/sqlite-collection";
|
|
16
|
+
import type { SqliteWasmWorkerOpenOptions } from "../worker/sqlite-open-options";
|
|
16
17
|
|
|
17
18
|
export const useDrizzleSqliteDb = <TSchema extends Record<string, unknown>>(
|
|
18
19
|
WorkerConstructor: new () => Worker,
|
|
@@ -22,6 +23,11 @@ export const useDrizzleSqliteDb = <TSchema extends Record<string, unknown>>(
|
|
|
22
23
|
debug?: boolean,
|
|
23
24
|
/** Optional interceptor to log ALL SQL queries (including direct Drizzle queries) */
|
|
24
25
|
interceptor?: SQLInterceptor,
|
|
26
|
+
/**
|
|
27
|
+
* Pragmas applied when the worker first opens this `dbName` in the session.
|
|
28
|
+
* Ignored if that database was already started (same global worker + dbName).
|
|
29
|
+
*/
|
|
30
|
+
workerOpenOptions?: SqliteWasmWorkerOpenOptions,
|
|
25
31
|
) => {
|
|
26
32
|
const resolveRef = useRef<null | (() => void)>(null);
|
|
27
33
|
const rejectRef = useRef<null | ((error: unknown) => void)>(null);
|
|
@@ -63,7 +69,7 @@ export const useDrizzleSqliteDb = <TSchema extends Record<string, unknown>>(
|
|
|
63
69
|
"../worker/global-manager"
|
|
64
70
|
);
|
|
65
71
|
const manager = getSqliteWorkerManager();
|
|
66
|
-
const instance = await manager.getDbInstance(dbName);
|
|
72
|
+
const instance = await manager.getDbInstance(dbName, workerOpenOptions);
|
|
67
73
|
|
|
68
74
|
if (mounted) {
|
|
69
75
|
sqliteClientRef.current = instance;
|
|
@@ -76,7 +82,7 @@ export const useDrizzleSqliteDb = <TSchema extends Record<string, unknown>>(
|
|
|
76
82
|
return () => {
|
|
77
83
|
mounted = false;
|
|
78
84
|
};
|
|
79
|
-
}, [dbName, WorkerConstructor]);
|
|
85
|
+
}, [dbName, WorkerConstructor, workerOpenOptions]);
|
|
80
86
|
|
|
81
87
|
// Store interceptor in a ref to avoid recreating drizzle on interceptor changes
|
|
82
88
|
const interceptorRef = useRef(interceptor);
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export { drizzleSqliteWasm } from "./drizzle/direct";
|
|
2
2
|
export {
|
|
3
3
|
sqliteCollectionOptions as drizzleCollectionOptions,
|
|
4
|
-
type DrizzleSqliteCollection,
|
|
5
4
|
type SqliteCollectionConfig,
|
|
6
5
|
type SQLOperation,
|
|
7
6
|
type SQLInterceptor,
|
|
8
7
|
} from "./collections/sqlite-collection";
|
|
8
|
+
export { createSyncedSqliteCollection } from "./collections/synced-sqlite-collection";
|
|
9
9
|
export { syncableTable } from "@firtoz/drizzle-utils";
|
|
10
10
|
export { makeId } from "@firtoz/drizzle-utils";
|
|
11
11
|
export type {
|
|
@@ -34,5 +34,15 @@ export {
|
|
|
34
34
|
} from "./worker/global-manager";
|
|
35
35
|
export { SqliteWorkerManager, DbInstance } from "./worker/manager";
|
|
36
36
|
export type { ISqliteWorkerClient } from "./worker/manager";
|
|
37
|
+
export type {
|
|
38
|
+
SqliteWasmJournalMode,
|
|
39
|
+
SqliteWasmSynchronousMode,
|
|
40
|
+
SqliteWasmWorkerOpenOptions,
|
|
41
|
+
} from "./worker/sqlite-open-options";
|
|
42
|
+
export {
|
|
43
|
+
SqliteWasmJournalModeSchema,
|
|
44
|
+
SqliteWasmSynchronousModeSchema,
|
|
45
|
+
SqliteWasmWorkerOpenOptionsSchema,
|
|
46
|
+
} from "./worker/sqlite-open-options";
|
|
37
47
|
export { customSqliteMigrate } from "./migration/migrator";
|
|
38
48
|
export type { DurableSqliteMigrationConfig } from "./migration/migrator";
|
package/src/worker/manager.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { exhaustiveGuard } from "@firtoz/maybe-error";
|
|
2
2
|
import { WorkerClient } from "@firtoz/worker-helper/WorkerClient";
|
|
3
|
+
import type { SqliteWasmWorkerOpenOptions } from "./sqlite-open-options";
|
|
3
4
|
import {
|
|
4
5
|
type SqliteWorkerClientMessage,
|
|
5
6
|
type SqliteWorkerServerMessage,
|
|
@@ -257,7 +258,10 @@ export class SqliteWorkerManager extends WorkerClient<
|
|
|
257
258
|
/**
|
|
258
259
|
* Get or create a database instance
|
|
259
260
|
*/
|
|
260
|
-
public async getDbInstance(
|
|
261
|
+
public async getDbInstance(
|
|
262
|
+
dbName: string,
|
|
263
|
+
openOptions?: SqliteWasmWorkerOpenOptions,
|
|
264
|
+
): Promise<DbInstance> {
|
|
261
265
|
// Check if instance already exists
|
|
262
266
|
let instance = this.dbInstances.get(dbName);
|
|
263
267
|
if (instance) {
|
|
@@ -283,6 +287,7 @@ export class SqliteWorkerManager extends WorkerClient<
|
|
|
283
287
|
type: SqliteWorkerClientMessageType.Start,
|
|
284
288
|
requestId: startRequestId,
|
|
285
289
|
dbName: dbName,
|
|
290
|
+
...(openOptions !== undefined ? { openOptions } : {}),
|
|
286
291
|
});
|
|
287
292
|
|
|
288
293
|
return instance;
|
package/src/worker/schema.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import z from "zod";
|
|
2
|
+
import { SqliteWasmWorkerOpenOptionsSchema } from "./sqlite-open-options";
|
|
2
3
|
|
|
3
4
|
export const RemoteCallbackIdSchema = z.string().brand("remote-callback-id");
|
|
4
5
|
export type RemoteCallbackId = z.infer<typeof RemoteCallbackIdSchema>;
|
|
@@ -55,6 +56,8 @@ export const SqliteWorkerClientMessageSchema = z.discriminatedUnion("type", [
|
|
|
55
56
|
type: z.literal(SqliteWorkerClientMessageType.Start),
|
|
56
57
|
requestId: StartRequestIdSchema,
|
|
57
58
|
dbName: z.string(),
|
|
59
|
+
/** Applied on first open of this `dbName` in the worker process. */
|
|
60
|
+
openOptions: SqliteWasmWorkerOpenOptionsSchema.optional(),
|
|
58
61
|
}),
|
|
59
62
|
RemoteCallbackRequestSchema,
|
|
60
63
|
CheckpointRequestSchema,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SQLite `PRAGMA synchronous` levels (see SQLite docs). Default worker behavior
|
|
5
|
+
* remains `FULL` for maximum durability with OPFS; `NORMAL` is often much faster
|
|
6
|
+
* for interactive UIs at the cost of a narrower crash window.
|
|
7
|
+
*/
|
|
8
|
+
export const SqliteWasmSynchronousModeSchema = z.enum([
|
|
9
|
+
"OFF",
|
|
10
|
+
"NORMAL",
|
|
11
|
+
"FULL",
|
|
12
|
+
"EXTRA",
|
|
13
|
+
]);
|
|
14
|
+
export type SqliteWasmSynchronousMode = z.infer<
|
|
15
|
+
typeof SqliteWasmSynchronousModeSchema
|
|
16
|
+
>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* SQLite `PRAGMA journal_mode` values the worker will pass through as
|
|
20
|
+
* `PRAGMA journal_mode=<value>;` (uppercase).
|
|
21
|
+
*/
|
|
22
|
+
export const SqliteWasmJournalModeSchema = z.enum([
|
|
23
|
+
"WAL",
|
|
24
|
+
"DELETE",
|
|
25
|
+
"TRUNCATE",
|
|
26
|
+
"MEMORY",
|
|
27
|
+
"OFF",
|
|
28
|
+
]);
|
|
29
|
+
export type SqliteWasmJournalMode = z.infer<typeof SqliteWasmJournalModeSchema>;
|
|
30
|
+
|
|
31
|
+
/** Options applied once when the worker opens a database file (OPFS or transient). */
|
|
32
|
+
export const SqliteWasmWorkerOpenOptionsSchema = z
|
|
33
|
+
.object({
|
|
34
|
+
synchronous: SqliteWasmSynchronousModeSchema.optional(),
|
|
35
|
+
journalMode: SqliteWasmJournalModeSchema.optional(),
|
|
36
|
+
})
|
|
37
|
+
.strict();
|
|
38
|
+
|
|
39
|
+
export type SqliteWasmWorkerOpenOptions = z.infer<
|
|
40
|
+
typeof SqliteWasmWorkerOpenOptionsSchema
|
|
41
|
+
>;
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "./schema";
|
|
13
13
|
import { handleRemoteCallback } from "../drizzle/handle-callback";
|
|
14
14
|
import { exhaustiveGuard } from "@firtoz/maybe-error";
|
|
15
|
+
import type { SqliteWasmWorkerOpenOptions } from "./sqlite-open-options";
|
|
15
16
|
import type { Sqlite3Static, Database } from "../types";
|
|
16
17
|
|
|
17
18
|
// Declare self as DedicatedWorkerGlobalScope for TypeScript
|
|
@@ -133,10 +134,31 @@ class SqliteWorkerHelper extends WorkerHelper<
|
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
|
|
137
|
+
private applyOpenPragmas(
|
|
138
|
+
db: Database,
|
|
139
|
+
openOptions?: SqliteWasmWorkerOpenOptions,
|
|
140
|
+
) {
|
|
141
|
+
const journalMode = openOptions?.journalMode ?? "WAL";
|
|
142
|
+
const synchronous = openOptions?.synchronous ?? "FULL";
|
|
143
|
+
try {
|
|
144
|
+
db.exec(`PRAGMA journal_mode=${journalMode};`);
|
|
145
|
+
db.exec(`PRAGMA synchronous=${synchronous};`);
|
|
146
|
+
this.log(
|
|
147
|
+
"PRAGMA journal_mode=",
|
|
148
|
+
journalMode,
|
|
149
|
+
"synchronous=",
|
|
150
|
+
synchronous,
|
|
151
|
+
);
|
|
152
|
+
} catch (e) {
|
|
153
|
+
this.error("Error applying open pragmas:", e);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
136
157
|
private async startDatabase(
|
|
137
158
|
sqlite3: Sqlite3Static,
|
|
138
159
|
dbName: string,
|
|
139
160
|
requestId: StartRequestId,
|
|
161
|
+
openOptions?: SqliteWasmWorkerOpenOptions,
|
|
140
162
|
) {
|
|
141
163
|
const dbId = DbIdSchema.parse(crypto.randomUUID());
|
|
142
164
|
|
|
@@ -146,24 +168,14 @@ class SqliteWorkerHelper extends WorkerHelper<
|
|
|
146
168
|
if ("opfs" in sqlite3) {
|
|
147
169
|
db = new sqlite3.oo1.OpfsDb(dbFileName);
|
|
148
170
|
this.log("OPFS database created:", db.filename);
|
|
149
|
-
|
|
150
|
-
// Configure database for reliable persistence
|
|
151
|
-
try {
|
|
152
|
-
// Ensure WAL mode is enabled
|
|
153
|
-
db.exec("PRAGMA journal_mode=WAL;");
|
|
154
|
-
// Use FULL synchronous mode to ensure data is written to persistent storage
|
|
155
|
-
// before transactions are considered complete
|
|
156
|
-
db.exec("PRAGMA synchronous=FULL;");
|
|
157
|
-
this.log("Database configured with WAL mode and FULL synchronous");
|
|
158
|
-
} catch (e) {
|
|
159
|
-
this.error("Error configuring database:", e);
|
|
160
|
-
}
|
|
171
|
+
this.applyOpenPragmas(db, openOptions);
|
|
161
172
|
} else {
|
|
162
173
|
db = new sqlite3.oo1.DB(dbFileName, "c");
|
|
163
174
|
this.log(
|
|
164
175
|
"OPFS is not available, created transient database",
|
|
165
176
|
db.filename,
|
|
166
177
|
);
|
|
178
|
+
this.applyOpenPragmas(db, openOptions);
|
|
167
179
|
}
|
|
168
180
|
|
|
169
181
|
// Store database with initialized flag
|
|
@@ -183,7 +195,12 @@ class SqliteWorkerHelper extends WorkerHelper<
|
|
|
183
195
|
case SqliteWorkerClientMessageType.Start:
|
|
184
196
|
{
|
|
185
197
|
const sqlite3 = await this.initPromise;
|
|
186
|
-
await this.startDatabase(
|
|
198
|
+
await this.startDatabase(
|
|
199
|
+
sqlite3,
|
|
200
|
+
data.dbName,
|
|
201
|
+
data.requestId,
|
|
202
|
+
data.openOptions,
|
|
203
|
+
);
|
|
187
204
|
}
|
|
188
205
|
break;
|
|
189
206
|
case SqliteWorkerClientMessageType.RemoteCallbackRequest:
|