@evolu/common 7.4.0 → 8.0.0-next.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 -61
- package/dist/src/Array.d.ts +343 -102
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +181 -85
- package/dist/src/Assert.d.ts +57 -11
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +47 -11
- package/dist/src/BigInt.d.ts +6 -1
- package/dist/src/BigInt.d.ts.map +1 -1
- package/dist/src/BigInt.js +5 -0
- package/dist/src/Brand.d.ts +40 -12
- package/dist/src/Brand.d.ts.map +1 -1
- package/dist/src/Brand.js +5 -0
- package/dist/src/Buffer.d.ts +6 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +7 -2
- package/dist/src/Cache.d.ts +10 -2
- package/dist/src/Cache.d.ts.map +1 -1
- package/dist/src/Cache.js +6 -0
- package/dist/src/Callbacks.d.ts +10 -7
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +23 -13
- package/dist/src/Console.d.ts +361 -69
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +217 -117
- package/dist/src/Crypto.d.ts +70 -21
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +72 -27
- package/dist/src/Eq.d.ts +22 -2
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +21 -1
- package/dist/src/Error.d.ts +23 -12
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +27 -11
- package/dist/src/Function.d.ts +128 -56
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +118 -14
- package/dist/src/Identicon.d.ts +5 -0
- package/dist/src/Identicon.d.ts.map +1 -1
- package/dist/src/Identicon.js +6 -1
- package/dist/src/Lookup.d.ts +160 -0
- package/dist/src/Lookup.d.ts.map +1 -0
- package/dist/src/Lookup.js +192 -0
- package/dist/src/Microtask.d.ts +21 -0
- package/dist/src/Microtask.d.ts.map +1 -0
- package/dist/src/Microtask.js +37 -0
- package/dist/src/Number.d.ts +18 -4
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +23 -1
- package/dist/src/Object.d.ts +110 -5
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +108 -5
- package/dist/src/Option.d.ts +58 -0
- package/dist/src/Option.d.ts.map +1 -0
- package/dist/src/Option.js +43 -0
- package/dist/src/Order.d.ts +5 -0
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +8 -3
- package/dist/src/Platform.d.ts +36 -2
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +10 -6
- package/dist/src/Polyfills.d.ts +27 -0
- package/dist/src/Polyfills.d.ts.map +1 -0
- package/dist/src/Polyfills.js +299 -0
- package/dist/src/Random.d.ts +18 -7
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +9 -7
- package/dist/src/Redacted.d.ts +7 -4
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +5 -0
- package/dist/src/Ref.d.ts +31 -16
- package/dist/src/Ref.d.ts.map +1 -1
- package/dist/src/Ref.js +35 -7
- package/dist/src/RefCount.d.ts +62 -0
- package/dist/src/RefCount.d.ts.map +1 -0
- package/dist/src/RefCount.js +83 -0
- package/dist/src/Relation.d.ts +84 -29
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Relation.js +83 -105
- package/dist/src/Resource.d.ts +263 -0
- package/dist/src/Resource.d.ts.map +1 -0
- package/dist/src/Resource.js +389 -0
- package/dist/src/Result.d.ts +390 -374
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +142 -70
- package/dist/src/Schedule.d.ts +953 -0
- package/dist/src/Schedule.d.ts.map +1 -0
- package/dist/src/Schedule.js +1199 -0
- package/dist/src/Set.d.ts +181 -0
- package/dist/src/Set.d.ts.map +1 -0
- package/dist/src/Set.js +137 -0
- package/dist/src/Skiplist.d.ts +6 -1
- package/dist/src/Skiplist.d.ts.map +1 -1
- package/dist/src/Skiplist.js +5 -0
- package/dist/src/Sqlite.d.ts +160 -60
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +290 -190
- package/dist/src/Store.d.ts +36 -28
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +55 -15
- package/dist/src/String.d.ts +5 -0
- package/dist/src/String.d.ts.map +1 -1
- package/dist/src/String.js +5 -0
- package/dist/src/Task.d.ts +2263 -423
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1488 -348
- package/dist/src/Test.d.ts +122 -0
- package/dist/src/Test.d.ts.map +1 -0
- package/dist/src/Test.js +66 -0
- package/dist/src/Time.d.ts +201 -48
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +190 -86
- package/dist/src/Tracer.d.ts +48 -0
- package/dist/src/Tracer.d.ts.map +1 -0
- package/dist/src/Tracer.js +6 -0
- package/dist/src/Type.d.ts +582 -209
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +362 -240
- package/dist/src/Types.d.ts +160 -6
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/Types.js +22 -2
- package/dist/src/WebSocket.d.ts +113 -58
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +210 -118
- package/dist/src/Worker.d.ts +245 -60
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +220 -64
- package/dist/src/index.d.ts +25 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +19 -3
- package/dist/src/local-first/Db.d.ts +24 -213
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +622 -293
- package/dist/src/local-first/Error.d.ts +12 -0
- package/dist/src/local-first/Error.d.ts.map +1 -0
- package/dist/src/local-first/Error.js +6 -0
- package/dist/src/local-first/Evolu.d.ts +330 -285
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +472 -402
- package/dist/src/local-first/LocalAuth.d.ts +10 -10
- package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
- package/dist/src/local-first/LocalAuth.js +6 -1
- package/dist/src/local-first/Owner.d.ts +70 -27
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +27 -14
- package/dist/src/local-first/Protocol.d.ts +58 -50
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +253 -155
- package/dist/src/local-first/Query.d.ts +133 -43
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +139 -90
- package/dist/src/local-first/Relay.d.ts +18 -38
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +59 -171
- package/dist/src/local-first/Schema.d.ts +139 -144
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +78 -163
- package/dist/src/local-first/Shared.d.ts +125 -0
- package/dist/src/local-first/Shared.d.ts.map +1 -0
- package/dist/src/local-first/Shared.js +569 -0
- package/dist/src/local-first/Storage.d.ts +50 -53
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +121 -173
- package/dist/src/local-first/Timestamp.d.ts +28 -35
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +24 -25
- package/dist/src/local-first/index.d.ts +2 -15
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +2 -15
- package/package.json +36 -26
- package/src/Array.ts +467 -106
- package/src/Assert.ts +87 -12
- package/src/BigInt.ts +7 -1
- package/src/Brand.ts +41 -12
- package/src/Buffer.ts +10 -4
- package/src/Cache.ts +11 -2
- package/src/Callbacks.ts +33 -23
- package/src/Console.ts +567 -178
- package/src/Crypto.ts +111 -62
- package/src/Eq.ts +30 -3
- package/src/Error.ts +50 -34
- package/src/Function.ts +140 -68
- package/src/Identicon.ts +7 -1
- package/src/Lookup.ts +415 -0
- package/src/Microtask.ts +58 -0
- package/src/Number.ts +40 -7
- package/src/Object.ts +155 -16
- package/src/Option.ts +74 -0
- package/src/Order.ts +11 -3
- package/src/Platform.ts +43 -7
- package/src/Polyfills.ts +465 -0
- package/src/Random.ts +25 -11
- package/src/Redacted.ts +8 -4
- package/src/Ref.ts +72 -21
- package/src/RefCount.ts +170 -0
- package/src/Relation.ts +188 -136
- package/src/Resource.ts +864 -0
- package/src/Result.ts +548 -381
- package/src/Schedule.ts +1471 -0
- package/src/Set.ts +247 -0
- package/src/Skiplist.ts +7 -1
- package/src/Sqlite.ts +409 -304
- package/src/Store.ts +94 -48
- package/src/String.ts +6 -0
- package/src/Task.ts +3685 -740
- package/src/Test.ts +162 -0
- package/src/Time.ts +348 -145
- package/src/Tracer.ts +54 -0
- package/src/Type.ts +1064 -630
- package/src/Types.ts +202 -7
- package/src/WebSocket.ts +311 -178
- package/src/Worker.ts +527 -147
- package/src/index.ts +74 -3
- package/src/local-first/Db.ts +871 -597
- package/src/local-first/Error.ts +17 -0
- package/src/local-first/Evolu.ts +884 -823
- package/src/local-first/LocalAuth.ts +10 -10
- package/src/local-first/Owner.ts +94 -42
- package/src/local-first/Protocol.ts +440 -303
- package/src/local-first/Query.ts +243 -194
- package/src/local-first/Relay.ts +91 -252
- package/src/local-first/Schema.ts +284 -394
- package/src/local-first/Shared.ts +932 -0
- package/src/local-first/Storage.ts +209 -282
- package/src/local-first/Timestamp.ts +38 -45
- package/src/local-first/index.ts +2 -16
- package/LICENSE +0 -21
- package/dist/src/Instances.d.ts +0 -34
- package/dist/src/Instances.d.ts.map +0 -1
- package/dist/src/Instances.js +0 -44
- package/dist/src/Resources.d.ts +0 -118
- package/dist/src/Resources.d.ts.map +0 -1
- package/dist/src/Resources.js +0 -197
- package/dist/src/local-first/Platform.d.ts +0 -25
- package/dist/src/local-first/Platform.d.ts.map +0 -1
- package/dist/src/local-first/Platform.js +0 -1
- package/dist/src/local-first/Public.d.ts +0 -22
- package/dist/src/local-first/Public.d.ts.map +0 -1
- package/dist/src/local-first/Public.js +0 -15
- package/dist/src/local-first/PublicKysely.d.ts +0 -148
- package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
- package/dist/src/local-first/PublicKysely.js +0 -184
- package/dist/src/local-first/Sync.d.ts +0 -112
- package/dist/src/local-first/Sync.d.ts.map +0 -1
- package/dist/src/local-first/Sync.js +0 -529
- package/src/Instances.ts +0 -90
- package/src/Resources.ts +0 -367
- package/src/local-first/Platform.ts +0 -27
- package/src/local-first/Public.ts +0 -42
- package/src/local-first/PublicKysely.ts +0 -239
- package/src/local-first/Sync.ts +0 -960
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-agnostic Evolu SharedWorker.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
7
|
+
if (value !== null && value !== void 0) {
|
|
8
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
9
|
+
var dispose, inner;
|
|
10
|
+
if (async) {
|
|
11
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
12
|
+
dispose = value[Symbol.asyncDispose];
|
|
13
|
+
}
|
|
14
|
+
if (dispose === void 0) {
|
|
15
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
16
|
+
dispose = value[Symbol.dispose];
|
|
17
|
+
if (async) inner = dispose;
|
|
18
|
+
}
|
|
19
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
20
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
21
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
22
|
+
}
|
|
23
|
+
else if (async) {
|
|
24
|
+
env.stack.push({ async: true });
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
29
|
+
return function (env) {
|
|
30
|
+
function fail(e) {
|
|
31
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
32
|
+
env.hasError = true;
|
|
33
|
+
}
|
|
34
|
+
var r, s = 0;
|
|
35
|
+
function next() {
|
|
36
|
+
while (r = env.stack.pop()) {
|
|
37
|
+
try {
|
|
38
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
39
|
+
if (r.dispose) {
|
|
40
|
+
var result = r.dispose.call(r.value);
|
|
41
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
42
|
+
}
|
|
43
|
+
else s |= 1;
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
fail(e);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
50
|
+
if (env.hasError) throw env.error;
|
|
51
|
+
}
|
|
52
|
+
return next();
|
|
53
|
+
};
|
|
54
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
55
|
+
var e = new Error(message);
|
|
56
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
57
|
+
});
|
|
58
|
+
import { emptyArray, firstInArray, isNonEmptyArray, shiftFromArray, } from "../Array.js";
|
|
59
|
+
import { assert, assertNotDisposed } from "../Assert.js";
|
|
60
|
+
import { createCallbacks } from "../Callbacks.js";
|
|
61
|
+
import { exhaustiveCheck } from "../Function.js";
|
|
62
|
+
import { structuralLookup } from "../Lookup.js";
|
|
63
|
+
import { createRefCountByKey } from "../RefCount.js";
|
|
64
|
+
import { createSharedResourceByKey, createSharedResourceByKeyWithClaims, } from "../Resource.js";
|
|
65
|
+
import { ok } from "../Result.js";
|
|
66
|
+
import { spaced } from "../Schedule.js";
|
|
67
|
+
import { repeat, unabortable, } from "../Task.js";
|
|
68
|
+
import { createId } from "../Type.js";
|
|
69
|
+
import { createProtocolMessageForUnsubscribe, createProtocolMessageFromCrdtMessages, parseProtocolHeader, } from "./Protocol.js";
|
|
70
|
+
import { makePatches, } from "./Query.js";
|
|
71
|
+
export const initSharedWorker = (self) => async (run) => {
|
|
72
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
73
|
+
try {
|
|
74
|
+
const { createMessagePort, consoleStoreOutputEntry, createWebSocket } = run.deps;
|
|
75
|
+
const console = run.deps.console.child("SharedWorker");
|
|
76
|
+
const stack = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
|
|
77
|
+
const sharedWorkerReady = Promise.withResolvers();
|
|
78
|
+
// TODO: Use heartbeat to detect and prune dead ports.
|
|
79
|
+
const tabPorts = new Set();
|
|
80
|
+
// Buffer console/error outputs until the first tab connects its port.
|
|
81
|
+
const queuedTabOutputs = [];
|
|
82
|
+
const postTabOutput = (output) => {
|
|
83
|
+
if (tabPorts.size === 0)
|
|
84
|
+
queuedTabOutputs.push(output);
|
|
85
|
+
else
|
|
86
|
+
for (const port of tabPorts)
|
|
87
|
+
port.postMessage(output);
|
|
88
|
+
};
|
|
89
|
+
stack.defer(consoleStoreOutputEntry.subscribe(() => {
|
|
90
|
+
const entry = consoleStoreOutputEntry.get();
|
|
91
|
+
if (entry)
|
|
92
|
+
postTabOutput({ type: "OnConsoleEntry", entry });
|
|
93
|
+
}));
|
|
94
|
+
// Register ASAP so the worker does not miss connections.
|
|
95
|
+
self.onConnect = (port) => {
|
|
96
|
+
void sharedWorkerReady.promise.then(() => {
|
|
97
|
+
// The underlying port buffers messages until onMessage is assigned.
|
|
98
|
+
port.onMessage = (message) => {
|
|
99
|
+
switch (message.type) {
|
|
100
|
+
case "InitTab": {
|
|
101
|
+
/**
|
|
102
|
+
* One SharedWorker serves multiple tabs. The most recently
|
|
103
|
+
* initialized tab's level wins.
|
|
104
|
+
*/
|
|
105
|
+
console.setLevel(message.consoleLevel);
|
|
106
|
+
const tabPort = createMessagePort(message.port);
|
|
107
|
+
tabPorts.add(tabPort);
|
|
108
|
+
if (queuedTabOutputs.length > 0) {
|
|
109
|
+
queuedTabOutputs.forEach(postTabOutput);
|
|
110
|
+
queuedTabOutputs.length = 0;
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case "CreateEvolu": {
|
|
115
|
+
void sharedWorkerRunWithSharedEvoluDeps(async (run) => {
|
|
116
|
+
const sharedEvoluResult = await run(sharedEvolusByName.acquire(message.name));
|
|
117
|
+
if (!sharedEvoluResult.ok)
|
|
118
|
+
return sharedEvoluResult;
|
|
119
|
+
sharedEvoluResult.value.createInstance(message.evoluPort, message.dbWorkerPort, () => void sharedWorkerRunWithSharedEvoluDeps(sharedEvolusByName.release(message.name)));
|
|
120
|
+
return ok();
|
|
121
|
+
});
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
default:
|
|
125
|
+
console.error("Unknown shared worker input", message);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
const initSharedWorkerRun = run.create();
|
|
131
|
+
const transports = stack.use(await initSharedWorkerRun.orThrow(createSharedResourceByKeyWithClaims((transport) => createWebSocket(transport.url, {
|
|
132
|
+
binaryType: "arraybuffer",
|
|
133
|
+
onOpen: () => {
|
|
134
|
+
const ownerIds = transports.getClaimsForResource(transport);
|
|
135
|
+
console.debug("transportOpen", {
|
|
136
|
+
url: transport.url,
|
|
137
|
+
ownerIds: [...ownerIds],
|
|
138
|
+
});
|
|
139
|
+
forEachSharedEvolu((sharedEvolu) => {
|
|
140
|
+
sharedEvolu.requestCreateSyncMessages(ownerIds);
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
onMessage(data) {
|
|
144
|
+
if (!(data instanceof ArrayBuffer))
|
|
145
|
+
return;
|
|
146
|
+
const inputMessage = new globalThis.Uint8Array(data);
|
|
147
|
+
const headerResult = parseProtocolHeader(new globalThis.Uint8Array(data));
|
|
148
|
+
if (!headerResult.ok) {
|
|
149
|
+
console.debug("transportInvalidProtocolMessage", {
|
|
150
|
+
url: transport.url,
|
|
151
|
+
byteLength: inputMessage.byteLength,
|
|
152
|
+
});
|
|
153
|
+
// TODO: Propagate invalid protocol messages to sync state.
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
console.debug("transportProtocolMessage", {
|
|
157
|
+
url: transport.url,
|
|
158
|
+
ownerId: headerResult.value.ownerId,
|
|
159
|
+
byteLength: inputMessage.byteLength,
|
|
160
|
+
});
|
|
161
|
+
forEachSharedEvolu((sharedEvolu) => {
|
|
162
|
+
sharedEvolu.requestApplySyncMessage(headerResult.value.ownerId, inputMessage);
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
}), {
|
|
166
|
+
onFirstClaimAdded: (ownerId, webSocket) => {
|
|
167
|
+
if (!webSocket.isOpen())
|
|
168
|
+
return;
|
|
169
|
+
forEachSharedEvolu((sharedEvolu) => {
|
|
170
|
+
sharedEvolu.requestCreateSyncMessages(new Set([ownerId]));
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
onLastClaimRemoved: (ownerId, webSocket) => {
|
|
174
|
+
webSocket.send(createProtocolMessageForUnsubscribe(ownerId));
|
|
175
|
+
},
|
|
176
|
+
// Keep sockets alive briefly across short owner churn.
|
|
177
|
+
idleDisposeAfter: "3s",
|
|
178
|
+
resourceLookup: structuralLookup,
|
|
179
|
+
})));
|
|
180
|
+
const sharedWorkerRunWithSharedEvoluDeps = initSharedWorkerRun.addDeps({
|
|
181
|
+
transports,
|
|
182
|
+
postTabOutput,
|
|
183
|
+
});
|
|
184
|
+
const sharedEvolusByName = stack.use(await sharedWorkerRunWithSharedEvoluDeps.orThrow(createSharedResourceByKey(createSharedEvolu, {
|
|
185
|
+
lookup: structuralLookup,
|
|
186
|
+
})));
|
|
187
|
+
const forEachSharedEvolu = (callback) => {
|
|
188
|
+
for (const sharedEvolu of sharedEvolusByName
|
|
189
|
+
.snapshot()
|
|
190
|
+
.resourcesByKey.values()) {
|
|
191
|
+
callback(sharedEvolu);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
sharedWorkerReady.resolve();
|
|
195
|
+
console.info("initSharedWorker");
|
|
196
|
+
return ok(stack.move());
|
|
197
|
+
}
|
|
198
|
+
catch (e_1) {
|
|
199
|
+
env_1.error = e_1;
|
|
200
|
+
env_1.hasError = true;
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
const result_1 = __disposeResources(env_1);
|
|
204
|
+
if (result_1)
|
|
205
|
+
await result_1;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
const createSharedEvolu = (name) => (run) => {
|
|
209
|
+
const stack = new AsyncDisposableStack();
|
|
210
|
+
const sharedEvoluRun = run.create();
|
|
211
|
+
const console = run.deps.console.child(name).child("SharedWorker");
|
|
212
|
+
const { createMessagePort, postTabOutput, transports } = run.deps;
|
|
213
|
+
const evoluInstancesByPortId = new Map();
|
|
214
|
+
const queue = [];
|
|
215
|
+
const callbacks = stack.use(createCallbacks(run.deps));
|
|
216
|
+
let leaderDbWorkerPort = null;
|
|
217
|
+
let queueProcessingFiber = null;
|
|
218
|
+
stack.use(sharedEvoluRun);
|
|
219
|
+
const moved = stack.move();
|
|
220
|
+
const ensureQueueProcessing = () => {
|
|
221
|
+
if (queueProcessingFiber ||
|
|
222
|
+
!isNonEmptyArray(queue) ||
|
|
223
|
+
!leaderDbWorkerPort) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const first = firstInArray(queue);
|
|
227
|
+
const callbackId = callbacks.register(({ response }) => {
|
|
228
|
+
queueProcessingFiber?.abort();
|
|
229
|
+
queueProcessingFiber = null;
|
|
230
|
+
switch (response.type) {
|
|
231
|
+
case "ForEvolu": {
|
|
232
|
+
handleResponseForEvolu(response, first);
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
case "ForSharedWorker":
|
|
236
|
+
handleResponseForSharedWorker(response);
|
|
237
|
+
break;
|
|
238
|
+
default:
|
|
239
|
+
exhaustiveCheck(response);
|
|
240
|
+
}
|
|
241
|
+
// Complete the current queue item and continue with the next one.
|
|
242
|
+
shiftFromArray(queue);
|
|
243
|
+
ensureQueueProcessing();
|
|
244
|
+
});
|
|
245
|
+
/**
|
|
246
|
+
* The leader DbWorker can disappear at any time when its tab closes, so
|
|
247
|
+
* keep resending the current request until some leader answers. The
|
|
248
|
+
* response handler aborts this retry loop and advances the queue.
|
|
249
|
+
*/
|
|
250
|
+
queueProcessingFiber = sharedEvoluRun.daemon(repeat(() => {
|
|
251
|
+
assert(leaderDbWorkerPort, "Expected a leader DbWorker");
|
|
252
|
+
leaderDbWorkerPort.postMessage({ callbackId, request: first });
|
|
253
|
+
return ok();
|
|
254
|
+
}, spaced("5s")));
|
|
255
|
+
};
|
|
256
|
+
const handleResponseForEvolu = (response, first) => {
|
|
257
|
+
const instance = evoluInstancesByPortId.get(response.evoluPortId);
|
|
258
|
+
const port = instance?.evoluPort;
|
|
259
|
+
if (!port)
|
|
260
|
+
return;
|
|
261
|
+
switch (response.message.type) {
|
|
262
|
+
case "Mutate":
|
|
263
|
+
case "Query": {
|
|
264
|
+
const previousRowsByQuery = instance.rowsByQuery;
|
|
265
|
+
const nextRowsByQuery = new Map(previousRowsByQuery);
|
|
266
|
+
const patchesByQuery = new Map();
|
|
267
|
+
for (const [query, rows] of response.message.rowsByQuery) {
|
|
268
|
+
nextRowsByQuery.set(query, rows);
|
|
269
|
+
patchesByQuery.set(query, makePatches(previousRowsByQuery.get(query), rows));
|
|
270
|
+
}
|
|
271
|
+
instance.rowsByQuery = nextRowsByQuery;
|
|
272
|
+
instance.evoluPort.postMessage({
|
|
273
|
+
type: "OnPatchesByQuery",
|
|
274
|
+
patchesByQuery,
|
|
275
|
+
onCompleteIds: first.message.type === "Mutate"
|
|
276
|
+
? first.message.onCompleteIds
|
|
277
|
+
: emptyArray,
|
|
278
|
+
});
|
|
279
|
+
if (response.message.type === "Mutate") {
|
|
280
|
+
const mutateResponse = response.message;
|
|
281
|
+
for (const [otherEvoluPortId, otherEvoluInstance,] of evoluInstancesByPortId) {
|
|
282
|
+
if (otherEvoluPortId === response.evoluPortId)
|
|
283
|
+
continue;
|
|
284
|
+
otherEvoluInstance.evoluPort.postMessage({
|
|
285
|
+
type: "RefreshQueries",
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
sharedEvoluRun((run) => {
|
|
289
|
+
const protocolMessagesByOwnerId = new Map();
|
|
290
|
+
for (const syncOwner of instance.usedSyncOwners.keys()) {
|
|
291
|
+
const { owner } = syncOwner;
|
|
292
|
+
const messages = mutateResponse.messagesByOwnerId.get(owner.id);
|
|
293
|
+
// Skip owners this instance does not currently sync for
|
|
294
|
+
// writing. Read-only owners cannot produce protocol
|
|
295
|
+
// messages because they do not have a write key.
|
|
296
|
+
if (!messages || !("writeKey" in owner))
|
|
297
|
+
continue;
|
|
298
|
+
protocolMessagesByOwnerId.set(owner.id, createProtocolMessageFromCrdtMessages(run.deps)(owner, messages));
|
|
299
|
+
}
|
|
300
|
+
sendProtocolMessagesByOwnerId(protocolMessagesByOwnerId);
|
|
301
|
+
return ok();
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
case "Export":
|
|
307
|
+
port.postMessage({ type: "OnExport", file: response.message.file }, [
|
|
308
|
+
response.message.file.buffer,
|
|
309
|
+
]);
|
|
310
|
+
break;
|
|
311
|
+
default:
|
|
312
|
+
exhaustiveCheck(response.message);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
const handleResponseForSharedWorker = (response) => {
|
|
316
|
+
switch (response.message.type) {
|
|
317
|
+
case "CreateSyncMessages":
|
|
318
|
+
sendProtocolMessagesByOwnerId(response.message.protocolMessagesByOwnerId);
|
|
319
|
+
break;
|
|
320
|
+
case "ApplySyncMessage":
|
|
321
|
+
if (response.message.didWriteMessages) {
|
|
322
|
+
refreshQueries();
|
|
323
|
+
}
|
|
324
|
+
if (!response.message.result.ok) {
|
|
325
|
+
if (response.message.result.error.type !== "AbortError") {
|
|
326
|
+
postTabOutput({
|
|
327
|
+
type: "OnError",
|
|
328
|
+
error: response.message.result.error,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
switch (response.message.result.value.type) {
|
|
334
|
+
case "Response":
|
|
335
|
+
sendProtocolMessagesByOwnerId(new Map([
|
|
336
|
+
[
|
|
337
|
+
response.message.ownerId,
|
|
338
|
+
response.message.result.value.message,
|
|
339
|
+
],
|
|
340
|
+
]));
|
|
341
|
+
break;
|
|
342
|
+
case "Broadcast":
|
|
343
|
+
case "NoResponse":
|
|
344
|
+
break;
|
|
345
|
+
default:
|
|
346
|
+
exhaustiveCheck(response.message.result.value);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
break;
|
|
350
|
+
default:
|
|
351
|
+
exhaustiveCheck(response.message);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
const sendProtocolMessagesByOwnerId = (protocolMessagesByOwnerId) => {
|
|
355
|
+
for (const [ownerId, protocolMessage] of protocolMessagesByOwnerId) {
|
|
356
|
+
for (const transport of transports.getResourceKeysForClaim(ownerId)) {
|
|
357
|
+
const webSocket = transports.getResource(transport);
|
|
358
|
+
if (!webSocket?.isOpen())
|
|
359
|
+
continue;
|
|
360
|
+
console.debug("sendProtocolMessage", {
|
|
361
|
+
ownerId,
|
|
362
|
+
url: transport.url,
|
|
363
|
+
byteLength: protocolMessage.byteLength,
|
|
364
|
+
});
|
|
365
|
+
webSocket.send(protocolMessage);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
const refreshQueries = () => {
|
|
370
|
+
for (const evoluInstance of evoluInstancesByPortId.values()) {
|
|
371
|
+
evoluInstance.evoluPort.postMessage({ type: "RefreshQueries" });
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
const getUsedOwnersById = (ownerIds) => {
|
|
375
|
+
const ownersById = new Map();
|
|
376
|
+
for (const evoluInstance of evoluInstancesByPortId.values()) {
|
|
377
|
+
for (const { owner } of evoluInstance.usedSyncOwners.keys()) {
|
|
378
|
+
if (!ownerIds.has(owner.id) || !("writeKey" in owner))
|
|
379
|
+
continue;
|
|
380
|
+
ownersById.set(owner.id, owner);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return ownersById;
|
|
384
|
+
};
|
|
385
|
+
const removeAllUsedSyncOwners = (evoluInstance) => unabortable(async (run) => {
|
|
386
|
+
for (const syncOwner of evoluInstance.usedSyncOwners.keys()) {
|
|
387
|
+
while (evoluInstance.usedSyncOwners.has(syncOwner)) {
|
|
388
|
+
await run(toggleUsedSyncOwner(evoluInstance, syncOwner, "remove"));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return ok();
|
|
392
|
+
});
|
|
393
|
+
const toggleUsedSyncOwner = (evoluInstance, syncOwner, action) => unabortable(async (run) => {
|
|
394
|
+
if (action === "add") {
|
|
395
|
+
evoluInstance.usedSyncOwners.increment(syncOwner);
|
|
396
|
+
await run(transports.addClaim(syncOwner.owner.id, syncOwner.transports));
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
evoluInstance.usedSyncOwners.decrement(syncOwner);
|
|
400
|
+
await run(transports.removeClaim(syncOwner.owner.id, syncOwner.transports));
|
|
401
|
+
}
|
|
402
|
+
return ok();
|
|
403
|
+
});
|
|
404
|
+
return ok({
|
|
405
|
+
createInstance: (nativeEvoluPort, nativeDbWorkerPort, releaseSharedEvolu) => {
|
|
406
|
+
assertNotDisposed(moved);
|
|
407
|
+
const evoluPort = createMessagePort(nativeEvoluPort);
|
|
408
|
+
const dbWorkerPort = createMessagePort(nativeDbWorkerPort);
|
|
409
|
+
const evoluPortId = createId(run.deps);
|
|
410
|
+
const evoluInstance = {
|
|
411
|
+
evoluPort,
|
|
412
|
+
dbWorkerPort,
|
|
413
|
+
releaseSharedEvolu,
|
|
414
|
+
rowsByQuery: new Map(),
|
|
415
|
+
usedSyncOwners: createRefCountByKey({
|
|
416
|
+
lookup: ({ owner: { id } }) => id,
|
|
417
|
+
}),
|
|
418
|
+
};
|
|
419
|
+
evoluInstancesByPortId.set(evoluPortId, evoluInstance);
|
|
420
|
+
dbWorkerPort.onMessage = (message) => {
|
|
421
|
+
switch (message.type) {
|
|
422
|
+
case "LeaderAcquired": {
|
|
423
|
+
leaderDbWorkerPort = evoluInstance.dbWorkerPort;
|
|
424
|
+
console.info("leaderAcquired");
|
|
425
|
+
ensureQueueProcessing();
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
case "OnQueuedResponse": {
|
|
429
|
+
callbacks.execute(message.callbackId, message);
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
case "OnConsoleEntry":
|
|
433
|
+
case "OnError": {
|
|
434
|
+
postTabOutput(message);
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
default:
|
|
438
|
+
exhaustiveCheck(message);
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
evoluPort.onMessage = (evoluMessage) => {
|
|
442
|
+
switch (evoluMessage.type) {
|
|
443
|
+
case "Query":
|
|
444
|
+
case "Export": {
|
|
445
|
+
queue.push({
|
|
446
|
+
type: "ForEvolu",
|
|
447
|
+
evoluPortId,
|
|
448
|
+
message: evoluMessage,
|
|
449
|
+
});
|
|
450
|
+
ensureQueueProcessing();
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
case "Mutate": {
|
|
454
|
+
// TODO: Delegate do vsech evolu instances, co to pouzivaji
|
|
455
|
+
queue.push({
|
|
456
|
+
type: "ForEvolu",
|
|
457
|
+
evoluPortId,
|
|
458
|
+
message: evoluMessage,
|
|
459
|
+
});
|
|
460
|
+
ensureQueueProcessing();
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
case "UseOwner": {
|
|
464
|
+
const evoluInstance = evoluInstancesByPortId.get(evoluPortId);
|
|
465
|
+
if (!evoluInstance)
|
|
466
|
+
break;
|
|
467
|
+
for (const { owner, action } of evoluMessage.actions) {
|
|
468
|
+
console.debug("useOwner", {
|
|
469
|
+
evoluPortId,
|
|
470
|
+
action,
|
|
471
|
+
ownerId: owner.owner.id,
|
|
472
|
+
transportUrls: owner.transports.map(({ url }) => url),
|
|
473
|
+
});
|
|
474
|
+
void sharedEvoluRun(toggleUsedSyncOwner(evoluInstance, owner, action));
|
|
475
|
+
}
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
case "Dispose": {
|
|
479
|
+
const evoluInstance = evoluInstancesByPortId.get(evoluPortId);
|
|
480
|
+
if (!evoluInstance)
|
|
481
|
+
break;
|
|
482
|
+
evoluInstancesByPortId.delete(evoluPortId);
|
|
483
|
+
const hadLastPort = evoluInstancesByPortId.size === 0;
|
|
484
|
+
console.info("evoluDispose", {
|
|
485
|
+
name,
|
|
486
|
+
evoluPortId,
|
|
487
|
+
hadLastPort,
|
|
488
|
+
});
|
|
489
|
+
// Potential plan: keep DbWorker ports in a SharedResource
|
|
490
|
+
// abstraction instead of deleting them eagerly here. DbWorkers use
|
|
491
|
+
// leader election because SQLite WASM needs a single active owner.
|
|
492
|
+
// When the last Evolu instance is disposed, broadcast shutdown to
|
|
493
|
+
// all DbWorkers so the current leader can dispose itself and the
|
|
494
|
+
// followers can clean up consistently.
|
|
495
|
+
sharedEvoluRun(async (run) => {
|
|
496
|
+
await run(removeAllUsedSyncOwners(evoluInstance));
|
|
497
|
+
evoluInstance.usedSyncOwners[Symbol.dispose]();
|
|
498
|
+
return ok();
|
|
499
|
+
});
|
|
500
|
+
// if (hadLastPort) evoluInstance.releaseSharedEvolu();
|
|
501
|
+
// TODO: Dispose SharedEvolu on the last port.
|
|
502
|
+
// TODO: Decided what to do with DbWorker but probably dispose it, but
|
|
503
|
+
// https://bugs.webkit.org/show_bug.cgi?id=301520
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
default:
|
|
507
|
+
exhaustiveCheck(evoluMessage);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
},
|
|
511
|
+
requestCreateSyncMessages: (ownerIds) => {
|
|
512
|
+
const ownersToSync = [...getUsedOwnersById(ownerIds).values()];
|
|
513
|
+
if (!isNonEmptyArray(ownersToSync))
|
|
514
|
+
return;
|
|
515
|
+
console.debug("requestCreateSyncMessages", {
|
|
516
|
+
ownerIds: ownersToSync.map(({ id }) => id),
|
|
517
|
+
});
|
|
518
|
+
queue.push({
|
|
519
|
+
type: "ForSharedWorker",
|
|
520
|
+
message: {
|
|
521
|
+
type: "CreateSyncMessages",
|
|
522
|
+
owners: ownersToSync,
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
ensureQueueProcessing();
|
|
526
|
+
},
|
|
527
|
+
requestApplySyncMessage: (ownerId, inputMessage) => {
|
|
528
|
+
const owner = getUsedOwnersById(new Set([ownerId])).get(ownerId);
|
|
529
|
+
if (!owner)
|
|
530
|
+
return;
|
|
531
|
+
console.debug("requestApplySyncMessage", {
|
|
532
|
+
ownerId,
|
|
533
|
+
byteLength: inputMessage.byteLength,
|
|
534
|
+
});
|
|
535
|
+
queue.push({
|
|
536
|
+
type: "ForSharedWorker",
|
|
537
|
+
message: {
|
|
538
|
+
type: "ApplySyncMessage",
|
|
539
|
+
owner,
|
|
540
|
+
inputMessage,
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
ensureQueueProcessing();
|
|
544
|
+
},
|
|
545
|
+
[Symbol.asyncDispose]: () => moved.disposeAsync(),
|
|
546
|
+
});
|
|
547
|
+
};
|
|
548
|
+
// | (Typed<"reset"> & {
|
|
549
|
+
// readonly onCompleteId: CallbackId;
|
|
550
|
+
// readonly reload: boolean;
|
|
551
|
+
// readonly restore?: {
|
|
552
|
+
// readonly sqliteSchema: SqliteSchema;
|
|
553
|
+
// readonly mnemonic: Mnemonic;
|
|
554
|
+
// };
|
|
555
|
+
// })
|
|
556
|
+
// | (Typed<"ensureSqliteSchema"> & {
|
|
557
|
+
// readonly sqliteSchema: SqliteSchema;
|
|
558
|
+
// })
|
|
559
|
+
// | (Typed<"export"> & {
|
|
560
|
+
// readonly onCompleteId: CallbackId;
|
|
561
|
+
// })
|
|
562
|
+
// | (Typed<"useOwner"> & {
|
|
563
|
+
// readonly use: boolean;
|
|
564
|
+
// readonly owner: SyncOwner;
|
|
565
|
+
// });
|
|
566
|
+
// | (Typed<"onReset"> & {
|
|
567
|
+
// readonly onCompleteId: CallbackId;
|
|
568
|
+
// readonly reload: boolean;
|
|
569
|
+
// })
|