@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.
Files changed (73) hide show
  1. package/dist/src/Assert.d.ts +5 -5
  2. package/dist/src/Assert.d.ts.map +1 -1
  3. package/dist/src/Assert.js +4 -4
  4. package/dist/src/Identicon.js +4 -4
  5. package/dist/src/LockManager.d.ts +74 -0
  6. package/dist/src/LockManager.d.ts.map +1 -0
  7. package/dist/src/LockManager.js +103 -0
  8. package/dist/src/Microtask.d.ts.map +1 -1
  9. package/dist/src/Microtask.js +90 -26
  10. package/dist/src/Object.d.ts.map +1 -1
  11. package/dist/src/RefCount.d.ts.map +1 -1
  12. package/dist/src/RefCount.js +153 -68
  13. package/dist/src/Resource.d.ts +6 -6
  14. package/dist/src/Resource.d.ts.map +1 -1
  15. package/dist/src/Resource.js +211 -162
  16. package/dist/src/Schedule.d.ts +5 -5
  17. package/dist/src/Schedule.d.ts.map +1 -1
  18. package/dist/src/Schedule.js +53 -44
  19. package/dist/src/Sqlite.d.ts +2 -0
  20. package/dist/src/Sqlite.d.ts.map +1 -1
  21. package/dist/src/Sqlite.js +64 -40
  22. package/dist/src/Task.d.ts +6 -33
  23. package/dist/src/Task.d.ts.map +1 -1
  24. package/dist/src/Task.js +213 -204
  25. package/dist/src/Test.d.ts +4 -4
  26. package/dist/src/Time.d.ts +5 -0
  27. package/dist/src/Time.d.ts.map +1 -1
  28. package/dist/src/Time.js +5 -0
  29. package/dist/src/Type.d.ts +7 -0
  30. package/dist/src/Type.d.ts.map +1 -1
  31. package/dist/src/Type.js +7 -0
  32. package/dist/src/WebSocket.d.ts.map +1 -1
  33. package/dist/src/WebSocket.js +7 -6
  34. package/dist/src/Worker.d.ts +42 -8
  35. package/dist/src/Worker.d.ts.map +1 -1
  36. package/dist/src/Worker.js +298 -75
  37. package/dist/src/index.d.ts +1 -0
  38. package/dist/src/index.d.ts.map +1 -1
  39. package/dist/src/index.js +1 -0
  40. package/dist/src/local-first/Db.d.ts +5 -4
  41. package/dist/src/local-first/Db.d.ts.map +1 -1
  42. package/dist/src/local-first/Db.js +44 -38
  43. package/dist/src/local-first/Evolu.d.ts +3 -2
  44. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  45. package/dist/src/local-first/Evolu.js +90 -80
  46. package/dist/src/local-first/Owner.d.ts.map +1 -1
  47. package/dist/src/local-first/Shared.d.ts +65 -54
  48. package/dist/src/local-first/Shared.d.ts.map +1 -1
  49. package/dist/src/local-first/Shared.js +387 -373
  50. package/package.json +1 -1
  51. package/src/Array.ts +8 -8
  52. package/src/Assert.ts +5 -5
  53. package/src/Identicon.ts +4 -4
  54. package/src/LockManager.ts +181 -0
  55. package/src/Microtask.ts +17 -11
  56. package/src/Object.ts +3 -3
  57. package/src/RefCount.ts +25 -21
  58. package/src/Resource.ts +63 -56
  59. package/src/Schedule.ts +70 -48
  60. package/src/Set.ts +4 -4
  61. package/src/Sqlite.ts +41 -30
  62. package/src/Task.ts +77 -120
  63. package/src/Test.ts +4 -4
  64. package/src/Time.ts +7 -0
  65. package/src/Type.ts +9 -0
  66. package/src/WebSocket.ts +7 -6
  67. package/src/Worker.ts +240 -49
  68. package/src/index.ts +1 -0
  69. package/src/local-first/Db.ts +69 -46
  70. package/src/local-first/Evolu.ts +74 -68
  71. package/src/local-first/Owner.ts +1 -1
  72. package/src/local-first/Query.ts +1 -1
  73. package/src/local-first/Shared.ts +472 -464
@@ -1,9 +1,61 @@
1
1
  /**
2
- * Platform-agnostic Worker abstractions.
2
+ * Typed, disposable worker and messaging primitives.
3
3
  *
4
4
  * @module
5
5
  */
6
- import { assert } from "./Assert.js";
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 { assert, assertNotDisposed } from "./Assert.js";
7
59
  import { testWaitForMacrotask } from "./Test.js";
8
60
  /**
9
61
  * Creates an in-memory {@link Worker}.
@@ -38,30 +90,55 @@ export const createSharedWorker = (initWorker) => {
38
90
  * support. Message delivery is asynchronous in-process.
39
91
  */
40
92
  export const createMessageChannel = () => {
41
- const state1 = {
42
- handler: null,
43
- queue: [],
44
- flushScheduled: false,
45
- };
46
- const state2 = {
47
- handler: null,
48
- queue: [],
49
- flushScheduled: false,
50
- };
51
- const native1 = createNativeMessagePortToken();
52
- const native2 = createNativeMessagePortToken();
53
- const port1 = createTestPort(state1, state2, native1);
54
- const port2 = createTestPort(state2, state1, native2);
55
- nativePortRegistry.set(native1, port1);
56
- nativePortRegistry.set(native2, port2);
57
- return {
58
- port1,
59
- port2,
60
- [Symbol.dispose]: () => {
61
- port1[Symbol.dispose]();
62
- port2[Symbol.dispose]();
63
- },
64
- };
93
+ const env_1 = { stack: [], error: void 0, hasError: false };
94
+ try {
95
+ const state1 = {
96
+ handler: null,
97
+ queue: [],
98
+ flushScheduled: false,
99
+ flushTimeoutId: null,
100
+ ownerCount: 0,
101
+ pendingTransferCount: 0,
102
+ };
103
+ const state2 = {
104
+ handler: null,
105
+ queue: [],
106
+ flushScheduled: false,
107
+ flushTimeoutId: null,
108
+ ownerCount: 0,
109
+ pendingTransferCount: 0,
110
+ };
111
+ const native1 = createNativeMessagePortToken();
112
+ const native2 = createNativeMessagePortToken();
113
+ const port1Registration = {
114
+ receive: state1,
115
+ peerReceive: state2,
116
+ native: native1,
117
+ };
118
+ const port2Registration = {
119
+ receive: state2,
120
+ peerReceive: state1,
121
+ native: native2,
122
+ };
123
+ const disposer = __addDisposableResource(env_1, new DisposableStack(), false);
124
+ const port1 = disposer.use(createTestPort(port1Registration));
125
+ const port2 = disposer.use(createTestPort(port2Registration));
126
+ const disposables = disposer.move();
127
+ nativePortRegistry.set(native1, port1Registration);
128
+ nativePortRegistry.set(native2, port2Registration);
129
+ return {
130
+ port1,
131
+ port2,
132
+ [Symbol.dispose]: () => disposables.dispose(),
133
+ };
134
+ }
135
+ catch (e_1) {
136
+ env_1.error = e_1;
137
+ env_1.hasError = true;
138
+ }
139
+ finally {
140
+ __disposeResources(env_1);
141
+ }
65
142
  };
66
143
  /**
67
144
  * Creates an in-memory {@link MessagePort} from a native token.
@@ -70,10 +147,72 @@ export const createMessageChannel = () => {
70
147
  * support. Message delivery through returned ports is asynchronous in-process.
71
148
  */
72
149
  export const createMessagePort = (nativePort) => {
73
- const pair = nativePortRegistry.get(nativePort);
74
- assert(pair, "Unknown native port — did you transfer it?");
75
- return pair;
150
+ const registration = nativePortRegistry.get(nativePort);
151
+ assert(registration, "Unknown native port — did you transfer it?");
152
+ return createTestPort(registration);
76
153
  };
154
+ /**
155
+ * Creates an in-memory {@link BroadcastChannel}.
156
+ *
157
+ * This is a memory-only fallback for platforms without native BroadcastChannel
158
+ * support. Message delivery is asynchronous in-process.
159
+ */
160
+ export const createBroadcastChannel = (name) => {
161
+ const env_2 = { stack: [], error: void 0, hasError: false };
162
+ try {
163
+ let onMessageHandler = null;
164
+ const dispatch = (message) => {
165
+ onMessageHandler?.(message);
166
+ };
167
+ let dispatches = broadcastChannelDispatchesByName.get(name);
168
+ if (!dispatches) {
169
+ dispatches = new Set();
170
+ broadcastChannelDispatchesByName.set(name, dispatches);
171
+ }
172
+ dispatches.add(dispatch);
173
+ const disposer = __addDisposableResource(env_2, new DisposableStack(), false);
174
+ disposer.defer(() => {
175
+ onMessageHandler = null;
176
+ dispatches.delete(dispatch);
177
+ if (dispatches.size === 0)
178
+ broadcastChannelDispatchesByName.delete(name);
179
+ });
180
+ const disposables = disposer.move();
181
+ return {
182
+ postMessage: (message) => {
183
+ assertNotDisposed(disposables);
184
+ const dispatches = broadcastChannelDispatchesByName.get(name);
185
+ assert(dispatches, "Expected broadcast channel dispatches");
186
+ for (const otherDispatch of [...dispatches]) {
187
+ if (otherDispatch === dispatch)
188
+ continue;
189
+ globalThis.setTimeout(() => {
190
+ if (!dispatches.has(otherDispatch))
191
+ return;
192
+ otherDispatch(message);
193
+ }, 0);
194
+ }
195
+ },
196
+ get onMessage() {
197
+ return disposables.disposed ? null : onMessageHandler;
198
+ },
199
+ set onMessage(fn) {
200
+ if (disposables.disposed)
201
+ return;
202
+ onMessageHandler = fn;
203
+ },
204
+ [Symbol.dispose]: () => disposables.dispose(),
205
+ };
206
+ }
207
+ catch (e_2) {
208
+ env_2.error = e_2;
209
+ env_2.hasError = true;
210
+ }
211
+ finally {
212
+ __disposeResources(env_2);
213
+ }
214
+ };
215
+ const broadcastChannelDispatchesByName = new Map();
77
216
  /**
78
217
  * Creates a connected {@link TestWorker} for testing.
79
218
  *
@@ -110,25 +249,62 @@ export const testCreateSharedWorker = () => {
110
249
  * {@link testCreateMessagePort} can look them up by their native token.
111
250
  */
112
251
  export const testCreateMessageChannel = () => {
113
- const channel = createMessageChannel();
114
- let disposed = false;
115
- return {
116
- port1: channel.port1,
117
- port2: channel.port2,
118
- isDisposed: () => disposed,
119
- [Symbol.dispose]: () => {
120
- disposed = true;
121
- channel[Symbol.dispose]();
122
- },
123
- };
252
+ const env_3 = { stack: [], error: void 0, hasError: false };
253
+ try {
254
+ const disposer = __addDisposableResource(env_3, new DisposableStack(), false);
255
+ const channel = disposer.use(createMessageChannel());
256
+ const disposables = disposer.move();
257
+ return {
258
+ port1: channel.port1,
259
+ port2: channel.port2,
260
+ isDisposed: () => disposables.disposed,
261
+ [Symbol.dispose]: () => disposables.dispose(),
262
+ };
263
+ }
264
+ catch (e_3) {
265
+ env_3.error = e_3;
266
+ env_3.hasError = true;
267
+ }
268
+ finally {
269
+ __disposeResources(env_3);
270
+ }
124
271
  };
125
272
  /** Creates an in-memory {@link CreateMessagePort} for testing. */
126
273
  export const testCreateMessagePort = (nativePort) => createMessagePort(nativePort);
274
+ /** Creates an in-memory {@link BroadcastChannel} for testing. */
275
+ export const testCreateBroadcastChannel = (name) => {
276
+ const env_4 = { stack: [], error: void 0, hasError: false };
277
+ try {
278
+ const disposer = __addDisposableResource(env_4, new DisposableStack(), false);
279
+ const channel = disposer.use(createBroadcastChannel(name));
280
+ const disposables = disposer.move();
281
+ return {
282
+ postMessage: channel.postMessage,
283
+ get onMessage() {
284
+ return channel.onMessage;
285
+ },
286
+ set onMessage(value) {
287
+ channel.onMessage = value;
288
+ },
289
+ isDisposed: () => disposables.disposed,
290
+ [Symbol.dispose]: () => disposables.dispose(),
291
+ };
292
+ }
293
+ catch (e_4) {
294
+ env_4.error = e_4;
295
+ env_4.hasError = true;
296
+ }
297
+ finally {
298
+ __disposeResources(env_4);
299
+ }
300
+ };
127
301
  /**
128
- * Waits long enough for multi-hop in-memory worker message delivery in tests.
302
+ * Waits the minimal time necessary for in-memory worker message delivery in
303
+ * tests.
129
304
  *
130
- * Some flows require several queued macrotasks across worker/message-port
131
- * boundaries; this helper advances that pipeline deterministically.
305
+ * This is better than `vi.waitFor` when a test only needs to flush the known
306
+ * in-memory worker/message-port pipeline. It encodes that transport boundary
307
+ * once, avoids polling, and keeps worker-focused tests fast and explicit.
132
308
  */
133
309
  export const testWaitForWorkerMessage = async () => {
134
310
  await testWaitForMacrotask();
@@ -184,43 +360,90 @@ const createMemorySharedWorkerPair = () => {
184
360
  };
185
361
  return { worker, self, connect };
186
362
  };
187
- const createPort = (receive, peerReceive, native) => {
188
- const scheduleFlush = (state) => {
189
- if (state.flushScheduled)
190
- return;
191
- state.flushScheduled = true;
192
- // Native worker messages are task-queued; use macrotask timing.
193
- setTimeout(() => {
194
- state.flushScheduled = false;
195
- const handler = state.handler;
196
- if (!handler)
363
+ const createPort = ({ receive, peerReceive, native, }) => {
364
+ const env_5 = { stack: [], error: void 0, hasError: false };
365
+ try {
366
+ const disposer = __addDisposableResource(env_5, new DisposableStack(), false);
367
+ if (receive.pendingTransferCount > 0)
368
+ receive.pendingTransferCount -= 1;
369
+ else
370
+ receive.ownerCount += 1;
371
+ disposer.defer(() => {
372
+ receive.ownerCount -= 1;
373
+ if (receive.ownerCount > 0)
197
374
  return;
198
- for (const message of state.queue.splice(0)) {
199
- handler(message);
375
+ nativePortRegistry.delete(native);
376
+ if (receive.flushTimeoutId != null) {
377
+ globalThis.clearTimeout(receive.flushTimeoutId);
378
+ receive.flushTimeoutId = null;
200
379
  }
201
- }, 0);
202
- };
203
- return {
204
- postMessage: (message) => {
205
- peerReceive.queue.push(message);
206
- scheduleFlush(peerReceive);
207
- },
208
- get onMessage() {
209
- return receive.handler;
210
- },
211
- set onMessage(fn) {
212
- receive.handler = fn;
213
- if (fn)
214
- scheduleFlush(receive);
215
- },
216
- native,
217
- [Symbol.dispose]: () => {
218
380
  receive.handler = null;
219
381
  receive.flushScheduled = false;
220
- },
221
- };
382
+ receive.queue.length = 0;
383
+ });
384
+ const disposables = disposer.move();
385
+ const scheduleFlush = (state) => {
386
+ if (state.flushScheduled || state.ownerCount === 0)
387
+ return;
388
+ state.flushScheduled = true;
389
+ // Native worker messages are task-queued; use macrotask timing.
390
+ state.flushTimeoutId = globalThis.setTimeout(() => {
391
+ state.flushTimeoutId = null;
392
+ state.flushScheduled = false;
393
+ if (state.ownerCount === 0)
394
+ return;
395
+ const firstHandler = state.handler;
396
+ if (!firstHandler)
397
+ return;
398
+ for (const message of state.queue.splice(0)) {
399
+ const handler = state.handler;
400
+ if (!handler)
401
+ return;
402
+ handler(message);
403
+ }
404
+ }, 0);
405
+ };
406
+ return {
407
+ postMessage: (message, transfer) => {
408
+ if (disposables.disposed ||
409
+ receive.ownerCount === 0 ||
410
+ peerReceive.ownerCount === 0)
411
+ return;
412
+ for (const transferable of transfer ?? []) {
413
+ if (transferable instanceof globalThis.ArrayBuffer)
414
+ continue;
415
+ const registration = nativePortRegistry.get(transferable);
416
+ if (!registration)
417
+ continue;
418
+ registration.receive.ownerCount += 1;
419
+ registration.receive.pendingTransferCount += 1;
420
+ }
421
+ peerReceive.queue.push(message);
422
+ scheduleFlush(peerReceive);
423
+ },
424
+ get onMessage() {
425
+ return disposables.disposed ? null : receive.handler;
426
+ },
427
+ set onMessage(fn) {
428
+ if (disposables.disposed || receive.ownerCount === 0)
429
+ return;
430
+ receive.handler = fn;
431
+ if (fn)
432
+ scheduleFlush(receive);
433
+ },
434
+ native,
435
+ [Symbol.dispose]: () => disposables.dispose(),
436
+ };
437
+ }
438
+ catch (e_5) {
439
+ env_5.error = e_5;
440
+ env_5.hasError = true;
441
+ }
442
+ finally {
443
+ __disposeResources(env_5);
444
+ }
222
445
  };
223
- const createTestPort = (receive, peerReceive, native) => createPort(receive, peerReceive, native);
446
+ const createTestPort = (registration) => createPort(registration);
224
447
  const createNativeMessagePortToken = () => ({});
225
448
  /**
226
449
  * Registry mapping native port tokens to their in-memory port counterparts.
@@ -16,6 +16,7 @@ export * from "./Eq.js";
16
16
  export * from "./Error.js";
17
17
  export * from "./Function.js";
18
18
  export * from "./Identicon.js";
19
+ export * from "./LockManager.js";
19
20
  export * from "./Lookup.js";
20
21
  export * from "./Microtask.js";
21
22
  export * from "./Number.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAG5B,YAAY,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EACV,YAAY,EACZ,KAAK,EACL,WAAW,EACX,SAAS,EACT,UAAU,GACX,MAAM,wBAAwB,CAAC;AAChC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,YAAY,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,SAAS,EACT,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,GAAG,GACT,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EACV,mBAAmB,EACnB,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,cAAc,EACd,yBAAyB,EACzB,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,YAAY,GACb,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAIV,SAAS,GACV,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,SAAS,EACT,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,cAAc,EACd,6BAA6B,EAC7B,mBAAmB,EACnB,cAAc,EACd,4BAA4B,GAC7B,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAG5B,YAAY,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EACV,YAAY,EACZ,KAAK,EACL,WAAW,EACX,SAAS,EACT,UAAU,GACX,MAAM,wBAAwB,CAAC;AAChC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,YAAY,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,SAAS,EACT,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,GAAG,GACT,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EACV,mBAAmB,EACnB,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,cAAc,EACd,yBAAyB,EACzB,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,YAAY,GACb,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAIV,SAAS,GACV,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,SAAS,EACT,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,cAAc,EACd,6BAA6B,EAC7B,mBAAmB,EACnB,cAAc,EACd,4BAA4B,GAC7B,MAAM,4BAA4B,CAAC"}
package/dist/src/index.js CHANGED
@@ -16,6 +16,7 @@ export * from "./Eq.js";
16
16
  export * from "./Error.js";
17
17
  export * from "./Function.js";
18
18
  export * from "./Identicon.js";
19
+ export * from "./LockManager.js";
19
20
  export * from "./Lookup.js";
20
21
  export * from "./Microtask.js";
21
22
  export * from "./Number.js";
@@ -5,14 +5,15 @@
5
5
  */
6
6
  import type { ConsoleLevel } from "../Console.js";
7
7
  import { EncryptionKey } from "../Crypto.js";
8
+ import type { LockManagerDep } from "../LockManager.js";
8
9
  import type { CreateSqliteDriverDep, SqliteSchema } from "../Sqlite.js";
9
- import { type LeaderLockDep, type Task } from "../Task.js";
10
+ import { type Task } from "../Task.js";
10
11
  import type { Name } from "../Type.js";
11
- import type { NativeMessagePort, Worker, WorkerDeps, WorkerSelf } from "../Worker.js";
12
+ import type { CreateBroadcastChannelDep, NativeMessagePort, Worker, WorkerDeps, WorkerSelf } from "../Worker.js";
12
13
  import type { DbWorkerInput, DbWorkerOutput } from "./Shared.js";
13
14
  export type DbWorker = Worker<DbWorkerInit>;
14
15
  export interface DbWorkerInit {
15
- readonly type: "Init";
16
+ readonly type: "DbWorkerInit";
16
17
  readonly name: Name;
17
18
  readonly consoleLevel: ConsoleLevel;
18
19
  readonly sqliteSchema: SqliteSchema;
@@ -24,6 +25,6 @@ export type CreateDbWorker = () => DbWorker;
24
25
  export interface CreateDbWorkerDep {
25
26
  readonly createDbWorker: CreateDbWorker;
26
27
  }
27
- export type DbWorkerDeps = WorkerDeps & LeaderLockDep & CreateSqliteDriverDep;
28
+ export type DbWorkerDeps = WorkerDeps & CreateBroadcastChannelDep & LockManagerDep & CreateSqliteDriverDep;
28
29
  export declare const startDbWorker: (self: WorkerSelf<DbWorkerInit>) => Task<void, never, DbWorkerDeps>;
29
30
  //# sourceMappingURL=Db.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Db.d.ts","sourceRoot":"","sources":["../../../src/local-first/Db.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,aAAa,EAGd,MAAM,cAAc,CAAC;AAItB,OAAO,KAAK,EACV,qBAAqB,EAGrB,YAAY,EACb,MAAM,cAAc,CAAC;AAUtB,OAAO,EAAY,KAAK,aAAa,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAErE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AASvC,OAAO,KAAK,EACV,iBAAiB,EACjB,MAAM,EACN,UAAU,EACV,UAAU,EACX,MAAM,cAAc,CAAC;AAqBtB,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA8BjE,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;AAE5C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;CACjE;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;CACzC;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,aAAa,GAAG,qBAAqB,CAAC;AAE9E,eAAO,MAAM,aAAa,GACvB,MAAM,UAAU,CAAC,YAAY,CAAC,KAAG,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,YAAY,CAoO/D,CAAC"}
1
+ {"version":3,"file":"Db.d.ts","sourceRoot":"","sources":["../../../src/local-first/Db.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,aAAa,EAGd,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAIxD,OAAO,KAAK,EACV,qBAAqB,EAGrB,YAAY,EACb,MAAM,cAAc,CAAC;AAUtB,OAAO,EAAY,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AASvC,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,MAAM,EACN,UAAU,EACV,UAAU,EACX,MAAM,cAAc,CAAC;AAqBtB,OAAO,KAAK,EAEV,aAAa,EACb,cAAc,EAGf,MAAM,aAAa,CAAC;AA+BrB,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;AAE5C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;CACjE;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;CACzC;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,GACnC,yBAAyB,GACzB,cAAc,GACd,qBAAqB,CAAC;AAExB,eAAO,MAAM,aAAa,GACvB,MAAM,UAAU,CAAC,YAAY,CAAC,KAAG,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,YAAY,CA8O/D,CAAC"}
@@ -59,6 +59,7 @@ import { appendToArray, firstInArray, } from "../Array.js";
59
59
  import { assert, assertNonEmptyReadonlyArray, assertType } from "../Assert.js";
60
60
  import { EncryptionKey, } from "../Crypto.js";
61
61
  import { exhaustiveCheck, lazyFalse, lazyVoid } from "../Function.js";
62
+ import { acquireLeaderLock } from "../LockManager.js";
62
63
  import { createRecord, getProperty, objectToEntries } from "../Object.js";
63
64
  import { ok } from "../Result.js";
64
65
  import { booleanToSqliteBoolean, createSqlite, sql, SqliteBoolean, sqliteBooleanToBoolean, sqliteQueryStringToSqliteQuery, SqliteValue, } from "../Sqlite.js";
@@ -68,16 +69,18 @@ import { Id, IdBytes, idBytesToId, idToIdBytes, onePositiveInt, } from "../Type.
68
69
  import { ownerIdBytesToOwnerId, ownerIdToOwnerIdBytes } from "./Owner.js";
69
70
  import { applyProtocolMessageAsClient, createProtocolMessageForSync, decryptAndDecodeDbChange, encodeAndEncryptDbChange, protocolVersion, SubscriptionFlags, } from "./Protocol.js";
70
71
  import { ensureSqliteSchema, getEvoluSqliteSchema, systemColumns, } from "./Schema.js";
72
+ import { consoleEntryOrErrorBroadcastChannelName } from "./Shared.js";
71
73
  import { createBaseSqliteStorage, createBaseSqliteStorageTables, DbChange, getTimestampInsertStrategy, readOwnerUsageOrDefault, updateOwnerUsage, } from "./Storage.js";
72
74
  import { createInitialTimestamp, defaultTimestampMaxDrift, receiveTimestamp, sendTimestamp, TimestampBytes, timestampBytesToTimestamp, timestampToTimestampBytes, } from "./Timestamp.js";
73
- export const startDbWorker = (self) => async (_run) => {
75
+ export const startDbWorker = (self) => async (run) => {
74
76
  const env_1 = { stack: [], error: void 0, hasError: false };
75
77
  try {
76
- const stack = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
77
- const run = stack.use(_run.create());
78
- const { deps } = run;
78
+ const disposer = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
79
+ disposer.use(self);
80
+ const dbWorkerRun = disposer.use(run.create());
81
+ const { deps } = dbWorkerRun;
79
82
  let initialized = false;
80
- const initMessage = await run.orThrow(callback(({ ok }) => {
83
+ const initMessage = await dbWorkerRun.orThrow(callback(({ ok }) => {
81
84
  self.onMessage = (message) => {
82
85
  assert(!initialized, "DbWorker must be initialized only once");
83
86
  initialized = true;
@@ -86,24 +89,29 @@ export const startDbWorker = (self) => async (_run) => {
86
89
  }));
87
90
  const console = deps.console.child(initMessage.name).child("DbWorker");
88
91
  console.setLevel(initMessage.consoleLevel);
89
- console.info("start DbWorker");
90
- const port = stack.use(deps.createMessagePort(initMessage.port));
91
- stack.defer(deps.consoleStoreOutputEntry.subscribe(() => {
92
+ const port = disposer.use(deps.createMessagePort(initMessage.port));
93
+ const consoleEntryOrErrorBroadcastChannel = disposer.use(deps.createBroadcastChannel(consoleEntryOrErrorBroadcastChannelName));
94
+ disposer.defer(deps.consoleStoreOutputEntry.subscribe(() => {
92
95
  const entry = deps.consoleStoreOutputEntry.get();
93
96
  if (entry)
94
- port.postMessage({ type: "OnConsoleEntry", entry });
97
+ consoleEntryOrErrorBroadcastChannel.postMessage({
98
+ type: "ConsoleEntry",
99
+ entry,
100
+ });
95
101
  }));
96
- stack.use(await run.orThrow(deps.leaderLock.lock(initMessage.name)));
97
- if (stack.disposed)
98
- return ok();
102
+ console.debug("createDbWorker");
103
+ disposer.use(await dbWorkerRun.orThrow(acquireLeaderLock(initMessage.name)));
99
104
  port.postMessage({ type: "LeaderAcquired", name: initMessage.name });
100
- const sqlite = stack.use(await run.orThrow(createSqlite(initMessage.name, initMessage.memoryOnly
105
+ const sqlite = disposer.use(await dbWorkerRun.orThrow(createSqlite(initMessage.name, initMessage.memoryOnly
101
106
  ? { mode: "memory" }
102
107
  : { mode: "encrypted", encryptionKey: initMessage.encryptionKey })));
103
108
  console.debug("SQLite created");
104
- const baseSqliteStorage = createBaseSqliteStorage({ sqlite, ...run.deps });
109
+ const baseSqliteStorage = createBaseSqliteStorage({
110
+ sqlite,
111
+ ...dbWorkerRun.deps,
112
+ });
105
113
  const dbDeps = {
106
- ...run.deps,
114
+ ...dbWorkerRun.deps,
107
115
  sqlite,
108
116
  sqliteSchema: initMessage.sqliteSchema,
109
117
  baseSqliteStorage,
@@ -120,27 +128,22 @@ export const startDbWorker = (self) => async (_run) => {
120
128
  });
121
129
  const storage = createClientStorage({ ...dbDeps, clock })({
122
130
  onError: (error) => {
123
- port.postMessage({ type: "OnError", error });
131
+ consoleEntryOrErrorBroadcastChannel.postMessage({
132
+ type: "Error",
133
+ error,
134
+ });
124
135
  },
125
136
  });
126
- // TODO: Call on dispose message.
127
- const _moved = stack.move();
128
- const runWithStorage = run.addDeps({ storage });
129
- /**
130
- * SharedWorker retries until some leader replies, so this worker must
131
- * ignore callback IDs it already completed.
132
- *
133
- * The Set intentionally lives for one leader's lifetime. If that leader tab
134
- * closes, retries go to a new leader with a fresh Set. Revisit this only if
135
- * memory pressure shows up; even millions of short callback IDs are
136
- * acceptable here, and naive eviction could let a still-retried request run
137
- * again.
138
- */
139
- const processedRequestIds = new Set();
140
- port.onMessage = ({ callbackId, request }) => {
141
- if (processedRequestIds.has(callbackId))
137
+ const disposables = disposer.move();
138
+ const dbWorkerRunWithStorage = dbWorkerRun.addDeps({ storage });
139
+ port.onMessage = (input) => {
140
+ if (input.type === "Dispose") {
141
+ if (!disposables.disposed)
142
+ console.debug("disposeDbWorker");
143
+ void disposables.disposeAsync();
142
144
  return;
143
- processedRequestIds.add(callbackId);
145
+ }
146
+ const { callbackId, request } = input;
144
147
  const postQueuedResponse = (response) => {
145
148
  port.postMessage({ type: "OnQueuedResponse", callbackId, response }, response.type === "ForEvolu" && response.message.type === "Export"
146
149
  ? [response.message.file.buffer]
@@ -152,12 +155,15 @@ export const startDbWorker = (self) => async (_run) => {
152
155
  case "Mutate": {
153
156
  const result = handleMutation({ ...dbDeps, clock })(request.message);
154
157
  if (!result.ok) {
155
- port.postMessage({ type: "OnError", error: result.error });
158
+ consoleEntryOrErrorBroadcastChannel.postMessage({
159
+ type: "Error",
160
+ error: result.error,
161
+ });
156
162
  }
157
163
  else {
158
164
  postQueuedResponse({
159
165
  type: "ForEvolu",
160
- evoluPortId: request.evoluPortId,
166
+ id: request.id,
161
167
  message: result.value,
162
168
  });
163
169
  }
@@ -166,7 +172,7 @@ export const startDbWorker = (self) => async (_run) => {
166
172
  case "Query":
167
173
  postQueuedResponse({
168
174
  type: "ForEvolu",
169
- evoluPortId: request.evoluPortId,
175
+ id: request.id,
170
176
  message: {
171
177
  type: "Query",
172
178
  rowsByQuery: loadQueries(dbDeps)(request.message.queries),
@@ -176,7 +182,7 @@ export const startDbWorker = (self) => async (_run) => {
176
182
  case "Export":
177
183
  postQueuedResponse({
178
184
  type: "ForEvolu",
179
- evoluPortId: request.evoluPortId,
185
+ id: request.id,
180
186
  message: {
181
187
  type: "Export",
182
188
  file: dbDeps.sqlite.export(),
@@ -211,7 +217,7 @@ export const startDbWorker = (self) => async (_run) => {
211
217
  }
212
218
  case "ApplySyncMessage": {
213
219
  const { owner, inputMessage } = request.message;
214
- runWithStorage(async (run) => {
220
+ dbWorkerRunWithStorage(async (run) => {
215
221
  storage.setRequestContext(owner.encryptionKey);
216
222
  const result = await run(applyProtocolMessageAsClient(inputMessage, {
217
223
  writeKey: owner.writeKey,