@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,932 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-agnostic Evolu SharedWorker.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
emptyArray,
|
|
9
|
+
firstInArray,
|
|
10
|
+
isNonEmptyArray,
|
|
11
|
+
shiftFromArray,
|
|
12
|
+
type NonEmptyReadonlyArray,
|
|
13
|
+
} from "../Array.js";
|
|
14
|
+
import { assert, assertNotDisposed } from "../Assert.js";
|
|
15
|
+
import { createCallbacks } from "../Callbacks.js";
|
|
16
|
+
import type { ConsoleEntry, ConsoleLevel } from "../Console.js";
|
|
17
|
+
import { exhaustiveCheck } from "../Function.js";
|
|
18
|
+
import { structuralLookup, type StructuralLookupKey } from "../Lookup.js";
|
|
19
|
+
import { createRefCountByKey, type RefCountByKey } from "../RefCount.js";
|
|
20
|
+
import {
|
|
21
|
+
createSharedResourceByKey,
|
|
22
|
+
createSharedResourceByKeyWithClaims,
|
|
23
|
+
type BorrowedResource,
|
|
24
|
+
type SharedResourceByKeyWithClaims,
|
|
25
|
+
} from "../Resource.js";
|
|
26
|
+
import { ok, type Result } from "../Result.js";
|
|
27
|
+
import { spaced } from "../Schedule.js";
|
|
28
|
+
import type { NonEmptyReadonlySet } from "../Set.js";
|
|
29
|
+
import {
|
|
30
|
+
repeat,
|
|
31
|
+
unabortable,
|
|
32
|
+
type AbortError,
|
|
33
|
+
type Fiber,
|
|
34
|
+
type Task,
|
|
35
|
+
} from "../Task.js";
|
|
36
|
+
import { createId, type Id, type Name } from "../Type.js";
|
|
37
|
+
import type { Callback, ExtractType } from "../Types.js";
|
|
38
|
+
import type { CreateWebSocketDep, WebSocket } from "../WebSocket.js";
|
|
39
|
+
import type {
|
|
40
|
+
SharedWorker as CommonSharedWorker,
|
|
41
|
+
MessagePort,
|
|
42
|
+
NativeMessagePort,
|
|
43
|
+
SharedWorkerSelf,
|
|
44
|
+
WorkerDeps,
|
|
45
|
+
} from "../Worker.js";
|
|
46
|
+
import type { EvoluError } from "./Error.js";
|
|
47
|
+
import type { Owner, OwnerId, OwnerTransport, SyncOwner } from "./Owner.js";
|
|
48
|
+
import {
|
|
49
|
+
createProtocolMessageForUnsubscribe,
|
|
50
|
+
createProtocolMessageFromCrdtMessages,
|
|
51
|
+
parseProtocolHeader,
|
|
52
|
+
type ApplyProtocolMessageAsClientResult,
|
|
53
|
+
type ProtocolError,
|
|
54
|
+
type ProtocolMessage,
|
|
55
|
+
} from "./Protocol.js";
|
|
56
|
+
import {
|
|
57
|
+
makePatches,
|
|
58
|
+
type Patch,
|
|
59
|
+
type Query,
|
|
60
|
+
type RowsByQueryMap,
|
|
61
|
+
} from "./Query.js";
|
|
62
|
+
import type { MutationChange } from "./Schema.js";
|
|
63
|
+
import type { CrdtMessage } from "./Storage.js";
|
|
64
|
+
|
|
65
|
+
export type SharedWorker = CommonSharedWorker<SharedWorkerInput>;
|
|
66
|
+
|
|
67
|
+
export interface SharedWorkerDep {
|
|
68
|
+
readonly sharedWorker: SharedWorker;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type SharedWorkerDeps = WorkerDeps & CreateWebSocketDep;
|
|
72
|
+
|
|
73
|
+
export type SharedWorkerInput =
|
|
74
|
+
| {
|
|
75
|
+
readonly type: "InitTab";
|
|
76
|
+
readonly consoleLevel: ConsoleLevel;
|
|
77
|
+
readonly port: NativeMessagePort<EvoluTabOutput>;
|
|
78
|
+
}
|
|
79
|
+
| {
|
|
80
|
+
readonly type: "CreateEvolu";
|
|
81
|
+
readonly name: Name;
|
|
82
|
+
readonly evoluPort: NativeMessagePort<EvoluOutput, EvoluInput>;
|
|
83
|
+
readonly dbWorkerPort: NativeMessagePort<DbWorkerInput, DbWorkerOutput>;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type EvoluTabOutput =
|
|
87
|
+
| {
|
|
88
|
+
readonly type: "OnConsoleEntry";
|
|
89
|
+
readonly entry: ConsoleEntry;
|
|
90
|
+
}
|
|
91
|
+
| {
|
|
92
|
+
readonly type: "OnError";
|
|
93
|
+
readonly error: EvoluError;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type EvoluInput =
|
|
97
|
+
| {
|
|
98
|
+
readonly type: "Mutate";
|
|
99
|
+
readonly changes: NonEmptyReadonlyArray<MutationChange>;
|
|
100
|
+
readonly onCompleteIds: ReadonlyArray<Id>;
|
|
101
|
+
readonly subscribedQueries: ReadonlySet<Query>;
|
|
102
|
+
}
|
|
103
|
+
| {
|
|
104
|
+
readonly type: "UseOwner";
|
|
105
|
+
readonly actions: ReadonlyArray<{
|
|
106
|
+
readonly owner: SyncOwner;
|
|
107
|
+
readonly action: "add" | "remove";
|
|
108
|
+
}>;
|
|
109
|
+
}
|
|
110
|
+
| {
|
|
111
|
+
readonly type: "Query";
|
|
112
|
+
readonly queries: NonEmptyReadonlySet<Query>;
|
|
113
|
+
}
|
|
114
|
+
| {
|
|
115
|
+
readonly type: "Export";
|
|
116
|
+
}
|
|
117
|
+
| {
|
|
118
|
+
readonly type: "Dispose";
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export type EvoluOutput =
|
|
122
|
+
| {
|
|
123
|
+
readonly type: "OnPatchesByQuery";
|
|
124
|
+
readonly patchesByQuery: ReadonlyMap<Query, ReadonlyArray<Patch>>;
|
|
125
|
+
readonly onCompleteIds: ReadonlyArray<Id>;
|
|
126
|
+
}
|
|
127
|
+
| {
|
|
128
|
+
readonly type: "RefreshQueries";
|
|
129
|
+
}
|
|
130
|
+
| {
|
|
131
|
+
readonly type: "OnExport";
|
|
132
|
+
readonly file: Uint8Array<ArrayBuffer>;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const initSharedWorker =
|
|
136
|
+
(
|
|
137
|
+
self: SharedWorkerSelf<SharedWorkerInput>,
|
|
138
|
+
): Task<AsyncDisposableStack, never, SharedWorkerDeps> =>
|
|
139
|
+
async (run) => {
|
|
140
|
+
const { createMessagePort, consoleStoreOutputEntry, createWebSocket } =
|
|
141
|
+
run.deps;
|
|
142
|
+
const console = run.deps.console.child("SharedWorker");
|
|
143
|
+
await using stack = new AsyncDisposableStack();
|
|
144
|
+
const sharedWorkerReady = Promise.withResolvers<void>();
|
|
145
|
+
|
|
146
|
+
// TODO: Use heartbeat to detect and prune dead ports.
|
|
147
|
+
const tabPorts = new Set<MessagePort<EvoluTabOutput>>();
|
|
148
|
+
|
|
149
|
+
// Buffer console/error outputs until the first tab connects its port.
|
|
150
|
+
const queuedTabOutputs: Array<EvoluTabOutput> = [];
|
|
151
|
+
const postTabOutput = (output: EvoluTabOutput): void => {
|
|
152
|
+
if (tabPorts.size === 0) queuedTabOutputs.push(output);
|
|
153
|
+
else for (const port of tabPorts) port.postMessage(output);
|
|
154
|
+
};
|
|
155
|
+
stack.defer(
|
|
156
|
+
consoleStoreOutputEntry.subscribe(() => {
|
|
157
|
+
const entry = consoleStoreOutputEntry.get();
|
|
158
|
+
if (entry) postTabOutput({ type: "OnConsoleEntry", entry });
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Register ASAP so the worker does not miss connections.
|
|
163
|
+
self.onConnect = (port) => {
|
|
164
|
+
void sharedWorkerReady.promise.then(() => {
|
|
165
|
+
// The underlying port buffers messages until onMessage is assigned.
|
|
166
|
+
port.onMessage = (message) => {
|
|
167
|
+
switch (message.type) {
|
|
168
|
+
case "InitTab": {
|
|
169
|
+
/**
|
|
170
|
+
* One SharedWorker serves multiple tabs. The most recently
|
|
171
|
+
* initialized tab's level wins.
|
|
172
|
+
*/
|
|
173
|
+
console.setLevel(message.consoleLevel);
|
|
174
|
+
|
|
175
|
+
const tabPort = createMessagePort<EvoluTabOutput>(message.port);
|
|
176
|
+
tabPorts.add(tabPort);
|
|
177
|
+
if (queuedTabOutputs.length > 0) {
|
|
178
|
+
queuedTabOutputs.forEach(postTabOutput);
|
|
179
|
+
queuedTabOutputs.length = 0;
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
case "CreateEvolu": {
|
|
185
|
+
void sharedWorkerRunWithSharedEvoluDeps(async (run) => {
|
|
186
|
+
const sharedEvoluResult = await run(
|
|
187
|
+
sharedEvolusByName.acquire(message.name),
|
|
188
|
+
);
|
|
189
|
+
if (!sharedEvoluResult.ok) return sharedEvoluResult;
|
|
190
|
+
sharedEvoluResult.value.createInstance(
|
|
191
|
+
message.evoluPort,
|
|
192
|
+
message.dbWorkerPort,
|
|
193
|
+
() =>
|
|
194
|
+
void sharedWorkerRunWithSharedEvoluDeps(
|
|
195
|
+
sharedEvolusByName.release(message.name),
|
|
196
|
+
),
|
|
197
|
+
);
|
|
198
|
+
return ok();
|
|
199
|
+
});
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
default:
|
|
203
|
+
console.error("Unknown shared worker input", message);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const initSharedWorkerRun = run.create();
|
|
210
|
+
|
|
211
|
+
const transports = stack.use(
|
|
212
|
+
await initSharedWorkerRun.orThrow(
|
|
213
|
+
createSharedResourceByKeyWithClaims<
|
|
214
|
+
WebSocket,
|
|
215
|
+
OwnerTransport,
|
|
216
|
+
OwnerId,
|
|
217
|
+
SharedWorkerDeps,
|
|
218
|
+
StructuralLookupKey
|
|
219
|
+
>(
|
|
220
|
+
(transport) =>
|
|
221
|
+
createWebSocket(transport.url, {
|
|
222
|
+
binaryType: "arraybuffer",
|
|
223
|
+
|
|
224
|
+
onOpen: () => {
|
|
225
|
+
const ownerIds = transports.getClaimsForResource(transport);
|
|
226
|
+
console.debug("transportOpen", {
|
|
227
|
+
url: transport.url,
|
|
228
|
+
ownerIds: [...ownerIds],
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
forEachSharedEvolu((sharedEvolu) => {
|
|
232
|
+
sharedEvolu.requestCreateSyncMessages(ownerIds);
|
|
233
|
+
});
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
onMessage(data) {
|
|
237
|
+
if (!(data instanceof ArrayBuffer)) return;
|
|
238
|
+
|
|
239
|
+
const inputMessage = new globalThis.Uint8Array(data);
|
|
240
|
+
const headerResult = parseProtocolHeader(
|
|
241
|
+
new globalThis.Uint8Array(data),
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
if (!headerResult.ok) {
|
|
245
|
+
console.debug("transportInvalidProtocolMessage", {
|
|
246
|
+
url: transport.url,
|
|
247
|
+
byteLength: inputMessage.byteLength,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// TODO: Propagate invalid protocol messages to sync state.
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.debug("transportProtocolMessage", {
|
|
255
|
+
url: transport.url,
|
|
256
|
+
ownerId: headerResult.value.ownerId,
|
|
257
|
+
byteLength: inputMessage.byteLength,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
forEachSharedEvolu((sharedEvolu) => {
|
|
261
|
+
sharedEvolu.requestApplySyncMessage(
|
|
262
|
+
headerResult.value.ownerId,
|
|
263
|
+
inputMessage,
|
|
264
|
+
);
|
|
265
|
+
});
|
|
266
|
+
},
|
|
267
|
+
}),
|
|
268
|
+
{
|
|
269
|
+
onFirstClaimAdded: (ownerId, webSocket) => {
|
|
270
|
+
if (!webSocket.isOpen()) return;
|
|
271
|
+
forEachSharedEvolu((sharedEvolu) => {
|
|
272
|
+
sharedEvolu.requestCreateSyncMessages(new Set([ownerId]));
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
onLastClaimRemoved: (ownerId, webSocket) => {
|
|
277
|
+
webSocket.send(createProtocolMessageForUnsubscribe(ownerId));
|
|
278
|
+
},
|
|
279
|
+
// Keep sockets alive briefly across short owner churn.
|
|
280
|
+
idleDisposeAfter: "3s",
|
|
281
|
+
resourceLookup: structuralLookup,
|
|
282
|
+
},
|
|
283
|
+
),
|
|
284
|
+
),
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
const sharedWorkerRunWithSharedEvoluDeps = initSharedWorkerRun.addDeps({
|
|
288
|
+
transports,
|
|
289
|
+
postTabOutput,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const sharedEvolusByName = stack.use(
|
|
293
|
+
await sharedWorkerRunWithSharedEvoluDeps.orThrow(
|
|
294
|
+
createSharedResourceByKey(createSharedEvolu, {
|
|
295
|
+
lookup: structuralLookup,
|
|
296
|
+
}),
|
|
297
|
+
),
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const forEachSharedEvolu = (
|
|
301
|
+
callback: Callback<BorrowedResource<SharedEvolu>>,
|
|
302
|
+
): void => {
|
|
303
|
+
for (const sharedEvolu of sharedEvolusByName
|
|
304
|
+
.snapshot()
|
|
305
|
+
.resourcesByKey.values()) {
|
|
306
|
+
callback(sharedEvolu);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
sharedWorkerReady.resolve();
|
|
311
|
+
console.info("initSharedWorker");
|
|
312
|
+
|
|
313
|
+
return ok(stack.move());
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
interface SharedEvolu extends AsyncDisposable {
|
|
317
|
+
readonly createInstance: (
|
|
318
|
+
evoluPort: NativeMessagePort<EvoluOutput, EvoluInput>,
|
|
319
|
+
dbWorkerPort: NativeMessagePort<DbWorkerInput, DbWorkerOutput>,
|
|
320
|
+
releaseSharedEvolu: () => void,
|
|
321
|
+
) => void;
|
|
322
|
+
|
|
323
|
+
readonly requestCreateSyncMessages: (ownerIds: ReadonlySet<OwnerId>) => void;
|
|
324
|
+
|
|
325
|
+
readonly requestApplySyncMessage: (
|
|
326
|
+
ownerId: OwnerId,
|
|
327
|
+
inputMessage: Uint8Array<ArrayBuffer>,
|
|
328
|
+
) => void;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
type SharedEvoluDeps = SharedWorkerDeps & PostTabOutputDep & TransportsDep;
|
|
332
|
+
|
|
333
|
+
interface PostTabOutputDep {
|
|
334
|
+
readonly postTabOutput: Callback<EvoluTabOutput>;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
interface TransportsDep {
|
|
338
|
+
readonly transports: SharedResourceByKeyWithClaims<
|
|
339
|
+
OwnerTransport,
|
|
340
|
+
OwnerId,
|
|
341
|
+
WebSocket,
|
|
342
|
+
SharedWorkerDeps
|
|
343
|
+
>;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export interface DbWorkerInput {
|
|
347
|
+
readonly callbackId: Id;
|
|
348
|
+
readonly request:
|
|
349
|
+
| {
|
|
350
|
+
readonly type: "ForEvolu";
|
|
351
|
+
readonly evoluPortId: Id;
|
|
352
|
+
readonly message: ExtractType<
|
|
353
|
+
EvoluInput,
|
|
354
|
+
"Mutate" | "Query" | "Export"
|
|
355
|
+
>;
|
|
356
|
+
}
|
|
357
|
+
| {
|
|
358
|
+
readonly type: "ForSharedWorker";
|
|
359
|
+
readonly message:
|
|
360
|
+
| {
|
|
361
|
+
readonly type: "CreateSyncMessages";
|
|
362
|
+
readonly owners: NonEmptyReadonlyArray<Owner>;
|
|
363
|
+
}
|
|
364
|
+
| {
|
|
365
|
+
readonly type: "ApplySyncMessage";
|
|
366
|
+
readonly owner: Owner;
|
|
367
|
+
readonly inputMessage: Uint8Array<ArrayBuffer>;
|
|
368
|
+
};
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export type DbWorkerOutput =
|
|
373
|
+
| EvoluTabOutput
|
|
374
|
+
| {
|
|
375
|
+
readonly type: "LeaderAcquired";
|
|
376
|
+
readonly name: Name;
|
|
377
|
+
}
|
|
378
|
+
| {
|
|
379
|
+
readonly type: "OnQueuedResponse";
|
|
380
|
+
readonly callbackId: Id;
|
|
381
|
+
readonly response:
|
|
382
|
+
| {
|
|
383
|
+
readonly type: "ForEvolu";
|
|
384
|
+
readonly evoluPortId: Id;
|
|
385
|
+
readonly message:
|
|
386
|
+
| {
|
|
387
|
+
readonly type: "Mutate";
|
|
388
|
+
readonly messagesByOwnerId: ReadonlyMap<
|
|
389
|
+
OwnerId,
|
|
390
|
+
NonEmptyReadonlyArray<CrdtMessage>
|
|
391
|
+
>;
|
|
392
|
+
readonly rowsByQuery: RowsByQueryMap;
|
|
393
|
+
}
|
|
394
|
+
| {
|
|
395
|
+
readonly type: "Query";
|
|
396
|
+
readonly rowsByQuery: RowsByQueryMap;
|
|
397
|
+
}
|
|
398
|
+
| {
|
|
399
|
+
readonly type: "Export";
|
|
400
|
+
readonly file: Uint8Array<ArrayBuffer>;
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
| {
|
|
404
|
+
readonly type: "ForSharedWorker";
|
|
405
|
+
readonly message:
|
|
406
|
+
| {
|
|
407
|
+
readonly type: "CreateSyncMessages";
|
|
408
|
+
readonly protocolMessagesByOwnerId: ReadonlyMap<
|
|
409
|
+
OwnerId,
|
|
410
|
+
ProtocolMessage
|
|
411
|
+
>;
|
|
412
|
+
}
|
|
413
|
+
| {
|
|
414
|
+
readonly type: "ApplySyncMessage";
|
|
415
|
+
readonly ownerId: OwnerId;
|
|
416
|
+
readonly didWriteMessages: boolean;
|
|
417
|
+
readonly result: Result<
|
|
418
|
+
ApplyProtocolMessageAsClientResult,
|
|
419
|
+
ProtocolError | AbortError
|
|
420
|
+
>;
|
|
421
|
+
};
|
|
422
|
+
};
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
export type SyncState = 123;
|
|
426
|
+
|
|
427
|
+
const createSharedEvolu =
|
|
428
|
+
(name: Name): Task<SharedEvolu, never, SharedEvoluDeps> =>
|
|
429
|
+
(run) => {
|
|
430
|
+
const stack = new AsyncDisposableStack();
|
|
431
|
+
const sharedEvoluRun = run.create();
|
|
432
|
+
|
|
433
|
+
const console = run.deps.console.child(name).child("SharedWorker");
|
|
434
|
+
const { createMessagePort, postTabOutput, transports } = run.deps;
|
|
435
|
+
|
|
436
|
+
interface EvoluInstanceState {
|
|
437
|
+
readonly evoluPort: MessagePort<EvoluOutput, EvoluInput>;
|
|
438
|
+
readonly dbWorkerPort: MessagePort<DbWorkerInput, DbWorkerOutput>;
|
|
439
|
+
readonly releaseSharedEvolu: () => void;
|
|
440
|
+
rowsByQuery: RowsByQueryMap;
|
|
441
|
+
readonly usedSyncOwners: RefCountByKey<SyncOwner>;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const evoluInstancesByPortId = new Map<Id, EvoluInstanceState>();
|
|
445
|
+
const queue: Array<DbWorkerInput["request"]> = [];
|
|
446
|
+
const callbacks = stack.use(
|
|
447
|
+
createCallbacks<ExtractType<DbWorkerOutput, "OnQueuedResponse">>(
|
|
448
|
+
run.deps,
|
|
449
|
+
),
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
let leaderDbWorkerPort = null as MessagePort<
|
|
453
|
+
DbWorkerInput,
|
|
454
|
+
DbWorkerOutput
|
|
455
|
+
> | null;
|
|
456
|
+
let queueProcessingFiber: Fiber<void, never, WorkerDeps> | null = null;
|
|
457
|
+
|
|
458
|
+
stack.use(sharedEvoluRun);
|
|
459
|
+
const moved = stack.move();
|
|
460
|
+
|
|
461
|
+
const ensureQueueProcessing = (): void => {
|
|
462
|
+
if (
|
|
463
|
+
queueProcessingFiber ||
|
|
464
|
+
!isNonEmptyArray(queue) ||
|
|
465
|
+
!leaderDbWorkerPort
|
|
466
|
+
) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const first = firstInArray(queue);
|
|
471
|
+
|
|
472
|
+
const callbackId = callbacks.register(({ response }) => {
|
|
473
|
+
queueProcessingFiber?.abort();
|
|
474
|
+
queueProcessingFiber = null;
|
|
475
|
+
|
|
476
|
+
switch (response.type) {
|
|
477
|
+
case "ForEvolu": {
|
|
478
|
+
handleResponseForEvolu(response, first);
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
case "ForSharedWorker":
|
|
483
|
+
handleResponseForSharedWorker(response);
|
|
484
|
+
break;
|
|
485
|
+
|
|
486
|
+
default:
|
|
487
|
+
exhaustiveCheck(response);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Complete the current queue item and continue with the next one.
|
|
491
|
+
shiftFromArray(queue);
|
|
492
|
+
ensureQueueProcessing();
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* The leader DbWorker can disappear at any time when its tab closes, so
|
|
497
|
+
* keep resending the current request until some leader answers. The
|
|
498
|
+
* response handler aborts this retry loop and advances the queue.
|
|
499
|
+
*/
|
|
500
|
+
queueProcessingFiber = sharedEvoluRun.daemon(
|
|
501
|
+
repeat(() => {
|
|
502
|
+
assert(leaderDbWorkerPort, "Expected a leader DbWorker");
|
|
503
|
+
leaderDbWorkerPort.postMessage({ callbackId, request: first });
|
|
504
|
+
return ok();
|
|
505
|
+
}, spaced("5s")),
|
|
506
|
+
);
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const handleResponseForEvolu = (
|
|
510
|
+
response: ExtractType<
|
|
511
|
+
ExtractType<DbWorkerOutput, "OnQueuedResponse">["response"],
|
|
512
|
+
"ForEvolu"
|
|
513
|
+
>,
|
|
514
|
+
first: DbWorkerInput["request"],
|
|
515
|
+
): void => {
|
|
516
|
+
const instance = evoluInstancesByPortId.get(response.evoluPortId);
|
|
517
|
+
const port = instance?.evoluPort;
|
|
518
|
+
|
|
519
|
+
if (!port) return;
|
|
520
|
+
|
|
521
|
+
switch (response.message.type) {
|
|
522
|
+
case "Mutate":
|
|
523
|
+
case "Query": {
|
|
524
|
+
const previousRowsByQuery = instance.rowsByQuery;
|
|
525
|
+
const nextRowsByQuery = new Map(previousRowsByQuery);
|
|
526
|
+
const patchesByQuery = new Map<Query, ReadonlyArray<Patch>>();
|
|
527
|
+
|
|
528
|
+
for (const [query, rows] of response.message.rowsByQuery) {
|
|
529
|
+
nextRowsByQuery.set(query, rows);
|
|
530
|
+
patchesByQuery.set(
|
|
531
|
+
query,
|
|
532
|
+
makePatches(previousRowsByQuery.get(query), rows),
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
instance.rowsByQuery = nextRowsByQuery;
|
|
537
|
+
|
|
538
|
+
instance.evoluPort.postMessage({
|
|
539
|
+
type: "OnPatchesByQuery",
|
|
540
|
+
patchesByQuery,
|
|
541
|
+
onCompleteIds:
|
|
542
|
+
first.message.type === "Mutate"
|
|
543
|
+
? first.message.onCompleteIds
|
|
544
|
+
: emptyArray,
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
if (response.message.type === "Mutate") {
|
|
548
|
+
const mutateResponse = response.message;
|
|
549
|
+
|
|
550
|
+
for (const [
|
|
551
|
+
otherEvoluPortId,
|
|
552
|
+
otherEvoluInstance,
|
|
553
|
+
] of evoluInstancesByPortId) {
|
|
554
|
+
if (otherEvoluPortId === response.evoluPortId) continue;
|
|
555
|
+
otherEvoluInstance.evoluPort.postMessage({
|
|
556
|
+
type: "RefreshQueries",
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
sharedEvoluRun<void, never>((run) => {
|
|
561
|
+
const protocolMessagesByOwnerId = new Map<
|
|
562
|
+
OwnerId,
|
|
563
|
+
ProtocolMessage
|
|
564
|
+
>();
|
|
565
|
+
|
|
566
|
+
for (const syncOwner of instance.usedSyncOwners.keys()) {
|
|
567
|
+
const { owner } = syncOwner;
|
|
568
|
+
const messages = mutateResponse.messagesByOwnerId.get(owner.id);
|
|
569
|
+
|
|
570
|
+
// Skip owners this instance does not currently sync for
|
|
571
|
+
// writing. Read-only owners cannot produce protocol
|
|
572
|
+
// messages because they do not have a write key.
|
|
573
|
+
if (!messages || !("writeKey" in owner)) continue;
|
|
574
|
+
|
|
575
|
+
protocolMessagesByOwnerId.set(
|
|
576
|
+
owner.id,
|
|
577
|
+
createProtocolMessageFromCrdtMessages(run.deps)(
|
|
578
|
+
owner,
|
|
579
|
+
messages,
|
|
580
|
+
),
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
sendProtocolMessagesByOwnerId(protocolMessagesByOwnerId);
|
|
585
|
+
|
|
586
|
+
return ok();
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
case "Export":
|
|
593
|
+
port.postMessage({ type: "OnExport", file: response.message.file }, [
|
|
594
|
+
response.message.file.buffer,
|
|
595
|
+
]);
|
|
596
|
+
break;
|
|
597
|
+
|
|
598
|
+
default:
|
|
599
|
+
exhaustiveCheck(response.message);
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
const handleResponseForSharedWorker = (
|
|
604
|
+
response: ExtractType<
|
|
605
|
+
ExtractType<DbWorkerOutput, "OnQueuedResponse">["response"],
|
|
606
|
+
"ForSharedWorker"
|
|
607
|
+
>,
|
|
608
|
+
): void => {
|
|
609
|
+
switch (response.message.type) {
|
|
610
|
+
case "CreateSyncMessages":
|
|
611
|
+
sendProtocolMessagesByOwnerId(
|
|
612
|
+
response.message.protocolMessagesByOwnerId,
|
|
613
|
+
);
|
|
614
|
+
break;
|
|
615
|
+
|
|
616
|
+
case "ApplySyncMessage":
|
|
617
|
+
if (response.message.didWriteMessages) {
|
|
618
|
+
refreshQueries();
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (!response.message.result.ok) {
|
|
622
|
+
if (response.message.result.error.type !== "AbortError") {
|
|
623
|
+
postTabOutput({
|
|
624
|
+
type: "OnError",
|
|
625
|
+
error: response.message.result.error,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
} else {
|
|
629
|
+
switch (response.message.result.value.type) {
|
|
630
|
+
case "Response":
|
|
631
|
+
sendProtocolMessagesByOwnerId(
|
|
632
|
+
new Map([
|
|
633
|
+
[
|
|
634
|
+
response.message.ownerId,
|
|
635
|
+
response.message.result.value.message,
|
|
636
|
+
],
|
|
637
|
+
]),
|
|
638
|
+
);
|
|
639
|
+
break;
|
|
640
|
+
|
|
641
|
+
case "Broadcast":
|
|
642
|
+
case "NoResponse":
|
|
643
|
+
break;
|
|
644
|
+
|
|
645
|
+
default:
|
|
646
|
+
exhaustiveCheck(response.message.result.value);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
break;
|
|
650
|
+
|
|
651
|
+
default:
|
|
652
|
+
exhaustiveCheck(response.message);
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
const sendProtocolMessagesByOwnerId = (
|
|
657
|
+
protocolMessagesByOwnerId: ReadonlyMap<OwnerId, ProtocolMessage>,
|
|
658
|
+
): void => {
|
|
659
|
+
for (const [ownerId, protocolMessage] of protocolMessagesByOwnerId) {
|
|
660
|
+
for (const transport of transports.getResourceKeysForClaim(ownerId)) {
|
|
661
|
+
const webSocket = transports.getResource(transport);
|
|
662
|
+
if (!webSocket?.isOpen()) continue;
|
|
663
|
+
|
|
664
|
+
console.debug("sendProtocolMessage", {
|
|
665
|
+
ownerId,
|
|
666
|
+
url: transport.url,
|
|
667
|
+
byteLength: protocolMessage.byteLength,
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
webSocket.send(protocolMessage);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
const refreshQueries = (): void => {
|
|
676
|
+
for (const evoluInstance of evoluInstancesByPortId.values()) {
|
|
677
|
+
evoluInstance.evoluPort.postMessage({ type: "RefreshQueries" });
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
const getUsedOwnersById = (
|
|
682
|
+
ownerIds: ReadonlySet<OwnerId>,
|
|
683
|
+
): ReadonlyMap<OwnerId, Owner> => {
|
|
684
|
+
const ownersById = new Map<OwnerId, Owner>();
|
|
685
|
+
|
|
686
|
+
for (const evoluInstance of evoluInstancesByPortId.values()) {
|
|
687
|
+
for (const { owner } of evoluInstance.usedSyncOwners.keys()) {
|
|
688
|
+
if (!ownerIds.has(owner.id) || !("writeKey" in owner)) continue;
|
|
689
|
+
|
|
690
|
+
ownersById.set(owner.id, owner);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return ownersById;
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
const removeAllUsedSyncOwners = (
|
|
698
|
+
evoluInstance: EvoluInstanceState,
|
|
699
|
+
): Task<void, never, SharedEvoluDeps> =>
|
|
700
|
+
unabortable(async (run) => {
|
|
701
|
+
for (const syncOwner of evoluInstance.usedSyncOwners.keys()) {
|
|
702
|
+
while (evoluInstance.usedSyncOwners.has(syncOwner)) {
|
|
703
|
+
await run(toggleUsedSyncOwner(evoluInstance, syncOwner, "remove"));
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return ok();
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
const toggleUsedSyncOwner = (
|
|
710
|
+
evoluInstance: EvoluInstanceState,
|
|
711
|
+
syncOwner: SyncOwner,
|
|
712
|
+
action: "add" | "remove",
|
|
713
|
+
): Task<void, never, SharedEvoluDeps> =>
|
|
714
|
+
unabortable(async (run) => {
|
|
715
|
+
if (action === "add") {
|
|
716
|
+
evoluInstance.usedSyncOwners.increment(syncOwner);
|
|
717
|
+
await run(
|
|
718
|
+
transports.addClaim(syncOwner.owner.id, syncOwner.transports),
|
|
719
|
+
);
|
|
720
|
+
} else {
|
|
721
|
+
evoluInstance.usedSyncOwners.decrement(syncOwner);
|
|
722
|
+
await run(
|
|
723
|
+
transports.removeClaim(syncOwner.owner.id, syncOwner.transports),
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
return ok();
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
return ok({
|
|
730
|
+
createInstance: (
|
|
731
|
+
nativeEvoluPort: NativeMessagePort<EvoluOutput, EvoluInput>,
|
|
732
|
+
nativeDbWorkerPort: NativeMessagePort<DbWorkerInput, DbWorkerOutput>,
|
|
733
|
+
releaseSharedEvolu: () => void,
|
|
734
|
+
): void => {
|
|
735
|
+
assertNotDisposed(moved);
|
|
736
|
+
|
|
737
|
+
const evoluPort = createMessagePort<EvoluOutput, EvoluInput>(
|
|
738
|
+
nativeEvoluPort,
|
|
739
|
+
);
|
|
740
|
+
const dbWorkerPort = createMessagePort<DbWorkerInput, DbWorkerOutput>(
|
|
741
|
+
nativeDbWorkerPort,
|
|
742
|
+
);
|
|
743
|
+
|
|
744
|
+
const evoluPortId = createId(run.deps);
|
|
745
|
+
|
|
746
|
+
const evoluInstance: EvoluInstanceState = {
|
|
747
|
+
evoluPort,
|
|
748
|
+
dbWorkerPort,
|
|
749
|
+
releaseSharedEvolu,
|
|
750
|
+
rowsByQuery: new Map(),
|
|
751
|
+
usedSyncOwners: createRefCountByKey<SyncOwner, OwnerId>({
|
|
752
|
+
lookup: ({ owner: { id } }) => id,
|
|
753
|
+
}),
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
evoluInstancesByPortId.set(evoluPortId, evoluInstance);
|
|
757
|
+
|
|
758
|
+
dbWorkerPort.onMessage = (message) => {
|
|
759
|
+
switch (message.type) {
|
|
760
|
+
case "LeaderAcquired": {
|
|
761
|
+
leaderDbWorkerPort = evoluInstance.dbWorkerPort;
|
|
762
|
+
console.info("leaderAcquired");
|
|
763
|
+
ensureQueueProcessing();
|
|
764
|
+
break;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
case "OnQueuedResponse": {
|
|
768
|
+
callbacks.execute(message.callbackId, message);
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
case "OnConsoleEntry":
|
|
773
|
+
case "OnError": {
|
|
774
|
+
postTabOutput(message);
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
default:
|
|
779
|
+
exhaustiveCheck(message);
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
evoluPort.onMessage = (evoluMessage) => {
|
|
784
|
+
switch (evoluMessage.type) {
|
|
785
|
+
case "Query":
|
|
786
|
+
case "Export": {
|
|
787
|
+
queue.push({
|
|
788
|
+
type: "ForEvolu",
|
|
789
|
+
evoluPortId,
|
|
790
|
+
message: evoluMessage,
|
|
791
|
+
});
|
|
792
|
+
ensureQueueProcessing();
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
case "Mutate": {
|
|
797
|
+
// TODO: Delegate do vsech evolu instances, co to pouzivaji
|
|
798
|
+
queue.push({
|
|
799
|
+
type: "ForEvolu",
|
|
800
|
+
evoluPortId,
|
|
801
|
+
message: evoluMessage,
|
|
802
|
+
});
|
|
803
|
+
ensureQueueProcessing();
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
case "UseOwner": {
|
|
808
|
+
const evoluInstance = evoluInstancesByPortId.get(evoluPortId);
|
|
809
|
+
if (!evoluInstance) break;
|
|
810
|
+
|
|
811
|
+
for (const { owner, action } of evoluMessage.actions) {
|
|
812
|
+
console.debug("useOwner", {
|
|
813
|
+
evoluPortId,
|
|
814
|
+
action,
|
|
815
|
+
ownerId: owner.owner.id,
|
|
816
|
+
transportUrls: owner.transports.map(({ url }) => url),
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
void sharedEvoluRun(
|
|
820
|
+
toggleUsedSyncOwner(evoluInstance, owner, action),
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
case "Dispose": {
|
|
827
|
+
const evoluInstance = evoluInstancesByPortId.get(evoluPortId);
|
|
828
|
+
if (!evoluInstance) break;
|
|
829
|
+
|
|
830
|
+
evoluInstancesByPortId.delete(evoluPortId);
|
|
831
|
+
const hadLastPort = evoluInstancesByPortId.size === 0;
|
|
832
|
+
console.info("evoluDispose", {
|
|
833
|
+
name,
|
|
834
|
+
evoluPortId,
|
|
835
|
+
hadLastPort,
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
// Potential plan: keep DbWorker ports in a SharedResource
|
|
839
|
+
// abstraction instead of deleting them eagerly here. DbWorkers use
|
|
840
|
+
// leader election because SQLite WASM needs a single active owner.
|
|
841
|
+
// When the last Evolu instance is disposed, broadcast shutdown to
|
|
842
|
+
// all DbWorkers so the current leader can dispose itself and the
|
|
843
|
+
// followers can clean up consistently.
|
|
844
|
+
sharedEvoluRun<void, never>(async (run) => {
|
|
845
|
+
await run(removeAllUsedSyncOwners(evoluInstance));
|
|
846
|
+
evoluInstance.usedSyncOwners[Symbol.dispose]();
|
|
847
|
+
return ok();
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
// if (hadLastPort) evoluInstance.releaseSharedEvolu();
|
|
851
|
+
|
|
852
|
+
// TODO: Dispose SharedEvolu on the last port.
|
|
853
|
+
|
|
854
|
+
// TODO: Decided what to do with DbWorker but probably dispose it, but
|
|
855
|
+
// https://bugs.webkit.org/show_bug.cgi?id=301520
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
default:
|
|
860
|
+
exhaustiveCheck(evoluMessage);
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
},
|
|
864
|
+
|
|
865
|
+
requestCreateSyncMessages: (ownerIds): void => {
|
|
866
|
+
const ownersToSync = [...getUsedOwnersById(ownerIds).values()];
|
|
867
|
+
|
|
868
|
+
if (!isNonEmptyArray(ownersToSync)) return;
|
|
869
|
+
|
|
870
|
+
console.debug("requestCreateSyncMessages", {
|
|
871
|
+
ownerIds: ownersToSync.map(({ id }) => id),
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
queue.push({
|
|
875
|
+
type: "ForSharedWorker",
|
|
876
|
+
message: {
|
|
877
|
+
type: "CreateSyncMessages",
|
|
878
|
+
owners: ownersToSync,
|
|
879
|
+
},
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
ensureQueueProcessing();
|
|
883
|
+
},
|
|
884
|
+
|
|
885
|
+
requestApplySyncMessage: (ownerId, inputMessage): void => {
|
|
886
|
+
const owner = getUsedOwnersById(new Set([ownerId])).get(ownerId);
|
|
887
|
+
|
|
888
|
+
if (!owner) return;
|
|
889
|
+
|
|
890
|
+
console.debug("requestApplySyncMessage", {
|
|
891
|
+
ownerId,
|
|
892
|
+
byteLength: inputMessage.byteLength,
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
queue.push({
|
|
896
|
+
type: "ForSharedWorker",
|
|
897
|
+
message: {
|
|
898
|
+
type: "ApplySyncMessage",
|
|
899
|
+
owner,
|
|
900
|
+
inputMessage,
|
|
901
|
+
},
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
ensureQueueProcessing();
|
|
905
|
+
},
|
|
906
|
+
|
|
907
|
+
[Symbol.asyncDispose]: () => moved.disposeAsync(),
|
|
908
|
+
});
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
// | (Typed<"reset"> & {
|
|
912
|
+
// readonly onCompleteId: CallbackId;
|
|
913
|
+
// readonly reload: boolean;
|
|
914
|
+
// readonly restore?: {
|
|
915
|
+
// readonly sqliteSchema: SqliteSchema;
|
|
916
|
+
// readonly mnemonic: Mnemonic;
|
|
917
|
+
// };
|
|
918
|
+
// })
|
|
919
|
+
// | (Typed<"ensureSqliteSchema"> & {
|
|
920
|
+
// readonly sqliteSchema: SqliteSchema;
|
|
921
|
+
// })
|
|
922
|
+
// | (Typed<"export"> & {
|
|
923
|
+
// readonly onCompleteId: CallbackId;
|
|
924
|
+
// })
|
|
925
|
+
// | (Typed<"useOwner"> & {
|
|
926
|
+
// readonly use: boolean;
|
|
927
|
+
// readonly owner: SyncOwner;
|
|
928
|
+
// });
|
|
929
|
+
// | (Typed<"onReset"> & {
|
|
930
|
+
// readonly onCompleteId: CallbackId;
|
|
931
|
+
// readonly reload: boolean;
|
|
932
|
+
// })
|