@firtoz/drizzle-sqlite-wasm 0.2.16 → 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 +23 -0
- package/README.md +24 -2
- package/package.json +7 -6
- package/src/collections/sqlite-collection.ts +9 -3
- package/src/collections/synced-sqlite-collection.ts +47 -0
- package/src/hooks/useDrizzleSqliteDb.ts +8 -2
- package/src/index.ts +12 -0
- 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,28 @@
|
|
|
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
|
+
|
|
20
|
+
## 0.2.17
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- [`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.
|
|
25
|
+
|
|
3
26
|
## 0.2.16
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -227,7 +227,9 @@ Create TanStack DB collections backed by SQLite:
|
|
|
227
227
|
|
|
228
228
|
```typescript
|
|
229
229
|
import { createCollection } from "@tanstack/db";
|
|
230
|
-
import {
|
|
230
|
+
import type { DrizzleSqliteTableCollection } from "@firtoz/drizzle-utils";
|
|
231
|
+
import { drizzleCollectionOptions } from "@firtoz/drizzle-sqlite-wasm";
|
|
232
|
+
import * as schema from "./schema";
|
|
231
233
|
|
|
232
234
|
const collection = createCollection(
|
|
233
235
|
drizzleCollectionOptions({
|
|
@@ -249,12 +251,16 @@ const completed = await collection.find({
|
|
|
249
251
|
orderBy: { createdAt: "desc" },
|
|
250
252
|
});
|
|
251
253
|
|
|
254
|
+
type TodosCollection = DrizzleSqliteTableCollection<typeof schema.todoTable>;
|
|
255
|
+
|
|
252
256
|
// Subscribe to changes
|
|
253
257
|
collection.subscribe((todos) => {
|
|
254
258
|
console.log("Todos updated:", todos);
|
|
255
259
|
});
|
|
256
260
|
```
|
|
257
261
|
|
|
262
|
+
Use `DrizzleSqliteTableCollection<TTable>` from `@firtoz/drizzle-utils` when you want a reusable collection type alias (shared with `@firtoz/drizzle-durable-sqlite`).
|
|
263
|
+
|
|
258
264
|
### Collection Options
|
|
259
265
|
|
|
260
266
|
**Config:**
|
|
@@ -287,11 +293,27 @@ Context provider for SQLite WASM:
|
|
|
287
293
|
- `dbName: string` - Name of the SQLite database
|
|
288
294
|
- `schema: TSchema` - Drizzle schema object
|
|
289
295
|
- `migrations: DurableSqliteMigrationConfig` - Migration configuration
|
|
296
|
+
- `workerOpenOptions?: SqliteWasmWorkerOpenOptions` - Optional `PRAGMA synchronous` / `journal_mode` on first DB open (see hook section below)
|
|
290
297
|
|
|
291
|
-
#### `useDrizzleSqliteDb(worker, dbName, schema, migrations)`
|
|
298
|
+
#### `useDrizzleSqliteDb(worker, dbName, schema, migrations, debug?, interceptor?, workerOpenOptions?)`
|
|
292
299
|
|
|
293
300
|
Hook to create a Drizzle instance with Web Worker:
|
|
294
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
|
+
|
|
295
317
|
```typescript
|
|
296
318
|
function MyComponent() {
|
|
297
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",
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
InsertToSelectSchema,
|
|
12
12
|
TableWithRequiredFields,
|
|
13
13
|
BaseSyncConfig,
|
|
14
|
+
IdOf,
|
|
14
15
|
} from "@firtoz/drizzle-utils";
|
|
15
16
|
import {
|
|
16
17
|
createSyncFunction,
|
|
@@ -67,8 +68,9 @@ export type ValidTableNames<TSchema extends Record<string, unknown>> = {
|
|
|
67
68
|
export type SqliteCollectionConfig<TTable extends Table> = Omit<
|
|
68
69
|
CollectionConfig<
|
|
69
70
|
InferSchemaOutput<SelectSchema<TTable>>,
|
|
70
|
-
|
|
71
|
-
InsertToSelectSchema<TTable
|
|
71
|
+
IdOf<TTable>,
|
|
72
|
+
InsertToSelectSchema<TTable>,
|
|
73
|
+
CollectionUtils<InferSchemaOutput<SelectSchema<TTable>>>
|
|
72
74
|
>,
|
|
73
75
|
"utils"
|
|
74
76
|
> & {
|
|
@@ -88,6 +90,9 @@ export function sqliteCollectionOptions<
|
|
|
88
90
|
|
|
89
91
|
const table = config.drizzle?._.fullSchema[tableName] as TTable;
|
|
90
92
|
|
|
93
|
+
type TItem = InferSchemaOutput<SelectSchema<TTable>>;
|
|
94
|
+
const getKey = createGetKeyFunction<TTable>();
|
|
95
|
+
|
|
91
96
|
const backend = createSqliteTableSyncBackend({
|
|
92
97
|
drizzle: config.drizzle,
|
|
93
98
|
table,
|
|
@@ -103,6 +108,7 @@ export function sqliteCollectionOptions<
|
|
|
103
108
|
readyPromise: config.readyPromise,
|
|
104
109
|
syncMode: config.syncMode,
|
|
105
110
|
debug: config.debug,
|
|
111
|
+
getSyncPersistKey: (item: TItem) => String(getKey(item)),
|
|
106
112
|
};
|
|
107
113
|
|
|
108
114
|
const syncResult = createSyncFunction(baseSyncConfig, backend);
|
|
@@ -111,7 +117,7 @@ export function sqliteCollectionOptions<
|
|
|
111
117
|
|
|
112
118
|
const collectionConfig = createCollectionConfig({
|
|
113
119
|
schema,
|
|
114
|
-
getKey
|
|
120
|
+
getKey,
|
|
115
121
|
syncResult,
|
|
116
122
|
onInsert: config.debug
|
|
117
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,9 +1,11 @@
|
|
|
1
1
|
export { drizzleSqliteWasm } from "./drizzle/direct";
|
|
2
2
|
export {
|
|
3
3
|
sqliteCollectionOptions as drizzleCollectionOptions,
|
|
4
|
+
type SqliteCollectionConfig,
|
|
4
5
|
type SQLOperation,
|
|
5
6
|
type SQLInterceptor,
|
|
6
7
|
} from "./collections/sqlite-collection";
|
|
8
|
+
export { createSyncedSqliteCollection } from "./collections/synced-sqlite-collection";
|
|
7
9
|
export { syncableTable } from "@firtoz/drizzle-utils";
|
|
8
10
|
export { makeId } from "@firtoz/drizzle-utils";
|
|
9
11
|
export type {
|
|
@@ -32,5 +34,15 @@ export {
|
|
|
32
34
|
} from "./worker/global-manager";
|
|
33
35
|
export { SqliteWorkerManager, DbInstance } from "./worker/manager";
|
|
34
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";
|
|
35
47
|
export { customSqliteMigrate } from "./migration/migrator";
|
|
36
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:
|