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