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