@evolu/web 3.1.2 → 3.3.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/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +4 -1
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +70 -12
- package/dist/src/Task.d.ts +6 -3
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +2 -2
- package/dist/src/Worker.d.ts +38 -1
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +61 -2
- package/dist/src/local-first/Evolu.d.ts +47 -3
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +336 -50
- package/dist/src/local-first/Shared.worker.js +14 -4
- package/package.json +4 -5
- package/src/Platform.test.ts +29 -1
- package/src/Platform.ts +2 -1
- package/src/Sqlite.test.ts +181 -4
- package/src/Sqlite.ts +85 -12
- package/src/Task.test.ts +3 -14
- package/src/Task.ts +8 -4
- package/src/Worker.test.ts +59 -0
- package/src/Worker.ts +66 -1
- package/src/local-first/Evolu.test.ts +601 -2
- package/src/local-first/Evolu.ts +321 -40
- package/src/local-first/Shared.worker.ts +32 -5
- package/dist/src/local-first/LocalAuth.d.ts +0 -3
- package/dist/src/local-first/LocalAuth.d.ts.map +0 -1
- package/dist/src/local-first/LocalAuth.js +0 -185
- package/src/local-first/LocalAuth.ts +0 -320
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Platform.d.ts","sourceRoot":"","sources":["../../src/Platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAE5D,+DAA+D;AAC/D,eAAO,MAAM,eAAe,QAAO,OAIlC,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,QAAO,WACY,CAAC;AAErD,eAAO,MAAM,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"Platform.d.ts","sourceRoot":"","sources":["../../src/Platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAE5D,+DAA+D;AAC/D,eAAO,MAAM,eAAe,QAAO,OAIlC,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,QAAO,WACY,CAAC;AAErD,eAAO,MAAM,SAAS,EAAE,SAOvB,CAAC"}
|
package/dist/src/Platform.js
CHANGED
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,kBAAkB,EAAa,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"Sqlite.d.ts","sourceRoot":"","sources":["../../src/Sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAa,MAAM,eAAe,CAAC;AAqCnE,eAAO,MAAM,sBAAsB,EAAE,kBAyLlC,CAAC"}
|
package/dist/src/Sqlite.js
CHANGED
|
@@ -50,7 +50,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
50
|
var e = new Error(message);
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
|
-
import { bytesToHex, createPreparedStatementsCache, exhaustiveCheck, ok, } from "@evolu/common";
|
|
53
|
+
import { bytesToHex, createPreparedStatementsCache, exhaustiveCheck, ok, performanceDurationBetween, PositiveMillis, sleep, tryAsync, } from "@evolu/common";
|
|
54
54
|
import sqlite3InitModule, {} from "@evolu/sqlite-wasm";
|
|
55
55
|
// @ts-expect-error Missing types.
|
|
56
56
|
globalThis.sqlite3ApiConfig = {
|
|
@@ -67,7 +67,7 @@ globalThis.sqlite3ApiConfig = {
|
|
|
67
67
|
// Init ASAP.
|
|
68
68
|
const sqlite3Promise = sqlite3InitModule();
|
|
69
69
|
const fileName = "evolu1.db";
|
|
70
|
-
export const createWasmSqliteDriver = (name, options) => async () => {
|
|
70
|
+
export const createWasmSqliteDriver = (name, options) => async (run) => {
|
|
71
71
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
72
72
|
try {
|
|
73
73
|
const sqlite3 = await sqlite3Promise;
|
|
@@ -77,6 +77,57 @@ export const createWasmSqliteDriver = (name, options) => async () => {
|
|
|
77
77
|
});
|
|
78
78
|
let deleteDatabaseFile = false;
|
|
79
79
|
const createOpfsSAHPoolVfs = async (options) => {
|
|
80
|
+
// Evolu opens a database only while it holds the database lock, but a
|
|
81
|
+
// DbWorker that ended without closing it, because its tab closed,
|
|
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. sqlite-wasm cannot set up a pool with a
|
|
85
|
+
// held file, and it then deletes the pool directory, which the held file
|
|
86
|
+
// usually but not always prevents. Only a worker that has ended can hold
|
|
87
|
+
// the files, and it can only release them, so the pool is set up once
|
|
88
|
+
// every file opens.
|
|
89
|
+
const canOpenPoolFiles = async () => {
|
|
90
|
+
const root = await navigator.storage.getDirectory();
|
|
91
|
+
// sqlite-wasm keeps the pool in `.${name}/.opaque` in both OPFS modes.
|
|
92
|
+
const poolDirectory = await tryAsync(() => root.getDirectoryHandle(`.${name}`));
|
|
93
|
+
if (!poolDirectory.ok)
|
|
94
|
+
return true;
|
|
95
|
+
const opaqueDirectory = await tryAsync(() => poolDirectory.value.getDirectoryHandle(".opaque"));
|
|
96
|
+
if (!opaqueDirectory.ok)
|
|
97
|
+
return true;
|
|
98
|
+
for await (const handle of opaqueDirectory.value.values()) {
|
|
99
|
+
if (handle.kind !== "file")
|
|
100
|
+
continue;
|
|
101
|
+
const accessHandle = await tryAsync(() => handle.createSyncAccessHandle());
|
|
102
|
+
if (!accessHandle.ok) {
|
|
103
|
+
// WebKit rejects a held file with InvalidStateError, other
|
|
104
|
+
// engines with NoModificationAllowedError, as the spec says.
|
|
105
|
+
// WebKit also uses InvalidStateError for a closed or invalid
|
|
106
|
+
// handle and a stopped context. A retry opens fresh handles from
|
|
107
|
+
// a new listing, and a stopped context ends the loop with its
|
|
108
|
+
// worker, so retrying those is harmless.
|
|
109
|
+
if (accessHandle.error instanceof DOMException &&
|
|
110
|
+
(accessHandle.error.name === "InvalidStateError" ||
|
|
111
|
+
accessHandle.error.name === "NoModificationAllowedError"))
|
|
112
|
+
return false;
|
|
113
|
+
throw accessHandle.error;
|
|
114
|
+
}
|
|
115
|
+
accessHandle.value.close();
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
};
|
|
119
|
+
const waitStart = run.deps.time.performance.now();
|
|
120
|
+
let retryDelay = 50;
|
|
121
|
+
let isWaitReported = false;
|
|
122
|
+
while (!(await canOpenPoolFiles())) {
|
|
123
|
+
const waited = performanceDurationBetween(waitStart, run.deps.time.performance.now());
|
|
124
|
+
if (!isWaitReported && waited >= 5000) {
|
|
125
|
+
isWaitReported = true;
|
|
126
|
+
run.deps.console.warn(`Waiting for an ended DbWorker to release the files of database ${name}.`);
|
|
127
|
+
}
|
|
128
|
+
await run.ok(sleep(PositiveMillis.orThrow(retryDelay)));
|
|
129
|
+
retryDelay = Math.min(retryDelay * 2, 1000);
|
|
130
|
+
}
|
|
80
131
|
const pool = await sqlite3.installOpfsSAHPoolVfs(options);
|
|
81
132
|
if (pool.isPaused())
|
|
82
133
|
await pool.unpauseVfs();
|
|
@@ -128,17 +179,24 @@ export const createWasmSqliteDriver = (name, options) => async () => {
|
|
|
128
179
|
exec: (query) => {
|
|
129
180
|
const prepared = cache.get(query);
|
|
130
181
|
if (prepared) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
182
|
+
try {
|
|
183
|
+
if (query.parameters.length > 0)
|
|
184
|
+
prepared.bind(query.parameters);
|
|
185
|
+
const rows = [];
|
|
186
|
+
while (prepared.step()) {
|
|
187
|
+
rows.push(prepared.get({}));
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
rows: rows,
|
|
191
|
+
changes: db.changes(),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
// SQLite refuses to bind a statement whose step failed until it is
|
|
196
|
+
// reset. That reset returns the step's error again, which
|
|
197
|
+
// PreparedStatement.reset would throw over the original one.
|
|
198
|
+
sqlite3.capi.sqlite3_reset(prepared);
|
|
136
199
|
}
|
|
137
|
-
prepared.reset();
|
|
138
|
-
return {
|
|
139
|
-
rows: rows,
|
|
140
|
-
changes: db.changes(),
|
|
141
|
-
};
|
|
142
200
|
}
|
|
143
201
|
const rows = db.exec(query.sql, {
|
|
144
202
|
returnValue: "resultRows",
|
package/dist/src/Task.d.ts
CHANGED
|
@@ -7,13 +7,16 @@ import { type DisposableRun, type RunCustomDeps } from "@evolu/common";
|
|
|
7
7
|
/**
|
|
8
8
|
* Creates a root {@link Run} for the browser.
|
|
9
9
|
*
|
|
10
|
-
* Defects are reported with the browser's global `reportError
|
|
11
|
-
*
|
|
10
|
+
* Defects are reported with the browser's global `reportError` as an `Error`
|
|
11
|
+
* from {@link defectToError}, because browsers show any other value only as
|
|
12
|
+
* unhelpful text. A custom `reportDefect` dependency overrides the browser
|
|
13
|
+
* default.
|
|
12
14
|
*
|
|
13
15
|
* ### Example
|
|
14
16
|
*
|
|
15
17
|
* ```ts
|
|
16
18
|
* import {
|
|
19
|
+
* assertEqual,
|
|
17
20
|
* createConsole,
|
|
18
21
|
* createConsoleFormatter,
|
|
19
22
|
* ok,
|
|
@@ -29,7 +32,7 @@ import { type DisposableRun, type RunCustomDeps } from "@evolu/common";
|
|
|
29
32
|
* await using run = createRun({ console });
|
|
30
33
|
* const appPromise = run.ok(() => ok("started"));
|
|
31
34
|
*
|
|
32
|
-
*
|
|
35
|
+
* assertEqual(await appPromise, "started");
|
|
33
36
|
* ```
|
|
34
37
|
*/
|
|
35
38
|
export declare function createRun(): DisposableRun;
|
package/dist/src/Task.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Task.d.ts","sourceRoot":"","sources":["../../src/Task.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"Task.d.ts","sourceRoot":"","sources":["../../src/Task.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAGL,KAAK,aAAa,EAElB,KAAK,aAAa,EACnB,MAAM,eAAe,CAAC;AAEvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,SAAS,IAAI,aAAa,CAAC;AAE3C,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EACxC,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,GACrB,aAAa,CAAC,CAAC,CAAC,CAAC"}
|
package/dist/src/Task.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
-
import { createRun as createCommonRun, } from "@evolu/common";
|
|
6
|
+
import { createRun as createCommonRun, defectToError, } from "@evolu/common";
|
|
7
7
|
export function createRun(deps) {
|
|
8
8
|
const reportDefect = (reported) => {
|
|
9
|
-
reportError(reported);
|
|
9
|
+
reportError(defectToError(reported));
|
|
10
10
|
};
|
|
11
11
|
return deps === undefined
|
|
12
12
|
? createCommonRun({ reportDefect })
|
package/dist/src/Worker.d.ts
CHANGED
|
@@ -44,7 +44,9 @@ export declare const createWorkerSelf: <Input, Output = never>(nativeSelf: Dedic
|
|
|
44
44
|
* Creates an Evolu {@link SharedWorkerSelf} from a Web `SharedWorkerGlobalScope`
|
|
45
45
|
* (`self` inside a shared worker).
|
|
46
46
|
*
|
|
47
|
-
* Disposing closes the shared worker scope for all connected clients.
|
|
47
|
+
* Disposing closes the shared worker scope for all connected clients. Browsers
|
|
48
|
+
* never pass a shared worker's uncaught errors to its pages; see
|
|
49
|
+
* {@link addUncaughtErrorListener}.
|
|
48
50
|
*/
|
|
49
51
|
export declare const createSharedWorkerSelf: <Input, Output = never>(nativeSelf: SharedWorkerGlobalScope) => SharedWorkerSelf<Input, Output>;
|
|
50
52
|
/**
|
|
@@ -55,6 +57,41 @@ export declare const createSharedWorkerSelf: <Input, Output = never>(nativeSelf:
|
|
|
55
57
|
* `onMessage` is set, and does not share state across tabs.
|
|
56
58
|
*/
|
|
57
59
|
export declare const createOneTabSharedWorkerSelfPolyfill: <Input, Output = never>(nativeSelf: DedicatedWorkerGlobalScope) => SharedWorkerSelf<Input, Output>;
|
|
60
|
+
/**
|
|
61
|
+
* Passes each uncaught error and unhandled rejection of a worker global scope
|
|
62
|
+
* to `listener` as an `Error` from {@link defectToError}.
|
|
63
|
+
*
|
|
64
|
+
* Browsers never pass a shared worker's uncaught errors to a page's error
|
|
65
|
+
* handlers, and no worker passes its unhandled rejections to its page. A shared
|
|
66
|
+
* worker calls this with its global scope, `self`, to send them to its pages,
|
|
67
|
+
* for example over a BroadcastChannel. Defects of a Run from {@link createRun}
|
|
68
|
+
* arrive too, because it reports them with `reportError`, which dispatches
|
|
69
|
+
* "error" on the global scope.
|
|
70
|
+
*
|
|
71
|
+
* The events stay uncanceled, so the browser still logs them, and a dedicated
|
|
72
|
+
* worker still passes its uncaught errors to its page. The listeners stay until
|
|
73
|
+
* disposal, so they also receive errors after a Run's panic.
|
|
74
|
+
*
|
|
75
|
+
* ### Example
|
|
76
|
+
*
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { assertEqual } from "@evolu/common";
|
|
79
|
+
* import { addUncaughtErrorListener } from "@evolu/web";
|
|
80
|
+
*
|
|
81
|
+
* const messages: Array<string> = [];
|
|
82
|
+
* // A worker passes its global scope, `self`.
|
|
83
|
+
* const scope = new EventTarget();
|
|
84
|
+
* using _listener = addUncaughtErrorListener(scope, (error) => {
|
|
85
|
+
* messages.push(error.message);
|
|
86
|
+
* });
|
|
87
|
+
*
|
|
88
|
+
* scope.dispatchEvent(
|
|
89
|
+
* Object.assign(new Event("error"), { error: new Error("unexpected") }),
|
|
90
|
+
* );
|
|
91
|
+
* assertEqual(messages, ["unexpected"]);
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare const addUncaughtErrorListener: (scope: EventTarget, listener: (error: Error) => void) => Disposable;
|
|
58
95
|
/** Creates deps shared by web worker entry points. */
|
|
59
96
|
export declare const createWorkerDeps: () => WorkerDeps & CreateBroadcastChannelDep & CreateMessageChannelDep;
|
|
60
97
|
//# sourceMappingURL=Worker.d.ts.map
|
package/dist/src/Worker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Worker.d.ts","sourceRoot":"","sources":["../../src/Worker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,sBAAsB,EACtB,yBAAyB,EACzB,uBAAuB,EACvB,iBAAiB,EACjB,cAAc,EAGd,YAAY,EACZ,gBAAgB,EAEhB,MAAM,EACN,UAAU,EACV,UAAU,EACX,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"Worker.d.ts","sourceRoot":"","sources":["../../src/Worker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,sBAAsB,EACtB,yBAAyB,EACzB,uBAAuB,EACvB,iBAAiB,EACjB,cAAc,EAGd,YAAY,EACZ,gBAAgB,EAEhB,MAAM,EACN,UAAU,EACV,UAAU,EACX,MAAM,eAAe,CAAC;AAUvB,kDAAkD;AAClD,eAAO,MAAM,YAAY,GAAI,KAAK,EAAE,MAAM,gBAC1B,UAAU,CAAC,MAAM,KAC9B,MAAM,CAAC,KAAK,EAAE,MAAM,CAAuB,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,iCAAiC,QAAO,IAiBpD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,KAAK,EAAE,MAAM,GAAG,KAAK,sBAClC,UAAU,CAAC,YAAY,KAC1C,YAAY,CAAC,KAAK,EAAE,MAAM,CAM5B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,EAAE,MAAM,GAAG,KAAK,OAAK,cAAc,CAC3E,KAAK,EACL,MAAM,CAcP,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,iBACuB,CAAC;AAExD,6EAA6E;AAC7E,eAAO,MAAM,sBAAsB,EAAE,sBAwCpC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,EAAE,MAAM,GAAG,KAAK,cACxC,0BAA0B,KACrC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAoC,CAAC;AAEhE;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GAAI,KAAK,EAAE,MAAM,GAAG,KAAK,cAC9C,uBAAuB,KAClC,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAoBhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oCAAoC,GAAI,KAAK,EAAE,MAAM,GAAG,KAAK,cAC5D,0BAA0B,KACrC,gBAAgB,CAAC,KAAK,EAAE,MAAM,CA2EhC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,wBAAwB,UAC5B,WAAW,YACR,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,KAC/B,UAsBF,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,gBAAgB,QAAO,UAAU,GAC5C,yBAAyB,GACzB,uBAWD,CAAC"}
|
package/dist/src/Worker.js
CHANGED
|
@@ -50,7 +50,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
50
|
var e = new Error(message);
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
|
-
import { assertNonNullable, createConsole, createConsoleStoreOutput, disposable, } from "@evolu/common";
|
|
53
|
+
import { assertNonNullable, createConsole, createConsoleStoreOutput, defectToError, disposable, } from "@evolu/common";
|
|
54
54
|
/** Creates a {@link Worker} from a Web Worker. */
|
|
55
55
|
export const createWorker = (nativeWorker) => wrap(nativeWorker);
|
|
56
56
|
/**
|
|
@@ -176,7 +176,9 @@ export const createWorkerSelf = (nativeSelf) => wrap(nativeSelf);
|
|
|
176
176
|
* Creates an Evolu {@link SharedWorkerSelf} from a Web `SharedWorkerGlobalScope`
|
|
177
177
|
* (`self` inside a shared worker).
|
|
178
178
|
*
|
|
179
|
-
* Disposing closes the shared worker scope for all connected clients.
|
|
179
|
+
* Disposing closes the shared worker scope for all connected clients. Browsers
|
|
180
|
+
* never pass a shared worker's uncaught errors to its pages; see
|
|
181
|
+
* {@link addUncaughtErrorListener}.
|
|
180
182
|
*/
|
|
181
183
|
export const createSharedWorkerSelf = (nativeSelf) => {
|
|
182
184
|
const self = {
|
|
@@ -285,6 +287,63 @@ export const createOneTabSharedWorkerSelfPolyfill = (nativeSelf) => {
|
|
|
285
287
|
__disposeResources(env_3);
|
|
286
288
|
}
|
|
287
289
|
};
|
|
290
|
+
/**
|
|
291
|
+
* Passes each uncaught error and unhandled rejection of a worker global scope
|
|
292
|
+
* to `listener` as an `Error` from {@link defectToError}.
|
|
293
|
+
*
|
|
294
|
+
* Browsers never pass a shared worker's uncaught errors to a page's error
|
|
295
|
+
* handlers, and no worker passes its unhandled rejections to its page. A shared
|
|
296
|
+
* worker calls this with its global scope, `self`, to send them to its pages,
|
|
297
|
+
* for example over a BroadcastChannel. Defects of a Run from {@link createRun}
|
|
298
|
+
* arrive too, because it reports them with `reportError`, which dispatches
|
|
299
|
+
* "error" on the global scope.
|
|
300
|
+
*
|
|
301
|
+
* The events stay uncanceled, so the browser still logs them, and a dedicated
|
|
302
|
+
* worker still passes its uncaught errors to its page. The listeners stay until
|
|
303
|
+
* disposal, so they also receive errors after a Run's panic.
|
|
304
|
+
*
|
|
305
|
+
* ### Example
|
|
306
|
+
*
|
|
307
|
+
* ```ts
|
|
308
|
+
* import { assertEqual } from "@evolu/common";
|
|
309
|
+
* import { addUncaughtErrorListener } from "@evolu/web";
|
|
310
|
+
*
|
|
311
|
+
* const messages: Array<string> = [];
|
|
312
|
+
* // A worker passes its global scope, `self`.
|
|
313
|
+
* const scope = new EventTarget();
|
|
314
|
+
* using _listener = addUncaughtErrorListener(scope, (error) => {
|
|
315
|
+
* messages.push(error.message);
|
|
316
|
+
* });
|
|
317
|
+
*
|
|
318
|
+
* scope.dispatchEvent(
|
|
319
|
+
* Object.assign(new Event("error"), { error: new Error("unexpected") }),
|
|
320
|
+
* );
|
|
321
|
+
* assertEqual(messages, ["unexpected"]);
|
|
322
|
+
* ```
|
|
323
|
+
*/
|
|
324
|
+
export const addUncaughtErrorListener = (scope, listener) => {
|
|
325
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
326
|
+
try {
|
|
327
|
+
const disposer = __addDisposableResource(env_4, new DisposableStack(), false);
|
|
328
|
+
const { signal } = disposer.adopt(new AbortController(), (controller) => {
|
|
329
|
+
controller.abort();
|
|
330
|
+
});
|
|
331
|
+
scope.addEventListener("error", (event) => {
|
|
332
|
+
listener(defectToError(event.error));
|
|
333
|
+
}, { signal });
|
|
334
|
+
scope.addEventListener("unhandledrejection", (event) => {
|
|
335
|
+
listener(defectToError(event.reason));
|
|
336
|
+
}, { signal });
|
|
337
|
+
return disposable({}, disposer);
|
|
338
|
+
}
|
|
339
|
+
catch (e_4) {
|
|
340
|
+
env_4.error = e_4;
|
|
341
|
+
env_4.hasError = true;
|
|
342
|
+
}
|
|
343
|
+
finally {
|
|
344
|
+
__disposeResources(env_4);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
288
347
|
/** Creates deps shared by web worker entry points. */
|
|
289
348
|
export const createWorkerDeps = () => {
|
|
290
349
|
const consoleStoreOutput = createConsoleStoreOutput();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ConsoleDep } from "@evolu/common";
|
|
1
|
+
import { type ConsoleDep, type ReloadAppDep } from "@evolu/common";
|
|
2
2
|
import type { EvoluDeps } from "@evolu/common/local-first";
|
|
3
3
|
export interface SharedWorkerUnsupported {
|
|
4
4
|
readonly type: "SharedWorkerUnsupported";
|
|
@@ -6,6 +6,50 @@ export interface SharedWorkerUnsupported {
|
|
|
6
6
|
export interface SharedWorkerUnsupportedDep {
|
|
7
7
|
readonly onSharedWorkerUnsupported: () => void;
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
export interface StorageUnavailableDep {
|
|
10
|
+
/**
|
|
11
|
+
* Called when the browser offers no persistent storage, as in Safari's
|
|
12
|
+
* Private Browsing, so every database is kept in memory. See Storage in the
|
|
13
|
+
* Shared module of `@evolu/common`.
|
|
14
|
+
*
|
|
15
|
+
* The app keeps working. Data synced with a relay comes back, as on a new
|
|
16
|
+
* device, and nothing stays on the device once the tabs close. That suits
|
|
17
|
+
* someone checking their app on a borrowed phone, so a message such as
|
|
18
|
+
* "Nothing from this session is kept on this device." tells the user what to
|
|
19
|
+
* expect. Data that exists only locally, or has not synced yet, is lost when
|
|
20
|
+
* the tab hosting the database closes or navigates away, even while other
|
|
21
|
+
* tabs stay open.
|
|
22
|
+
*/
|
|
23
|
+
readonly onStorageUnavailable: () => void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Creates Evolu dependencies for the web platform.
|
|
27
|
+
*
|
|
28
|
+
* When another build of the app waits for this one, this tab reloads to load
|
|
29
|
+
* the build the server now serves: at once if the user is not in the tab,
|
|
30
|
+
* otherwise once they leave it. See Builds in the Shared module of
|
|
31
|
+
* `@evolu/common`. A tab whose database refuses to start with
|
|
32
|
+
* {@link UnsupportedDbVersionError} reloads once for each stored version, even
|
|
33
|
+
* while the user is in it, because the server may now serve a build that
|
|
34
|
+
* supports the database. The error is reported when the reloaded build refuses
|
|
35
|
+
* it too, or when the tab has no session storage. A tab whose worker was
|
|
36
|
+
* relaunched without its state, as WebKit does when the process hosting it
|
|
37
|
+
* ends, also reloads at once.
|
|
38
|
+
*
|
|
39
|
+
* When the page enters the browser's back-forward cache, as Safari does on
|
|
40
|
+
* every navigation away, this tab ends its part as if it closed: it stops the
|
|
41
|
+
* database workers it hosts, so another tab takes them over, and it reloads if
|
|
42
|
+
* the user comes back to it.
|
|
43
|
+
*
|
|
44
|
+
* Where the browser offers no persistent storage, as in Safari's Private
|
|
45
|
+
* Browsing, the database is kept in memory, and
|
|
46
|
+
* {@link StorageUnavailableDep.onStorageUnavailable} lets the app tell the
|
|
47
|
+
* user.
|
|
48
|
+
*
|
|
49
|
+
* A custom {@link ReloadApp} replaces the default page reload, for example to
|
|
50
|
+
* save state first. It should end by reloading the page, because the other
|
|
51
|
+
* build waits until this tab reloads or closes, and a page restored from the
|
|
52
|
+
* back-forward cache cannot work until it reloads.
|
|
53
|
+
*/
|
|
54
|
+
export declare const createEvoluDeps: (deps?: Partial<ConsoleDep> & Partial<ReloadAppDep> & Partial<SharedWorkerUnsupportedDep> & Partial<StorageUnavailableDep>) => EvoluDeps;
|
|
11
55
|
//# sourceMappingURL=Evolu.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Evolu.d.ts","sourceRoot":"","sources":["../../../src/local-first/Evolu.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"Evolu.d.ts","sourceRoot":"","sources":["../../../src/local-first/Evolu.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,UAAU,EAEf,KAAK,YAAY,EAElB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAIV,SAAS,EAIV,MAAM,2BAA2B,CAAC;AAgBnC,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;CAC1C;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,yBAAyB,EAAE,MAAM,IAAI,CAAC;CAChD;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,oBAAoB,EAAE,MAAM,IAAI,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,eAAe,UACpB,OAAO,CAAC,UAAU,CAAC,GACvB,OAAO,CAAC,YAAY,CAAC,GACrB,OAAO,CAAC,0BAA0B,CAAC,GACnC,OAAO,CAAC,qBAAqB,CAAC,KAC/B,SAiPF,CAAC"}
|