@evolu/web 3.4.1 → 3.5.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/README.md +4 -0
- package/dist/src/Sqlite.d.ts +71 -2
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +126 -137
- package/dist/src/local-first/Db.worker.js +12 -3
- package/package.json +4 -4
- package/src/Sqlite.test.ts +446 -420
- package/src/Sqlite.ts +195 -171
- package/src/local-first/Db.worker.ts +17 -3
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@ This package provides browser-specific Evolu implementations, including
|
|
|
4
4
|
OPFS-backed SQLite, Web Workers, Shared Workers, and browser platform
|
|
5
5
|
dependencies.
|
|
6
6
|
|
|
7
|
+
SQLite comes from `@evolu/sqlite-wasm`. Bundlers emit its WebAssembly binary
|
|
8
|
+
with the app. Vite does not emit it from a dependency it prebundles, so add
|
|
9
|
+
`@evolu/sqlite-wasm` to `optimizeDeps.exclude`.
|
|
10
|
+
|
|
7
11
|
As runtimes increasingly implement the same Web Platform APIs, Evolu's portable
|
|
8
12
|
abstractions for `WebSocket`, `MessagePort`, and Web Locks live in
|
|
9
13
|
`@evolu/common`. Their concrete implementations are supplied by the runtime.
|
package/dist/src/Sqlite.d.ts
CHANGED
|
@@ -1,3 +1,72 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The SQLite driver of Evolu for the web, on `@evolu/sqlite-wasm`.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { type CreateSqliteDriver, type Result, type Task } from "@evolu/common";
|
|
7
|
+
import type { WaitForDatabaseRelease } from "@evolu/common/local-first";
|
|
8
|
+
import { type OpfsRootDep, type SqliteWasm, type SqliteWasmError, type SubtleCryptoDep } from "@evolu/sqlite-wasm";
|
|
9
|
+
/**
|
|
10
|
+
* Loads SQLite from the {@link sqliteWasmUrl} of `@evolu/sqlite-wasm` for
|
|
11
|
+
* {@link createWasmSqliteDriver}. It passes the fetch to
|
|
12
|
+
* {@link createSqliteWasm}, which compiles the binary while it downloads when
|
|
13
|
+
* the server sends it as `application/wasm`, and from its bytes otherwise.
|
|
14
|
+
*
|
|
15
|
+
* Start it in the worker's composition root, so SQLite loads while the worker
|
|
16
|
+
* waits for its database.
|
|
17
|
+
*/
|
|
18
|
+
export declare const loadSqliteWasm: Task<SqliteWasm, SqliteWasmError>;
|
|
19
|
+
/**
|
|
20
|
+
* Creates the {@link CreateSqliteDriver} of Evolu for the web.
|
|
21
|
+
*
|
|
22
|
+
* A database is the file `/evolu1.db` in a pool of OPFS sync access handles in
|
|
23
|
+
* the directory `.<name>`, in the format of SQLite's opfs-sahpool, and the pool
|
|
24
|
+
* encrypts it in the `encrypted` mode. So the databases `@evolu/web` 3 created
|
|
25
|
+
* with `@evolu/sqlite-wasm` 2.2.4 open unchanged, an encrypted one with the key
|
|
26
|
+
* 2.2.4 derived from the encryption key, which is never rekeyed. 2.2.4 derived
|
|
27
|
+
* it because of a bug in SQLite3 Multiple Ciphers: it took the encryption key,
|
|
28
|
+
* passed in SQLCipher's notation for a raw key, as a passphrase
|
|
29
|
+
* (https://github.com/utelle/SQLite3MultipleCiphers/issues/218), as
|
|
30
|
+
* {@link deriveLegacySqliteKey} of `@evolu/sqlite-wasm` describes. A new
|
|
31
|
+
* encrypted database is encrypted with the encryption key itself, so
|
|
32
|
+
* `@evolu/web` 3.4.1 and earlier cannot open it. A database in the `memory`
|
|
33
|
+
* mode uses no OPFS.
|
|
34
|
+
*
|
|
35
|
+
* In WebKit on macOS, such as Safari, whose file system ignores case by
|
|
36
|
+
* default, names that differ only in case share the directory, and both can
|
|
37
|
+
* hold it at once, so make database names differ in more than case.
|
|
38
|
+
*
|
|
39
|
+
* The pool is opened once, so the driver throws while another context holds a
|
|
40
|
+
* file of it. A DbWorker waits for the files with
|
|
41
|
+
* {@link createWaitForDatabaseRelease} first.
|
|
42
|
+
*
|
|
43
|
+
* The {@link SqliteDriver} contract has no error channel, so every error, such
|
|
44
|
+
* as a query's {@link SqliteError}, is thrown as the cause of an `Error` whose
|
|
45
|
+
* message is SQLite's message, or the error's type for an error without one.
|
|
46
|
+
*/
|
|
47
|
+
export declare const createWasmSqliteDriver: (deps: WasmSqliteDriverDeps) => CreateSqliteDriver;
|
|
48
|
+
/** Dependencies of {@link createWasmSqliteDriver}. */
|
|
49
|
+
export type WasmSqliteDriverDeps = OpfsRootDep & SqliteWasmLoadDep & SubtleCryptoDep;
|
|
50
|
+
/** Dependency wrapper for SQLite as {@link loadSqliteWasm} loads it. */
|
|
51
|
+
export interface SqliteWasmLoadDep {
|
|
52
|
+
/**
|
|
53
|
+
* SQLite, as {@link loadSqliteWasm} loads it, still loading or loaded. A
|
|
54
|
+
* failed load throws when a database is opened.
|
|
55
|
+
*/
|
|
56
|
+
readonly sqliteWasmLoad: PromiseLike<Result<SqliteWasm, SqliteWasmError>>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Creates the {@link WaitForDatabaseRelease} of Evolu for the web. It opens the
|
|
60
|
+
* pool {@link createWasmSqliteDriver} opens for the database, and disposes it
|
|
61
|
+
* once it opens.
|
|
62
|
+
*
|
|
63
|
+
* When another context holds a file of the pool, the pool is opened again after
|
|
64
|
+
* 50 ms, then after twice the previous delay, up to a second, with the
|
|
65
|
+
* `heldTimeout` option of {@link openSahPool}, and the wait fails with
|
|
66
|
+
* {@link DatabaseHeldError} when it is still held 10 seconds after the first
|
|
67
|
+
* attempt started. Evolu then refuses to start the database, and the app
|
|
68
|
+
* receives the error as its `evoluError`. Every other error is thrown, as the
|
|
69
|
+
* driver throws it.
|
|
70
|
+
*/
|
|
71
|
+
export declare const createWaitForDatabaseRelease: (deps: OpfsRootDep & SqliteWasmLoadDep) => WaitForDatabaseRelease;
|
|
3
72
|
//# sourceMappingURL=Sqlite.d.ts.map
|
package/dist/src/Sqlite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sqlite.d.ts","sourceRoot":"","sources":["../../src/Sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"Sqlite.d.ts","sourceRoot":"","sources":["../../src/Sqlite.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAKL,KAAK,kBAAkB,EAGvB,KAAK,MAAM,EAEX,KAAK,IAAI,EAGV,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EASL,KAAK,WAAW,EAOhB,KAAK,UAAU,EAEf,KAAK,eAAe,EACpB,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,EAAE,IAAI,CAAC,UAAU,EAAE,eAAe,CACS,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,sBAAsB,SAC1B,oBAAoB,KAAG,kBA+F7B,CAAC;AAEJ,sDAAsD;AACtD,MAAM,MAAM,oBAAoB,GAAG,WAAW,GAC5C,iBAAiB,GACjB,eAAe,CAAC;AAElB,wEAAwE;AACxE,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC;CAC3E;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,4BAA4B,SAChC,WAAW,GAAG,iBAAiB,KAAG,sBAaxC,CAAC"}
|
package/dist/src/Sqlite.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SQLite driver of Evolu for the web, on `@evolu/sqlite-wasm`.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
1
6
|
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
7
|
if (value !== null && value !== void 0) {
|
|
3
8
|
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
@@ -50,166 +55,98 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
55
|
var e = new Error(message);
|
|
51
56
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
57
|
});
|
|
53
|
-
import {
|
|
54
|
-
import
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
58
|
+
import { createPreparedStatementsCache, err, exhaustiveCheck, ok, } from "@evolu/common";
|
|
59
|
+
import { createEncryptedSqliteDatabase, createSqliteDatabase, createSqliteWasm, OpfsName, openSahPool, sqliteWasmUrl, SqliteVfsPath, } from "@evolu/sqlite-wasm";
|
|
60
|
+
/**
|
|
61
|
+
* Loads SQLite from the {@link sqliteWasmUrl} of `@evolu/sqlite-wasm` for
|
|
62
|
+
* {@link createWasmSqliteDriver}. It passes the fetch to
|
|
63
|
+
* {@link createSqliteWasm}, which compiles the binary while it downloads when
|
|
64
|
+
* the server sends it as `application/wasm`, and from its bytes otherwise.
|
|
65
|
+
*
|
|
66
|
+
* Start it in the worker's composition root, so SQLite loads while the worker
|
|
67
|
+
* waits for its database.
|
|
68
|
+
*/
|
|
69
|
+
export const loadSqliteWasm = (run) => run(createSqliteWasm(run.deps.nativeFetch(sqliteWasmUrl)), run.deps);
|
|
70
|
+
/**
|
|
71
|
+
* Creates the {@link CreateSqliteDriver} of Evolu for the web.
|
|
72
|
+
*
|
|
73
|
+
* A database is the file `/evolu1.db` in a pool of OPFS sync access handles in
|
|
74
|
+
* the directory `.<name>`, in the format of SQLite's opfs-sahpool, and the pool
|
|
75
|
+
* encrypts it in the `encrypted` mode. So the databases `@evolu/web` 3 created
|
|
76
|
+
* with `@evolu/sqlite-wasm` 2.2.4 open unchanged, an encrypted one with the key
|
|
77
|
+
* 2.2.4 derived from the encryption key, which is never rekeyed. 2.2.4 derived
|
|
78
|
+
* it because of a bug in SQLite3 Multiple Ciphers: it took the encryption key,
|
|
79
|
+
* passed in SQLCipher's notation for a raw key, as a passphrase
|
|
80
|
+
* (https://github.com/utelle/SQLite3MultipleCiphers/issues/218), as
|
|
81
|
+
* {@link deriveLegacySqliteKey} of `@evolu/sqlite-wasm` describes. A new
|
|
82
|
+
* encrypted database is encrypted with the encryption key itself, so
|
|
83
|
+
* `@evolu/web` 3.4.1 and earlier cannot open it. A database in the `memory`
|
|
84
|
+
* mode uses no OPFS.
|
|
85
|
+
*
|
|
86
|
+
* In WebKit on macOS, such as Safari, whose file system ignores case by
|
|
87
|
+
* default, names that differ only in case share the directory, and both can
|
|
88
|
+
* hold it at once, so make database names differ in more than case.
|
|
89
|
+
*
|
|
90
|
+
* The pool is opened once, so the driver throws while another context holds a
|
|
91
|
+
* file of it. A DbWorker waits for the files with
|
|
92
|
+
* {@link createWaitForDatabaseRelease} first.
|
|
93
|
+
*
|
|
94
|
+
* The {@link SqliteDriver} contract has no error channel, so every error, such
|
|
95
|
+
* as a query's {@link SqliteError}, is thrown as the cause of an `Error` whose
|
|
96
|
+
* message is SQLite's message, or the error's type for an error without one.
|
|
97
|
+
*/
|
|
98
|
+
export const createWasmSqliteDriver = (deps) => (name, options) => async (run) => {
|
|
71
99
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
72
100
|
try {
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const useDatabase = (database) => disposer.adopt(database, (database) => {
|
|
76
|
-
database.close();
|
|
77
|
-
});
|
|
101
|
+
const sqliteWasm = getOrThrowWithMessage(await deps.sqliteWasmLoad);
|
|
102
|
+
const databaseDeps = { ...deps, sqliteWasm };
|
|
78
103
|
let deleteDatabaseFile = false;
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// crashed or navigated away, can hold pool files after the lock has
|
|
83
|
-
// passed on: WebKit releases a terminated worker's locks and its files
|
|
84
|
-
// separately, in no set order
|
|
85
|
-
// (https://bugs.webkit.org/show_bug.cgi?id=301520). sqlite-wasm cannot
|
|
86
|
-
// set up a pool with a held file, and it then deletes the pool
|
|
87
|
-
// directory, which the held file usually but not always prevents. Only
|
|
88
|
-
// a worker that has ended can hold
|
|
89
|
-
// the files, and it can only release them, so the pool is set up once
|
|
90
|
-
// every file opens.
|
|
91
|
-
const canOpenPoolFiles = async () => {
|
|
92
|
-
const root = await navigator.storage.getDirectory();
|
|
93
|
-
// sqlite-wasm keeps the pool in `.${name}/.opaque` in both OPFS modes.
|
|
94
|
-
const poolDirectory = await tryAsync(() => root.getDirectoryHandle(`.${name}`));
|
|
95
|
-
if (!poolDirectory.ok)
|
|
96
|
-
return true;
|
|
97
|
-
const opaqueDirectory = await tryAsync(() => poolDirectory.value.getDirectoryHandle(".opaque"));
|
|
98
|
-
if (!opaqueDirectory.ok)
|
|
99
|
-
return true;
|
|
100
|
-
for await (const handle of opaqueDirectory.value.values()) {
|
|
101
|
-
if (handle.kind !== "file")
|
|
102
|
-
continue;
|
|
103
|
-
const accessHandle = await tryAsync(() => handle.createSyncAccessHandle());
|
|
104
|
-
if (!accessHandle.ok) {
|
|
105
|
-
// WebKit rejects a held file with InvalidStateError, other
|
|
106
|
-
// engines with NoModificationAllowedError, as the spec says
|
|
107
|
-
// (https://bugs.webkit.org/show_bug.cgi?id=326135).
|
|
108
|
-
// WebKit also uses InvalidStateError for a closed or invalid
|
|
109
|
-
// handle and a stopped context. A retry opens fresh handles from
|
|
110
|
-
// a new listing, and a stopped context ends the loop with its
|
|
111
|
-
// worker, so retrying those is harmless.
|
|
112
|
-
if (accessHandle.error instanceof DOMException &&
|
|
113
|
-
(accessHandle.error.name === "InvalidStateError" ||
|
|
114
|
-
accessHandle.error.name === "NoModificationAllowedError"))
|
|
115
|
-
return false;
|
|
116
|
-
throw accessHandle.error;
|
|
117
|
-
}
|
|
118
|
-
accessHandle.value.close();
|
|
119
|
-
}
|
|
120
|
-
return true;
|
|
121
|
-
};
|
|
122
|
-
const waitStart = run.deps.time.performance.now();
|
|
123
|
-
let retryDelay = 50;
|
|
124
|
-
let isWaitReported = false;
|
|
125
|
-
while (!(await canOpenPoolFiles())) {
|
|
126
|
-
const waited = performanceDurationBetween(waitStart, run.deps.time.performance.now());
|
|
127
|
-
if (!isWaitReported && waited >= 5000) {
|
|
128
|
-
isWaitReported = true;
|
|
129
|
-
run.deps.console.warn(`Waiting for an ended DbWorker to release the files of database ${name}.`);
|
|
130
|
-
}
|
|
131
|
-
await run.ok(sleep(PositiveMillis.orThrow(retryDelay)));
|
|
132
|
-
retryDelay = Math.min(retryDelay * 2, 1000);
|
|
133
|
-
}
|
|
134
|
-
const pool = await sqlite3.installOpfsSAHPoolVfs(options);
|
|
135
|
-
if (pool.isPaused())
|
|
136
|
-
await pool.unpauseVfs();
|
|
104
|
+
const disposer = __addDisposableResource(env_1, new DisposableStack(), false);
|
|
105
|
+
const openPool = async () => {
|
|
106
|
+
const pool = disposer.use(getOrThrowWithMessage(await run(openDatabasePool(name), databaseDeps)));
|
|
137
107
|
disposer.defer(() => {
|
|
138
108
|
if (deleteDatabaseFile)
|
|
139
|
-
pool.unlink(
|
|
140
|
-
pool.pauseVfs();
|
|
109
|
+
getOrThrowWithMessage(pool.unlink(databasePath));
|
|
141
110
|
});
|
|
142
111
|
return pool;
|
|
143
112
|
};
|
|
144
|
-
let
|
|
113
|
+
let database;
|
|
145
114
|
switch (options?.mode) {
|
|
146
115
|
case "memory":
|
|
147
|
-
|
|
148
|
-
db = useDatabase(new sqlite3.oo1.DB(":memory:"));
|
|
116
|
+
database = disposer.use(getOrThrowWithMessage(createSqliteDatabase(databaseDeps)({ type: "Memory" })));
|
|
149
117
|
break;
|
|
150
118
|
case "encrypted": {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
// oxlint-disable-next-line react/rules-of-hooks -- useDatabase registers disposal and is not a React Hook.
|
|
159
|
-
db = useDatabase(new pool.OpfsSAHPoolDb(
|
|
160
|
-
// SQLite normalizes this URI filename to SAH-pool path "/evolu1.db".
|
|
161
|
-
`file:${fileName}?vfs=multipleciphers-opfs-sahpool`));
|
|
162
|
-
db.exec(`
|
|
163
|
-
PRAGMA cipher = 'sqlcipher';
|
|
164
|
-
PRAGMA key = "x'${bytesToHex(options.encryptionKey)}'";
|
|
165
|
-
`);
|
|
119
|
+
const encrypted = getOrThrowWithMessage(await run(createEncryptedSqliteDatabase({
|
|
120
|
+
type: "EncryptedFile",
|
|
121
|
+
vfs: await openPool(),
|
|
122
|
+
path: databasePath,
|
|
123
|
+
key: options.encryptionKey,
|
|
124
|
+
}), databaseDeps));
|
|
125
|
+
database = disposer.use(encrypted.database);
|
|
166
126
|
break;
|
|
167
127
|
}
|
|
168
|
-
case undefined:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
128
|
+
case undefined:
|
|
129
|
+
database = disposer.use(getOrThrowWithMessage(createSqliteDatabase(databaseDeps)({
|
|
130
|
+
type: "File",
|
|
131
|
+
vfs: await openPool(),
|
|
132
|
+
path: databasePath,
|
|
133
|
+
})));
|
|
172
134
|
break;
|
|
173
|
-
}
|
|
174
135
|
default:
|
|
175
136
|
exhaustiveCheck(options);
|
|
176
137
|
}
|
|
177
|
-
const cache = disposer.use(createPreparedStatementsCache((sql) =>
|
|
178
|
-
statement.
|
|
138
|
+
const cache = disposer.use(createPreparedStatementsCache((sql) => getOrThrowWithMessage(database.prepare(sql)), (statement) => {
|
|
139
|
+
statement[Symbol.dispose]();
|
|
179
140
|
}));
|
|
180
141
|
const disposables = disposer.move();
|
|
181
142
|
return ok({
|
|
182
143
|
exec: (query) => {
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
prepared.bind(query.parameters);
|
|
188
|
-
const rows = [];
|
|
189
|
-
while (prepared.step()) {
|
|
190
|
-
rows.push(prepared.get({}));
|
|
191
|
-
}
|
|
192
|
-
return {
|
|
193
|
-
rows: rows,
|
|
194
|
-
changes: db.changes(),
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
finally {
|
|
198
|
-
// SQLite refuses to bind a statement whose step failed until it is
|
|
199
|
-
// reset. That reset returns the step's error again, which
|
|
200
|
-
// PreparedStatement.reset would throw over the original one.
|
|
201
|
-
sqlite3.capi.sqlite3_reset(prepared);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
const rows = db.exec(query.sql, {
|
|
205
|
-
returnValue: "resultRows",
|
|
206
|
-
rowMode: "object",
|
|
207
|
-
bind: query.parameters,
|
|
208
|
-
});
|
|
209
|
-
const changes = db.changes();
|
|
210
|
-
return { rows, changes };
|
|
144
|
+
const statement = cache.get(query);
|
|
145
|
+
return getOrThrowWithMessage(statement
|
|
146
|
+
? statement.run(query.parameters)
|
|
147
|
+
: database.run(query.sql, query.parameters));
|
|
211
148
|
},
|
|
212
|
-
export: () =>
|
|
149
|
+
export: () => getOrThrowWithMessage(database.export()),
|
|
213
150
|
deleteDatabase: () => {
|
|
214
151
|
deleteDatabaseFile = true;
|
|
215
152
|
disposables.dispose();
|
|
@@ -227,3 +164,55 @@ export const createWasmSqliteDriver = (name, options) => async (run) => {
|
|
|
227
164
|
__disposeResources(env_1);
|
|
228
165
|
}
|
|
229
166
|
};
|
|
167
|
+
/**
|
|
168
|
+
* Creates the {@link WaitForDatabaseRelease} of Evolu for the web. It opens the
|
|
169
|
+
* pool {@link createWasmSqliteDriver} opens for the database, and disposes it
|
|
170
|
+
* once it opens.
|
|
171
|
+
*
|
|
172
|
+
* When another context holds a file of the pool, the pool is opened again after
|
|
173
|
+
* 50 ms, then after twice the previous delay, up to a second, with the
|
|
174
|
+
* `heldTimeout` option of {@link openSahPool}, and the wait fails with
|
|
175
|
+
* {@link DatabaseHeldError} when it is still held 10 seconds after the first
|
|
176
|
+
* attempt started. Evolu then refuses to start the database, and the app
|
|
177
|
+
* receives the error as its `evoluError`. Every other error is thrown, as the
|
|
178
|
+
* driver throws it.
|
|
179
|
+
*/
|
|
180
|
+
export const createWaitForDatabaseRelease = (deps) => (name) => async (run) => {
|
|
181
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
182
|
+
try {
|
|
183
|
+
const sqliteWasm = getOrThrowWithMessage(await deps.sqliteWasmLoad);
|
|
184
|
+
const opened = await run(
|
|
185
|
+
// Longer than the two seconds Chromium gives a terminated worker.
|
|
186
|
+
openDatabasePool(name, { heldTimeout: "10s" }), { ...deps, sqliteWasm });
|
|
187
|
+
if (!opened.ok && opened.error.type === "SahPoolHeldError")
|
|
188
|
+
return err({ type: "DatabaseHeldError", name });
|
|
189
|
+
const _pool = __addDisposableResource(env_2, getOrThrowWithMessage(opened), false);
|
|
190
|
+
return ok();
|
|
191
|
+
}
|
|
192
|
+
catch (e_2) {
|
|
193
|
+
env_2.error = e_2;
|
|
194
|
+
env_2.hasError = true;
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
__disposeResources(env_2);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
// The pool of a database's file is in the directory `.<name>`, as in
|
|
201
|
+
// @evolu/web 3.
|
|
202
|
+
const openDatabasePool = (name, options) =>
|
|
203
|
+
// A Name is non-empty and URL-safe, so `.<name>` always passes.
|
|
204
|
+
openSahPool({ ...options, directory: [OpfsName.orThrow(`.${name}`)] });
|
|
205
|
+
// The path SQLite's opfs-sahpool gave `file:evolu1.db`, which @evolu/web 3
|
|
206
|
+
// opened.
|
|
207
|
+
const databasePath = /*#__PURE__*/ SqliteVfsPath.orThrow("/evolu1.db");
|
|
208
|
+
// The SqliteDriver contract has no error channel, and the WaitForDatabaseRelease
|
|
209
|
+
// contract only DatabaseHeldError, so any other error is thrown as the cause of
|
|
210
|
+
// an Error, with SQLite's message when it has one.
|
|
211
|
+
const getOrThrowWithMessage = (result) => {
|
|
212
|
+
if (result.ok)
|
|
213
|
+
return result.value;
|
|
214
|
+
const { error } = result;
|
|
215
|
+
throw new Error("message" in error && typeof error.message === "string"
|
|
216
|
+
? error.message
|
|
217
|
+
: error.type, { cause: error });
|
|
218
|
+
};
|
|
@@ -3,13 +3,22 @@ import { installPolyfills } from "@evolu/common/polyfills";
|
|
|
3
3
|
installPolyfills();
|
|
4
4
|
import { createRandomBytes } from "@evolu/common";
|
|
5
5
|
import { startDbWorker } from "@evolu/common/local-first";
|
|
6
|
-
import { createWasmSqliteDriver } from "../Sqlite.js";
|
|
6
|
+
import { createWaitForDatabaseRelease, createWasmSqliteDriver, loadSqliteWasm, } from "../Sqlite.js";
|
|
7
7
|
import { createRun } from "../Task.js";
|
|
8
8
|
import { createWorkerDeps, createWorkerSelf } from "../Worker.js";
|
|
9
9
|
const run = createRun({
|
|
10
10
|
...createWorkerDeps(),
|
|
11
|
-
createSqliteDriver: createWasmSqliteDriver,
|
|
12
11
|
lockManager: navigator.locks,
|
|
13
12
|
randomBytes: createRandomBytes(),
|
|
14
13
|
});
|
|
15
|
-
|
|
14
|
+
const sqliteDeps = {
|
|
15
|
+
opfsRoot: navigator.storage,
|
|
16
|
+
// SQLite loads while the DbWorker waits for its database.
|
|
17
|
+
sqliteWasmLoad: run(loadSqliteWasm),
|
|
18
|
+
subtleCrypto: crypto.subtle,
|
|
19
|
+
};
|
|
20
|
+
void run(startDbWorker(createWorkerSelf(self)), {
|
|
21
|
+
...run.deps,
|
|
22
|
+
createSqliteDriver: createWasmSqliteDriver(sqliteDeps),
|
|
23
|
+
waitForDatabaseRelease: createWaitForDatabaseRelease(sqliteDeps),
|
|
24
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evolu/web",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "Evolu for web",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"evolu",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"README.md"
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@evolu/sqlite-wasm": "
|
|
35
|
+
"@evolu/sqlite-wasm": "3.53.4-build1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@evolu/common": "8.
|
|
38
|
+
"@evolu/common": "8.19.0",
|
|
39
39
|
"@evolu/typescript-config": "0.1.1",
|
|
40
40
|
"@types/node": "^24.10.9",
|
|
41
41
|
"@types/sharedworker": "^0.0.229",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"user-agent-data-types": "^0.4.2"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@evolu/common": "^8.
|
|
47
|
+
"@evolu/common": "^8.19.0"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|
|
50
50
|
"access": "public"
|