@evolu/common 8.0.0-next.3 → 8.0.0-next.5
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/Assert.d.ts +10 -5
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +10 -5
- package/dist/src/Error.d.ts +5 -3
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +16 -0
- package/dist/src/Identicon.js +4 -4
- package/dist/src/LockManager.d.ts +74 -0
- package/dist/src/LockManager.d.ts.map +1 -0
- package/dist/src/LockManager.js +103 -0
- package/dist/src/Microtask.d.ts.map +1 -1
- package/dist/src/Microtask.js +90 -26
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/RefCount.d.ts.map +1 -1
- package/dist/src/RefCount.js +153 -68
- package/dist/src/Resource.d.ts +6 -6
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Resource.js +211 -162
- package/dist/src/Schedule.d.ts +5 -5
- package/dist/src/Schedule.d.ts.map +1 -1
- package/dist/src/Schedule.js +53 -44
- package/dist/src/Sqlite.d.ts +2 -0
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +66 -41
- package/dist/src/Task.d.ts +99 -123
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +273 -256
- package/dist/src/Test.d.ts +14 -15
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Time.d.ts +5 -0
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +5 -0
- package/dist/src/Type.d.ts +7 -0
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +7 -0
- package/dist/src/Types.d.ts +0 -8
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +7 -6
- package/dist/src/Worker.d.ts +42 -8
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +292 -112
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/local-first/Db.d.ts +5 -4
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +43 -37
- package/dist/src/local-first/Evolu.d.ts +3 -2
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +90 -80
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Shared.d.ts +65 -54
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +390 -376
- package/package.json +1 -1
- package/src/Array.ts +8 -8
- package/src/Assert.ts +11 -6
- package/src/Error.ts +5 -4
- package/src/Identicon.ts +4 -4
- package/src/LockManager.ts +181 -0
- package/src/Microtask.ts +17 -11
- package/src/Object.ts +3 -3
- package/src/RefCount.ts +25 -21
- package/src/Resource.ts +63 -56
- package/src/Schedule.ts +70 -48
- package/src/Set.ts +4 -4
- package/src/Sqlite.ts +43 -31
- package/src/Task.ts +392 -430
- package/src/Test.ts +14 -15
- package/src/Time.ts +7 -0
- package/src/Type.ts +9 -0
- package/src/Types.ts +0 -9
- package/src/WebSocket.ts +7 -6
- package/src/Worker.ts +195 -53
- package/src/index.ts +1 -0
- package/src/local-first/Db.ts +86 -61
- package/src/local-first/Evolu.ts +74 -68
- package/src/local-first/Owner.ts +1 -1
- package/src/local-first/Query.ts +1 -1
- package/src/local-first/Shared.ts +485 -476
|
@@ -56,67 +56,49 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
56
56
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
57
57
|
});
|
|
58
58
|
import { emptyArray, firstInArray, isNonEmptyArray, shiftFromArray, } from "../Array.js";
|
|
59
|
-
import { assert, assertNotDisposed } from "../Assert.js";
|
|
59
|
+
import { assert, assertNotAborted, assertNotDisposed } from "../Assert.js";
|
|
60
60
|
import { createCallbacks } from "../Callbacks.js";
|
|
61
|
-
import {
|
|
61
|
+
import { acquireLeaderLock } from "../LockManager.js";
|
|
62
62
|
import { structuralLookup } from "../Lookup.js";
|
|
63
63
|
import { createRefCountByKey } from "../RefCount.js";
|
|
64
64
|
import { createSharedResourceByKey, createSharedResourceByKeyWithClaims, } from "../Resource.js";
|
|
65
65
|
import { ok } from "../Result.js";
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
68
|
-
import {
|
|
66
|
+
import { createStore } from "../Store.js";
|
|
67
|
+
import { AbortError, createMutex, unabortable, } from "../Task.js";
|
|
68
|
+
import {} from "../Type.js";
|
|
69
69
|
import { createProtocolMessageForUnsubscribe, createProtocolMessageFromCrdtMessages, parseProtocolHeader, } from "./Protocol.js";
|
|
70
70
|
import { makePatches, } from "./Query.js";
|
|
71
|
+
export const consoleEntryOrErrorBroadcastChannelName = "evolu:console-entry-or-error";
|
|
71
72
|
export const initSharedWorker = (self) => async (run) => {
|
|
72
73
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
73
74
|
try {
|
|
74
|
-
const {
|
|
75
|
-
const console =
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
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);
|
|
75
|
+
const { deps } = run;
|
|
76
|
+
const console = deps.console.child("SharedWorker");
|
|
77
|
+
const disposer = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
|
|
78
|
+
const tabLeaderPortStore = disposer.use(createStore(null));
|
|
79
|
+
const consoleEntryOrErrorBroadcastChannel = disposer.use(deps.createBroadcastChannel(consoleEntryOrErrorBroadcastChannelName));
|
|
80
|
+
const postConsoleEntryOrError = (output) => {
|
|
81
|
+
consoleEntryOrErrorBroadcastChannel.postMessage(output);
|
|
88
82
|
};
|
|
89
|
-
|
|
90
|
-
const entry = consoleStoreOutputEntry.get();
|
|
91
|
-
if (entry)
|
|
92
|
-
postTabOutput({ type: "OnConsoleEntry", entry });
|
|
93
|
-
}));
|
|
83
|
+
const sharedWorkerReady = Promise.withResolvers();
|
|
94
84
|
// Register ASAP so the worker does not miss connections.
|
|
95
85
|
self.onConnect = (port) => {
|
|
96
86
|
void sharedWorkerReady.promise.then(() => {
|
|
97
87
|
// The underlying port buffers messages until onMessage is assigned.
|
|
98
88
|
port.onMessage = (message) => {
|
|
99
89
|
switch (message.type) {
|
|
100
|
-
case "
|
|
101
|
-
/**
|
|
102
|
-
* One SharedWorker serves multiple tabs. The most recently
|
|
103
|
-
* initialized tab's level wins.
|
|
104
|
-
*/
|
|
90
|
+
case "AnnounceTabLeader": {
|
|
105
91
|
console.setLevel(message.consoleLevel);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (queuedTabOutputs.length > 0) {
|
|
109
|
-
queuedTabOutputs.forEach(postTabOutput);
|
|
110
|
-
queuedTabOutputs.length = 0;
|
|
111
|
-
}
|
|
92
|
+
tabLeaderPortStore.set(port);
|
|
93
|
+
console.info("tabLeaderAnnounced");
|
|
112
94
|
break;
|
|
113
95
|
}
|
|
114
96
|
case "CreateEvolu": {
|
|
115
|
-
void
|
|
116
|
-
const
|
|
117
|
-
if (!
|
|
118
|
-
return
|
|
119
|
-
|
|
97
|
+
void sharedWorkerRun(async (run) => {
|
|
98
|
+
const tenant = await run(unabortable(tenantsByName.acquire(message)));
|
|
99
|
+
if (!tenant.ok)
|
|
100
|
+
return tenant;
|
|
101
|
+
tenant.value.addInstance(message, () => void sharedWorkerRun(tenantsByName.release(message)));
|
|
120
102
|
return ok();
|
|
121
103
|
});
|
|
122
104
|
break;
|
|
@@ -127,8 +109,12 @@ export const initSharedWorker = (self) => async (run) => {
|
|
|
127
109
|
};
|
|
128
110
|
});
|
|
129
111
|
};
|
|
130
|
-
|
|
131
|
-
|
|
112
|
+
disposer.defer(deps.consoleStoreOutputEntry.subscribe(() => {
|
|
113
|
+
const entry = deps.consoleStoreOutputEntry.get();
|
|
114
|
+
if (entry)
|
|
115
|
+
postConsoleEntryOrError({ type: "ConsoleEntry", entry });
|
|
116
|
+
}));
|
|
117
|
+
const transports = disposer.use(await run.orThrow(createSharedResourceByKeyWithClaims((transport) => deps.createWebSocket(transport.url, {
|
|
132
118
|
binaryType: "arraybuffer",
|
|
133
119
|
onOpen: () => {
|
|
134
120
|
const ownerIds = transports.getClaimsForResource(transport);
|
|
@@ -136,38 +122,37 @@ export const initSharedWorker = (self) => async (run) => {
|
|
|
136
122
|
url: transport.url,
|
|
137
123
|
ownerIds: [...ownerIds],
|
|
138
124
|
});
|
|
139
|
-
|
|
140
|
-
|
|
125
|
+
forEachTenant((tenant) => {
|
|
126
|
+
tenant.requestCreateSyncMessages(ownerIds);
|
|
141
127
|
});
|
|
142
128
|
},
|
|
143
129
|
onMessage(data) {
|
|
144
130
|
if (!(data instanceof ArrayBuffer))
|
|
145
131
|
return;
|
|
146
|
-
const
|
|
147
|
-
const headerResult = parseProtocolHeader(
|
|
132
|
+
const message = new Uint8Array(data);
|
|
133
|
+
const headerResult = parseProtocolHeader(message);
|
|
148
134
|
if (!headerResult.ok) {
|
|
149
135
|
console.debug("transportInvalidProtocolMessage", {
|
|
150
136
|
url: transport.url,
|
|
151
|
-
byteLength:
|
|
137
|
+
byteLength: message.byteLength,
|
|
152
138
|
});
|
|
153
|
-
// TODO: Propagate invalid protocol messages to sync state.
|
|
154
139
|
return;
|
|
155
140
|
}
|
|
156
141
|
console.debug("transportProtocolMessage", {
|
|
157
142
|
url: transport.url,
|
|
158
143
|
ownerId: headerResult.value.ownerId,
|
|
159
|
-
byteLength:
|
|
144
|
+
byteLength: message.byteLength,
|
|
160
145
|
});
|
|
161
|
-
|
|
162
|
-
|
|
146
|
+
forEachTenant((tenant) => {
|
|
147
|
+
tenant.requestApplySyncMessage(headerResult.value.ownerId, message);
|
|
163
148
|
});
|
|
164
149
|
},
|
|
165
150
|
}), {
|
|
166
151
|
onFirstClaimAdded: (ownerId, webSocket) => {
|
|
167
152
|
if (!webSocket.isOpen())
|
|
168
153
|
return;
|
|
169
|
-
|
|
170
|
-
|
|
154
|
+
forEachTenant((tenant) => {
|
|
155
|
+
tenant.requestCreateSyncMessages(new Set([ownerId]));
|
|
171
156
|
});
|
|
172
157
|
},
|
|
173
158
|
onLastClaimRemoved: (ownerId, webSocket) => {
|
|
@@ -177,23 +162,23 @@ export const initSharedWorker = (self) => async (run) => {
|
|
|
177
162
|
idleDisposeAfter: "3s",
|
|
178
163
|
resourceLookup: structuralLookup,
|
|
179
164
|
})));
|
|
180
|
-
const
|
|
165
|
+
const sharedWorkerRun = run.create({
|
|
166
|
+
...deps,
|
|
167
|
+
postConsoleEntryOrError,
|
|
168
|
+
tabLeaderPortStore,
|
|
181
169
|
transports,
|
|
182
|
-
postTabOutput,
|
|
183
170
|
});
|
|
184
|
-
const
|
|
185
|
-
|
|
171
|
+
const tenantsByName = disposer.use(await sharedWorkerRun.orThrow(createSharedResourceByKey(createEvoluTenant, {
|
|
172
|
+
idleDisposeAfter: "3s",
|
|
173
|
+
lookup: (message) => message.name,
|
|
186
174
|
})));
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
callback(sharedEvolu);
|
|
175
|
+
sharedWorkerReady.resolve();
|
|
176
|
+
const forEachTenant = (callback) => {
|
|
177
|
+
for (const tenant of tenantsByName.snapshot().resourcesByKey.values()) {
|
|
178
|
+
callback(tenant);
|
|
192
179
|
}
|
|
193
180
|
};
|
|
194
|
-
|
|
195
|
-
console.info("initSharedWorker");
|
|
196
|
-
return ok(stack.move());
|
|
181
|
+
return ok(disposer.move());
|
|
197
182
|
}
|
|
198
183
|
catch (e_1) {
|
|
199
184
|
env_1.error = e_1;
|
|
@@ -205,91 +190,156 @@ export const initSharedWorker = (self) => async (run) => {
|
|
|
205
190
|
await result_1;
|
|
206
191
|
}
|
|
207
192
|
};
|
|
208
|
-
const
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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;
|
|
193
|
+
const createEvoluTenant = ({ name, consoleLevel, sqliteSchema, encryptionKey, memoryOnly, }) => async (run) => {
|
|
194
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
195
|
+
try {
|
|
196
|
+
const disposer = __addDisposableResource(env_2, new AsyncDisposableStack(), true);
|
|
197
|
+
const tenantRun = disposer.use(run.create());
|
|
198
|
+
const { deps } = run;
|
|
199
|
+
const console = deps.console.child(name).child("SharedWorker");
|
|
200
|
+
const instancesById = disposer.adopt(new Map(), async (instancesById) => {
|
|
201
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
202
|
+
try {
|
|
203
|
+
const disposer = __addDisposableResource(env_3, new AsyncDisposableStack(), true);
|
|
204
|
+
for (const instance of [...instancesById.values()]) {
|
|
205
|
+
disposer.use(instance);
|
|
234
206
|
}
|
|
235
|
-
case "ForSharedWorker":
|
|
236
|
-
handleResponseForSharedWorker(response);
|
|
237
|
-
break;
|
|
238
|
-
default:
|
|
239
|
-
exhaustiveCheck(response);
|
|
240
207
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
208
|
+
catch (e_3) {
|
|
209
|
+
env_3.error = e_3;
|
|
210
|
+
env_3.hasError = true;
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
const result_3 = __disposeResources(env_3);
|
|
214
|
+
if (result_3)
|
|
215
|
+
await result_3;
|
|
216
|
+
}
|
|
244
217
|
});
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
nextRowsByQuery.set(query, rows);
|
|
269
|
-
patchesByQuery.set(query, makePatches(previousRowsByQuery.get(query), rows));
|
|
218
|
+
let dbWorkerPort = null;
|
|
219
|
+
const dbWorkerInited = Promise.withResolvers();
|
|
220
|
+
const initDbWorker = () => {
|
|
221
|
+
const tabLeaderPort = deps.tabLeaderPortStore.get();
|
|
222
|
+
assert(tabLeaderPort, "Expected tab leader port.");
|
|
223
|
+
const dbWorkerChannel = deps.createMessageChannel();
|
|
224
|
+
const currentDbWorkerPort = dbWorkerChannel.port2;
|
|
225
|
+
currentDbWorkerPort.onMessage = (message) => {
|
|
226
|
+
switch (message.type) {
|
|
227
|
+
case "LeaderAcquired": {
|
|
228
|
+
assert(dbWorkerPort !== currentDbWorkerPort, "Expected a new DbWorker port.");
|
|
229
|
+
dbWorkerPort?.[Symbol.dispose]();
|
|
230
|
+
dbWorkerPort = currentDbWorkerPort;
|
|
231
|
+
queueRequestInFlight = false;
|
|
232
|
+
console.info("leaderAcquired");
|
|
233
|
+
dbWorkerInited.resolve();
|
|
234
|
+
runQueue();
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
case "OnQueuedResponse": {
|
|
238
|
+
callbacks.execute(message.callbackId, message);
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
270
241
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
242
|
+
};
|
|
243
|
+
tabLeaderPort.postMessage({
|
|
244
|
+
type: "DbWorkerInit",
|
|
245
|
+
name,
|
|
246
|
+
consoleLevel,
|
|
247
|
+
sqliteSchema,
|
|
248
|
+
encryptionKey,
|
|
249
|
+
memoryOnly,
|
|
250
|
+
port: dbWorkerChannel.port1.native,
|
|
251
|
+
}, [dbWorkerChannel.port1.native]);
|
|
252
|
+
};
|
|
253
|
+
const queue = [];
|
|
254
|
+
const callbacks = disposer.use(createCallbacks(run.deps));
|
|
255
|
+
let queueRequestInFlight = false;
|
|
256
|
+
const runQueue = () => {
|
|
257
|
+
if (queueRequestInFlight || !isNonEmptyArray(queue) || !dbWorkerPort) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const request = firstInArray(queue);
|
|
261
|
+
const callbackId = callbacks.register(({ response }) => {
|
|
262
|
+
switch (response.type) {
|
|
263
|
+
case "ForEvolu": {
|
|
264
|
+
handleResponseForEvolu(response, request);
|
|
265
|
+
break;
|
|
287
266
|
}
|
|
288
|
-
|
|
267
|
+
case "ForSharedWorker":
|
|
268
|
+
handleResponseForSharedWorker(response);
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
// Complete the current queue item and continue with the next one.
|
|
272
|
+
shiftFromArray(queue);
|
|
273
|
+
queueRequestInFlight = false;
|
|
274
|
+
runQueue();
|
|
275
|
+
});
|
|
276
|
+
queueRequestInFlight = true;
|
|
277
|
+
dbWorkerPort.postMessage({ type: "Request", callbackId, request });
|
|
278
|
+
};
|
|
279
|
+
disposer.defer(async () => {
|
|
280
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
281
|
+
try {
|
|
282
|
+
dbWorkerPort?.postMessage({ type: "Dispose" });
|
|
283
|
+
dbWorkerPort = null;
|
|
284
|
+
queueRequestInFlight = false;
|
|
285
|
+
// The DbWorker holds this tenant leader lock while it is alive. Tenant
|
|
286
|
+
// disposal sends Dispose, then acquires the same lock to wait until the
|
|
287
|
+
// DbWorker releases it: either because Dispose was delivered or because
|
|
288
|
+
// the hosting tab closed. The wait is unabortable because tenant disposal
|
|
289
|
+
// must finish even after tenantRun receives an abort request.
|
|
290
|
+
const lock = await tenantRun(acquireLeaderLock(name));
|
|
291
|
+
// AbortError here means tenantRun was already disposed before this
|
|
292
|
+
// cleanup could start, violating the disposal ordering above.
|
|
293
|
+
assertNotAborted(lock);
|
|
294
|
+
const _ = __addDisposableResource(env_4, lock.value, true);
|
|
295
|
+
}
|
|
296
|
+
catch (e_4) {
|
|
297
|
+
env_4.error = e_4;
|
|
298
|
+
env_4.hasError = true;
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
const result_4 = __disposeResources(env_4);
|
|
302
|
+
if (result_4)
|
|
303
|
+
await result_4;
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
disposer.defer(deps.tabLeaderPortStore.subscribe(initDbWorker));
|
|
307
|
+
initDbWorker();
|
|
308
|
+
await dbWorkerInited.promise;
|
|
309
|
+
const disposables = disposer.move();
|
|
310
|
+
const handleResponseForEvolu = (response, first) => {
|
|
311
|
+
const instance = instancesById.get(response.id);
|
|
312
|
+
if (!instance)
|
|
313
|
+
return;
|
|
314
|
+
switch (response.message.type) {
|
|
315
|
+
case "Mutate":
|
|
316
|
+
case "Query": {
|
|
317
|
+
const nextRowsByQuery = new Map(instance.rowsByQuery);
|
|
318
|
+
const patchesByQuery = new Map();
|
|
319
|
+
for (const [query, rows] of response.message.rowsByQuery) {
|
|
320
|
+
nextRowsByQuery.set(query, rows);
|
|
321
|
+
patchesByQuery.set(query, makePatches(instance.rowsByQuery.get(query), rows));
|
|
322
|
+
}
|
|
323
|
+
instance.rowsByQuery = nextRowsByQuery;
|
|
324
|
+
instance.port.postMessage({
|
|
325
|
+
type: "OnPatchesByQuery",
|
|
326
|
+
patchesByQuery,
|
|
327
|
+
onCompleteIds: first.message.type === "Mutate"
|
|
328
|
+
? first.message.onCompleteIds
|
|
329
|
+
: emptyArray,
|
|
330
|
+
});
|
|
331
|
+
if (response.message.type === "Mutate") {
|
|
332
|
+
for (const [instanceId, instance] of instancesById) {
|
|
333
|
+
if (instanceId === response.id)
|
|
334
|
+
continue;
|
|
335
|
+
instance.port.postMessage({
|
|
336
|
+
type: "RefreshQueries",
|
|
337
|
+
});
|
|
338
|
+
}
|
|
289
339
|
const protocolMessagesByOwnerId = new Map();
|
|
290
340
|
for (const syncOwner of instance.usedSyncOwners.keys()) {
|
|
291
341
|
const { owner } = syncOwner;
|
|
292
|
-
const messages =
|
|
342
|
+
const messages = response.message.messagesByOwnerId.get(owner.id);
|
|
293
343
|
// Skip owners this instance does not currently sync for
|
|
294
344
|
// writing. Read-only owners cannot produce protocol
|
|
295
345
|
// messages because they do not have a write key.
|
|
@@ -298,252 +348,209 @@ const createSharedEvolu = (name) => (run) => {
|
|
|
298
348
|
protocolMessagesByOwnerId.set(owner.id, createProtocolMessageFromCrdtMessages(run.deps)(owner, messages));
|
|
299
349
|
}
|
|
300
350
|
sendProtocolMessagesByOwnerId(protocolMessagesByOwnerId);
|
|
301
|
-
|
|
302
|
-
|
|
351
|
+
}
|
|
352
|
+
break;
|
|
303
353
|
}
|
|
304
|
-
|
|
354
|
+
case "Export":
|
|
355
|
+
instance.port.postMessage({ type: "OnExport", file: response.message.file }, [response.message.file.buffer]);
|
|
356
|
+
break;
|
|
305
357
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
});
|
|
358
|
+
};
|
|
359
|
+
const handleResponseForSharedWorker = (response) => {
|
|
360
|
+
switch (response.message.type) {
|
|
361
|
+
case "CreateSyncMessages":
|
|
362
|
+
sendProtocolMessagesByOwnerId(response.message.protocolMessagesByOwnerId);
|
|
363
|
+
break;
|
|
364
|
+
case "ApplySyncMessage":
|
|
365
|
+
if (response.message.didWriteMessages) {
|
|
366
|
+
for (const instance of instancesById.values()) {
|
|
367
|
+
instance.port.postMessage({ type: "RefreshQueries" });
|
|
368
|
+
}
|
|
330
369
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
370
|
+
if (!response.message.result.ok) {
|
|
371
|
+
if (response.message.result.error.type !== "AbortError") {
|
|
372
|
+
deps.postConsoleEntryOrError({
|
|
373
|
+
type: "Error",
|
|
374
|
+
error: response.message.result.error,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
switch (response.message.result.value.type) {
|
|
380
|
+
case "Response":
|
|
381
|
+
sendProtocolMessagesByOwnerId(new Map([
|
|
382
|
+
[
|
|
383
|
+
response.message.ownerId,
|
|
384
|
+
response.message.result.value.message,
|
|
385
|
+
],
|
|
386
|
+
]));
|
|
387
|
+
break;
|
|
388
|
+
case "Broadcast":
|
|
389
|
+
case "NoResponse":
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
347
392
|
}
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
const sendProtocolMessagesByOwnerId = (protocolMessagesByOwnerId) => {
|
|
397
|
+
for (const [ownerId, protocolMessage] of protocolMessagesByOwnerId) {
|
|
398
|
+
for (const transport of deps.transports.getResourceKeysForClaim(ownerId)) {
|
|
399
|
+
const webSocket = deps.transports.getResource(transport);
|
|
400
|
+
if (!webSocket?.isOpen())
|
|
401
|
+
continue;
|
|
402
|
+
console.debug("sendProtocolMessage", {
|
|
403
|
+
ownerId,
|
|
404
|
+
url: transport.url,
|
|
405
|
+
byteLength: protocolMessage.byteLength,
|
|
406
|
+
});
|
|
407
|
+
webSocket.send(protocolMessage);
|
|
348
408
|
}
|
|
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
409
|
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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);
|
|
410
|
+
};
|
|
411
|
+
const getUsedOwnersById = (ownerIds) => {
|
|
412
|
+
const ownersById = new Map();
|
|
413
|
+
for (const instance of instancesById.values()) {
|
|
414
|
+
for (const { owner } of instance.usedSyncOwners.keys()) {
|
|
415
|
+
if (!ownerIds.has(owner.id) || !("writeKey" in owner))
|
|
416
|
+
continue;
|
|
417
|
+
ownersById.set(owner.id, owner);
|
|
418
|
+
}
|
|
381
419
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
await run(toggleUsedSyncOwner(evoluInstance, syncOwner, "remove"));
|
|
420
|
+
return ownersById;
|
|
421
|
+
};
|
|
422
|
+
const toggleSyncOwner = (instance, syncOwner, action) => async (run) => {
|
|
423
|
+
if (action === "add") {
|
|
424
|
+
instance.usedSyncOwners.increment(syncOwner);
|
|
425
|
+
return await run(deps.transports.addClaim(syncOwner.owner.id, syncOwner.transports));
|
|
389
426
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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)
|
|
427
|
+
else {
|
|
428
|
+
instance.usedSyncOwners.decrement(syncOwner);
|
|
429
|
+
return await run(deps.transports.removeClaim(syncOwner.owner.id, syncOwner.transports));
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
return ok({
|
|
433
|
+
addInstance: (message, onDisposed) => {
|
|
434
|
+
assertNotDisposed(disposables);
|
|
435
|
+
const instance = {
|
|
436
|
+
id: message.id,
|
|
437
|
+
port: deps.createMessagePort(message.evoluPort),
|
|
438
|
+
onDisposed,
|
|
439
|
+
rowsByQuery: new Map(),
|
|
440
|
+
useOwnerMutex: createMutex(),
|
|
441
|
+
usedSyncOwners: createRefCountByKey({
|
|
442
|
+
lookup: (syncOwner) => syncOwner.owner.id,
|
|
443
|
+
}),
|
|
444
|
+
[Symbol.asyncDispose]: () => disposer.disposeAsync(),
|
|
445
|
+
};
|
|
446
|
+
instancesById.set(instance.id, instance);
|
|
447
|
+
const disposer = new AsyncDisposableStack();
|
|
448
|
+
disposer.defer(instance.onDisposed);
|
|
449
|
+
disposer.use(instance.usedSyncOwners);
|
|
450
|
+
disposer.use(instance.useOwnerMutex);
|
|
451
|
+
disposer.defer(async () => {
|
|
452
|
+
await tenantRun(instance.useOwnerMutex.withLock(async (run) => {
|
|
453
|
+
for (const syncOwner of instance.usedSyncOwners.keys()) {
|
|
454
|
+
while (instance.usedSyncOwners.has(syncOwner)) {
|
|
455
|
+
await run(toggleSyncOwner(instance, syncOwner, "remove"));
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return ok();
|
|
459
|
+
}));
|
|
460
|
+
});
|
|
461
|
+
disposer.defer(() => {
|
|
462
|
+
instancesById.delete(instance.id);
|
|
463
|
+
console.info("evoluDispose", { name, id: instance.id });
|
|
464
|
+
});
|
|
465
|
+
disposer.use(instance.port);
|
|
466
|
+
// The main-thread Evolu instance holds this per-instance leader lock
|
|
467
|
+
// while it is alive. Acquiring the same lock here means the main
|
|
468
|
+
// thread instance was disposed or its tab closed, so the tenant-side
|
|
469
|
+
// instance must dispose itself.
|
|
470
|
+
void tenantRun(acquireLeaderLock(message.id)).then((lock) => {
|
|
471
|
+
if (!lock.ok)
|
|
472
|
+
return;
|
|
473
|
+
disposer.use(lock.value);
|
|
474
|
+
return disposer.disposeAsync();
|
|
475
|
+
});
|
|
476
|
+
instance.port.onMessage = (message) => {
|
|
477
|
+
switch (message.type) {
|
|
478
|
+
case "Query":
|
|
479
|
+
case "Export": {
|
|
480
|
+
queue.push({ type: "ForEvolu", id: instance.id, message });
|
|
481
|
+
runQueue();
|
|
466
482
|
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
483
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
if (!evoluInstance)
|
|
484
|
+
case "Mutate": {
|
|
485
|
+
// TODO: Delegate do vsech evolu instances, co to pouzivaji
|
|
486
|
+
queue.push({ type: "ForEvolu", id: instance.id, message });
|
|
487
|
+
runQueue();
|
|
481
488
|
break;
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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;
|
|
489
|
+
}
|
|
490
|
+
case "UseOwner": {
|
|
491
|
+
void tenantRun(instance.useOwnerMutex.withLock(async (run) => {
|
|
492
|
+
for (const { owner, action } of message.actions) {
|
|
493
|
+
console.debug("useOwner", {
|
|
494
|
+
id: instance.id,
|
|
495
|
+
action,
|
|
496
|
+
ownerId: owner.owner.id,
|
|
497
|
+
transportUrls: owner.transports.map(({ url }) => url),
|
|
498
|
+
});
|
|
499
|
+
await run(toggleSyncOwner(instance, owner, action));
|
|
500
|
+
}
|
|
501
|
+
return ok();
|
|
502
|
+
}));
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
505
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
506
|
+
};
|
|
507
|
+
},
|
|
508
|
+
requestCreateSyncMessages: (ownerIds) => {
|
|
509
|
+
const ownersToSync = [...getUsedOwnersById(ownerIds).values()];
|
|
510
|
+
if (!isNonEmptyArray(ownersToSync))
|
|
511
|
+
return;
|
|
512
|
+
console.debug("requestCreateSyncMessages", {
|
|
513
|
+
ownerIds: ownersToSync.map(({ id }) => id),
|
|
514
|
+
});
|
|
515
|
+
queue.push({
|
|
516
|
+
type: "ForSharedWorker",
|
|
517
|
+
message: {
|
|
518
|
+
type: "CreateSyncMessages",
|
|
519
|
+
owners: ownersToSync,
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
runQueue();
|
|
523
|
+
},
|
|
524
|
+
requestApplySyncMessage: (ownerId, inputMessage) => {
|
|
525
|
+
const owner = getUsedOwnersById(new Set([ownerId])).get(ownerId);
|
|
526
|
+
if (!owner)
|
|
527
|
+
return;
|
|
528
|
+
console.debug("requestApplySyncMessage", {
|
|
529
|
+
ownerId,
|
|
530
|
+
byteLength: inputMessage.byteLength,
|
|
531
|
+
});
|
|
532
|
+
queue.push({
|
|
533
|
+
type: "ForSharedWorker",
|
|
534
|
+
message: {
|
|
535
|
+
type: "ApplySyncMessage",
|
|
536
|
+
owner,
|
|
537
|
+
inputMessage,
|
|
538
|
+
},
|
|
539
|
+
});
|
|
540
|
+
runQueue();
|
|
541
|
+
},
|
|
542
|
+
[Symbol.asyncDispose]: () => disposables.disposeAsync(),
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
catch (e_2) {
|
|
546
|
+
env_2.error = e_2;
|
|
547
|
+
env_2.hasError = true;
|
|
548
|
+
}
|
|
549
|
+
finally {
|
|
550
|
+
const result_2 = __disposeResources(env_2);
|
|
551
|
+
if (result_2)
|
|
552
|
+
await result_2;
|
|
553
|
+
}
|
|
547
554
|
};
|
|
548
555
|
// | (Typed<"reset"> & {
|
|
549
556
|
// readonly onCompleteId: CallbackId;
|
|
@@ -567,3 +574,10 @@ const createSharedEvolu = (name) => (run) => {
|
|
|
567
574
|
// readonly onCompleteId: CallbackId;
|
|
568
575
|
// readonly reload: boolean;
|
|
569
576
|
// })
|
|
577
|
+
// TODO: Remaining SharedWorker lifetime issues.
|
|
578
|
+
// - Investigate heartbeat for ports and where liveness tracking is needed.
|
|
579
|
+
// - Sync state for monitoring.
|
|
580
|
+
// - Propagate invalid protocol messages to sync state.
|
|
581
|
+
// - Make DbWorker logically stateless by making all requests idempotent.
|
|
582
|
+
// - Investigate WebKit bug 301520 relevance to DbWorker disposal. (again)
|
|
583
|
+
// https://bugs.webkit.org/show_bug.cgi?id=301520
|