@evolu/common 8.0.0-next.3 → 8.0.0-next.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Assert.d.ts +5 -5
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +4 -4
- package/dist/src/Identicon.js +4 -4
- package/dist/src/LockManager.d.ts +74 -0
- package/dist/src/LockManager.d.ts.map +1 -0
- package/dist/src/LockManager.js +103 -0
- package/dist/src/Microtask.d.ts.map +1 -1
- package/dist/src/Microtask.js +90 -26
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/RefCount.d.ts.map +1 -1
- package/dist/src/RefCount.js +153 -68
- package/dist/src/Resource.d.ts +6 -6
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Resource.js +211 -162
- package/dist/src/Schedule.d.ts +5 -5
- package/dist/src/Schedule.d.ts.map +1 -1
- package/dist/src/Schedule.js +53 -44
- package/dist/src/Sqlite.d.ts +2 -0
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +64 -40
- package/dist/src/Task.d.ts +6 -33
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +213 -204
- package/dist/src/Test.d.ts +4 -4
- package/dist/src/Time.d.ts +5 -0
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +5 -0
- package/dist/src/Type.d.ts +7 -0
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +7 -0
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +7 -6
- package/dist/src/Worker.d.ts +42 -8
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +292 -112
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/local-first/Db.d.ts +5 -4
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +44 -38
- package/dist/src/local-first/Evolu.d.ts +3 -2
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +90 -80
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Shared.d.ts +65 -54
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +387 -373
- package/package.json +1 -1
- package/src/Array.ts +8 -8
- package/src/Assert.ts +5 -5
- package/src/Identicon.ts +4 -4
- package/src/LockManager.ts +181 -0
- package/src/Microtask.ts +17 -11
- package/src/Object.ts +3 -3
- package/src/RefCount.ts +25 -21
- package/src/Resource.ts +63 -56
- package/src/Schedule.ts +70 -48
- package/src/Set.ts +4 -4
- package/src/Sqlite.ts +41 -30
- package/src/Task.ts +77 -120
- package/src/Test.ts +4 -4
- package/src/Time.ts +7 -0
- package/src/Type.ts +9 -0
- package/src/WebSocket.ts +7 -6
- package/src/Worker.ts +195 -53
- package/src/index.ts +1 -0
- package/src/local-first/Db.ts +69 -46
- package/src/local-first/Evolu.ts +74 -68
- package/src/local-first/Owner.ts +1 -1
- package/src/local-first/Query.ts +1 -1
- package/src/local-first/Shared.ts +472 -464
package/src/Worker.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Typed, disposable worker and messaging primitives.
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { assert } from "./Assert.js";
|
|
7
|
+
import { assert, assertNotDisposed } from "./Assert.js";
|
|
8
8
|
import type { Brand } from "./Brand.js";
|
|
9
9
|
import type { ConsoleDep, ConsoleStoreOutputEntryDep } from "./Console.js";
|
|
10
10
|
import { testWaitForMacrotask } from "./Test.js";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Typed, disposable Worker.
|
|
14
14
|
*
|
|
15
15
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Worker
|
|
16
16
|
*/
|
|
@@ -20,7 +20,7 @@ export interface Worker<Input, Output = never> extends MessagePort<
|
|
|
20
20
|
> {}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Typed, disposable SharedWorker.
|
|
24
24
|
*
|
|
25
25
|
* A shared worker is shared across multiple clients (tabs, windows, iframes)
|
|
26
26
|
* and provides a port for bidirectional communication with each client.
|
|
@@ -33,7 +33,7 @@ export interface SharedWorker<Input, Output = never> extends Disposable {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
36
|
+
* Typed, disposable MessagePort.
|
|
37
37
|
*
|
|
38
38
|
* Note: There is no reliable way to detect when a port is closed or
|
|
39
39
|
* disconnected. Calling `postMessage` on a disposed port does not throw — it
|
|
@@ -114,7 +114,7 @@ export type WorkerDeps = ConsoleDep &
|
|
|
114
114
|
CreateMessagePortDep;
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
|
-
*
|
|
117
|
+
* Typed, disposable MessageChannel.
|
|
118
118
|
*
|
|
119
119
|
* Creates two entangled ports: keep one and transfer the other (e.g., to a
|
|
120
120
|
* SharedWorker via `postMessage` with `transfer`). Messages sent to one port
|
|
@@ -178,6 +178,31 @@ export interface CreateMessageChannelDep {
|
|
|
178
178
|
readonly createMessageChannel: CreateMessageChannel;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Typed, disposable BroadcastChannel.
|
|
183
|
+
*
|
|
184
|
+
* Broadcast channels deliver messages to other channels opened with the same
|
|
185
|
+
* name. The sending channel does not receive its own messages.
|
|
186
|
+
*
|
|
187
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel
|
|
188
|
+
*/
|
|
189
|
+
export interface BroadcastChannel<Input, Output = Input> extends Disposable {
|
|
190
|
+
/** Posts a message to other channels with the same name. */
|
|
191
|
+
readonly postMessage: (message: Input) => void;
|
|
192
|
+
|
|
193
|
+
/** Handler for incoming broadcast messages. */
|
|
194
|
+
onMessage: ((message: Output) => void) | null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Factory function to create a {@link BroadcastChannel}. */
|
|
198
|
+
export type CreateBroadcastChannel = <Input, Output = Input>(
|
|
199
|
+
name: string,
|
|
200
|
+
) => BroadcastChannel<Input, Output>;
|
|
201
|
+
|
|
202
|
+
export interface CreateBroadcastChannelDep {
|
|
203
|
+
readonly createBroadcastChannel: CreateBroadcastChannel;
|
|
204
|
+
}
|
|
205
|
+
|
|
181
206
|
/**
|
|
182
207
|
* Typed `self` for code running inside a dedicated worker.
|
|
183
208
|
*
|
|
@@ -250,17 +275,17 @@ export const createMessageChannel: CreateMessageChannel = <
|
|
|
250
275
|
handler: null,
|
|
251
276
|
queue: [],
|
|
252
277
|
flushScheduled: false,
|
|
253
|
-
|
|
278
|
+
flushTimeoutId: null,
|
|
279
|
+
ownerCount: 0,
|
|
254
280
|
pendingTransferCount: 0,
|
|
255
|
-
closed: false,
|
|
256
281
|
};
|
|
257
282
|
const state2: PortState<Input> = {
|
|
258
283
|
handler: null,
|
|
259
284
|
queue: [],
|
|
260
285
|
flushScheduled: false,
|
|
261
|
-
|
|
286
|
+
flushTimeoutId: null,
|
|
287
|
+
ownerCount: 0,
|
|
262
288
|
pendingTransferCount: 0,
|
|
263
|
-
closed: false,
|
|
264
289
|
};
|
|
265
290
|
|
|
266
291
|
const native1 = createNativeMessagePortToken<Input, Output>();
|
|
@@ -277,8 +302,11 @@ export const createMessageChannel: CreateMessageChannel = <
|
|
|
277
302
|
native: native2,
|
|
278
303
|
};
|
|
279
304
|
|
|
280
|
-
|
|
281
|
-
|
|
305
|
+
using disposer = new DisposableStack();
|
|
306
|
+
|
|
307
|
+
const port1 = disposer.use(createTestPort(port1Registration));
|
|
308
|
+
const port2 = disposer.use(createTestPort(port2Registration));
|
|
309
|
+
const disposables = disposer.move();
|
|
282
310
|
|
|
283
311
|
nativePortRegistry.set(native1, port1Registration);
|
|
284
312
|
nativePortRegistry.set(native2, port2Registration);
|
|
@@ -286,10 +314,7 @@ export const createMessageChannel: CreateMessageChannel = <
|
|
|
286
314
|
return {
|
|
287
315
|
port1,
|
|
288
316
|
port2,
|
|
289
|
-
[Symbol.dispose]: () =>
|
|
290
|
-
port1[Symbol.dispose]();
|
|
291
|
-
port2[Symbol.dispose]();
|
|
292
|
-
},
|
|
317
|
+
[Symbol.dispose]: () => disposables.dispose(),
|
|
293
318
|
};
|
|
294
319
|
};
|
|
295
320
|
|
|
@@ -307,6 +332,75 @@ export const createMessagePort: CreateMessagePort = <Input, Output = never>(
|
|
|
307
332
|
return createTestPort(registration as PortRegistration<Input, Output>);
|
|
308
333
|
};
|
|
309
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Creates an in-memory {@link BroadcastChannel}.
|
|
337
|
+
*
|
|
338
|
+
* This is a memory-only fallback for platforms without native BroadcastChannel
|
|
339
|
+
* support. Message delivery is asynchronous in-process.
|
|
340
|
+
*/
|
|
341
|
+
export const createBroadcastChannel: CreateBroadcastChannel = <
|
|
342
|
+
Input,
|
|
343
|
+
Output = Input,
|
|
344
|
+
>(
|
|
345
|
+
name: string,
|
|
346
|
+
): BroadcastChannel<Input, Output> => {
|
|
347
|
+
let onMessageHandler: ((message: Output) => void) | null = null;
|
|
348
|
+
const dispatch: BroadcastChannelDispatch<Output> = (message) => {
|
|
349
|
+
onMessageHandler?.(message);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
let dispatches = broadcastChannelDispatchesByName.get(name);
|
|
353
|
+
if (!dispatches) {
|
|
354
|
+
dispatches = new Set();
|
|
355
|
+
broadcastChannelDispatchesByName.set(name, dispatches);
|
|
356
|
+
}
|
|
357
|
+
dispatches.add(dispatch as BroadcastChannelDispatch<any>);
|
|
358
|
+
|
|
359
|
+
using disposer = new DisposableStack();
|
|
360
|
+
|
|
361
|
+
disposer.defer(() => {
|
|
362
|
+
onMessageHandler = null;
|
|
363
|
+
|
|
364
|
+
dispatches.delete(dispatch as BroadcastChannelDispatch<any>);
|
|
365
|
+
if (dispatches.size === 0) broadcastChannelDispatchesByName.delete(name);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const disposables = disposer.move();
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
postMessage: (message) => {
|
|
372
|
+
assertNotDisposed(disposables);
|
|
373
|
+
|
|
374
|
+
const dispatches = broadcastChannelDispatchesByName.get(name);
|
|
375
|
+
assert(dispatches, "Expected broadcast channel dispatches");
|
|
376
|
+
|
|
377
|
+
for (const otherDispatch of [...dispatches]) {
|
|
378
|
+
if (otherDispatch === dispatch) continue;
|
|
379
|
+
|
|
380
|
+
globalThis.setTimeout(() => {
|
|
381
|
+
if (!dispatches.has(otherDispatch)) return;
|
|
382
|
+
otherDispatch(message);
|
|
383
|
+
}, 0);
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
get onMessage() {
|
|
387
|
+
return disposables.disposed ? null : onMessageHandler;
|
|
388
|
+
},
|
|
389
|
+
set onMessage(fn) {
|
|
390
|
+
if (disposables.disposed) return;
|
|
391
|
+
onMessageHandler = fn;
|
|
392
|
+
},
|
|
393
|
+
[Symbol.dispose]: () => disposables.dispose(),
|
|
394
|
+
};
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
type BroadcastChannelDispatch<T> = (message: T) => void;
|
|
398
|
+
|
|
399
|
+
const broadcastChannelDispatchesByName = new Map<
|
|
400
|
+
string,
|
|
401
|
+
Set<BroadcastChannelDispatch<any>>
|
|
402
|
+
>();
|
|
403
|
+
|
|
310
404
|
/**
|
|
311
405
|
* Test {@link Worker} with access to its paired worker-side `self`.
|
|
312
406
|
*
|
|
@@ -342,6 +436,14 @@ export interface TestMessageChannel<
|
|
|
342
436
|
readonly isDisposed: () => boolean;
|
|
343
437
|
}
|
|
344
438
|
|
|
439
|
+
/** {@link BroadcastChannel} with disposal tracking for testing. */
|
|
440
|
+
export interface TestBroadcastChannel<
|
|
441
|
+
Input,
|
|
442
|
+
Output = Input,
|
|
443
|
+
> extends BroadcastChannel<Input, Output> {
|
|
444
|
+
readonly isDisposed: () => boolean;
|
|
445
|
+
}
|
|
446
|
+
|
|
345
447
|
/**
|
|
346
448
|
* Creates a connected {@link TestWorker} for testing.
|
|
347
449
|
*
|
|
@@ -358,7 +460,7 @@ export const testCreateWorker = <Input, Output = never>(): TestWorker<
|
|
|
358
460
|
self = nextSelf;
|
|
359
461
|
});
|
|
360
462
|
|
|
361
|
-
return Object.assign(worker, { self })
|
|
463
|
+
return Object.assign(worker, { self });
|
|
362
464
|
};
|
|
363
465
|
|
|
364
466
|
/**
|
|
@@ -378,7 +480,7 @@ export const testCreateSharedWorker = <
|
|
|
378
480
|
return Object.assign(worker, {
|
|
379
481
|
self,
|
|
380
482
|
connect,
|
|
381
|
-
})
|
|
483
|
+
});
|
|
382
484
|
};
|
|
383
485
|
|
|
384
486
|
/**
|
|
@@ -394,18 +496,16 @@ export const testCreateMessageChannel = <
|
|
|
394
496
|
Input,
|
|
395
497
|
Output = never,
|
|
396
498
|
>(): TestMessageChannel<Input, Output> => {
|
|
397
|
-
|
|
499
|
+
using disposer = new DisposableStack();
|
|
398
500
|
|
|
399
|
-
|
|
501
|
+
const channel = disposer.use(createMessageChannel<Input, Output>());
|
|
502
|
+
const disposables = disposer.move();
|
|
400
503
|
|
|
401
504
|
return {
|
|
402
505
|
port1: channel.port1,
|
|
403
506
|
port2: channel.port2,
|
|
404
|
-
isDisposed: () => disposed,
|
|
405
|
-
[Symbol.dispose]: () =>
|
|
406
|
-
disposed = true;
|
|
407
|
-
channel[Symbol.dispose]();
|
|
408
|
-
},
|
|
507
|
+
isDisposed: () => disposables.disposed,
|
|
508
|
+
[Symbol.dispose]: () => disposables.dispose(),
|
|
409
509
|
};
|
|
410
510
|
};
|
|
411
511
|
|
|
@@ -414,11 +514,35 @@ export const testCreateMessagePort: CreateMessagePort = <Input, Output = never>(
|
|
|
414
514
|
nativePort: NativeMessagePort<Input, Output>,
|
|
415
515
|
): MessagePort<Input, Output> => createMessagePort(nativePort);
|
|
416
516
|
|
|
517
|
+
/** Creates an in-memory {@link BroadcastChannel} for testing. */
|
|
518
|
+
export const testCreateBroadcastChannel = <Input, Output = Input>(
|
|
519
|
+
name: string,
|
|
520
|
+
): TestBroadcastChannel<Input, Output> => {
|
|
521
|
+
using disposer = new DisposableStack();
|
|
522
|
+
|
|
523
|
+
const channel = disposer.use(createBroadcastChannel<Input, Output>(name));
|
|
524
|
+
const disposables = disposer.move();
|
|
525
|
+
|
|
526
|
+
return {
|
|
527
|
+
postMessage: channel.postMessage,
|
|
528
|
+
get onMessage() {
|
|
529
|
+
return channel.onMessage;
|
|
530
|
+
},
|
|
531
|
+
set onMessage(value) {
|
|
532
|
+
channel.onMessage = value;
|
|
533
|
+
},
|
|
534
|
+
isDisposed: () => disposables.disposed,
|
|
535
|
+
[Symbol.dispose]: () => disposables.dispose(),
|
|
536
|
+
};
|
|
537
|
+
};
|
|
538
|
+
|
|
417
539
|
/**
|
|
418
|
-
* Waits
|
|
540
|
+
* Waits the minimal time necessary for in-memory worker message delivery in
|
|
541
|
+
* tests.
|
|
419
542
|
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
543
|
+
* This is better than `vi.waitFor` when a test only needs to flush the known
|
|
544
|
+
* in-memory worker/message-port pipeline. It encodes that transport boundary
|
|
545
|
+
* once, avoids polling, and keeps worker-focused tests fast and explicit.
|
|
422
546
|
*/
|
|
423
547
|
export const testWaitForWorkerMessage = async (): Promise<void> => {
|
|
424
548
|
await testWaitForMacrotask();
|
|
@@ -498,9 +622,9 @@ interface PortState<T> {
|
|
|
498
622
|
handler: ((message: T) => void) | null;
|
|
499
623
|
readonly queue: Array<T>;
|
|
500
624
|
flushScheduled: boolean;
|
|
501
|
-
|
|
625
|
+
flushTimeoutId: ReturnType<typeof globalThis.setTimeout> | null;
|
|
626
|
+
ownerCount: number;
|
|
502
627
|
pendingTransferCount: number;
|
|
503
|
-
closed: boolean;
|
|
504
628
|
}
|
|
505
629
|
|
|
506
630
|
interface PortRegistration<Input, Output> {
|
|
@@ -514,23 +638,46 @@ const createPort = <Input, Output>({
|
|
|
514
638
|
peerReceive,
|
|
515
639
|
native,
|
|
516
640
|
}: PortRegistration<Input, Output>): MessagePort<Input, Output> => {
|
|
641
|
+
using disposer = new DisposableStack();
|
|
642
|
+
|
|
517
643
|
if (receive.pendingTransferCount > 0) receive.pendingTransferCount -= 1;
|
|
518
|
-
receive.
|
|
519
|
-
|
|
644
|
+
else receive.ownerCount += 1;
|
|
645
|
+
|
|
646
|
+
disposer.defer(() => {
|
|
647
|
+
receive.ownerCount -= 1;
|
|
648
|
+
if (receive.ownerCount > 0) return;
|
|
649
|
+
|
|
650
|
+
nativePortRegistry.delete(native);
|
|
651
|
+
|
|
652
|
+
if (receive.flushTimeoutId != null) {
|
|
653
|
+
globalThis.clearTimeout(receive.flushTimeoutId);
|
|
654
|
+
receive.flushTimeoutId = null;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
receive.handler = null;
|
|
658
|
+
receive.flushScheduled = false;
|
|
659
|
+
receive.queue.length = 0;
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
const disposables = disposer.move();
|
|
520
663
|
|
|
521
664
|
const scheduleFlush = (state: PortState<any>): void => {
|
|
522
|
-
if (state.flushScheduled || state.
|
|
665
|
+
if (state.flushScheduled || state.ownerCount === 0) return;
|
|
523
666
|
state.flushScheduled = true;
|
|
524
667
|
|
|
525
668
|
// Native worker messages are task-queued; use macrotask timing.
|
|
526
|
-
setTimeout(() => {
|
|
669
|
+
state.flushTimeoutId = globalThis.setTimeout(() => {
|
|
670
|
+
state.flushTimeoutId = null;
|
|
527
671
|
state.flushScheduled = false;
|
|
528
|
-
if (state.
|
|
672
|
+
if (state.ownerCount === 0) return;
|
|
529
673
|
|
|
530
|
-
const
|
|
531
|
-
if (!
|
|
674
|
+
const firstHandler = state.handler;
|
|
675
|
+
if (!firstHandler) return;
|
|
532
676
|
|
|
533
677
|
for (const message of state.queue.splice(0)) {
|
|
678
|
+
const handler = state.handler;
|
|
679
|
+
if (!handler) return;
|
|
680
|
+
|
|
534
681
|
handler(message);
|
|
535
682
|
}
|
|
536
683
|
}, 0);
|
|
@@ -538,41 +685,36 @@ const createPort = <Input, Output>({
|
|
|
538
685
|
|
|
539
686
|
return {
|
|
540
687
|
postMessage: (message, transfer) => {
|
|
688
|
+
if (
|
|
689
|
+
disposables.disposed ||
|
|
690
|
+
receive.ownerCount === 0 ||
|
|
691
|
+
peerReceive.ownerCount === 0
|
|
692
|
+
)
|
|
693
|
+
return;
|
|
694
|
+
|
|
541
695
|
for (const transferable of transfer ?? []) {
|
|
542
696
|
if (transferable instanceof globalThis.ArrayBuffer) continue;
|
|
543
697
|
|
|
544
698
|
const registration = nativePortRegistry.get(transferable);
|
|
545
699
|
if (!registration) continue;
|
|
546
700
|
|
|
701
|
+
registration.receive.ownerCount += 1;
|
|
547
702
|
registration.receive.pendingTransferCount += 1;
|
|
548
|
-
registration.receive.closed = false;
|
|
549
703
|
}
|
|
550
704
|
|
|
551
|
-
if (peerReceive.closed) return;
|
|
552
705
|
peerReceive.queue.push(message);
|
|
553
706
|
scheduleFlush(peerReceive);
|
|
554
707
|
},
|
|
555
708
|
get onMessage() {
|
|
556
|
-
return
|
|
709
|
+
return disposables.disposed ? null : receive.handler;
|
|
557
710
|
},
|
|
558
711
|
set onMessage(fn) {
|
|
559
|
-
if (
|
|
712
|
+
if (disposables.disposed || receive.ownerCount === 0) return;
|
|
560
713
|
receive.handler = fn;
|
|
561
714
|
if (fn) scheduleFlush(receive);
|
|
562
715
|
},
|
|
563
716
|
native,
|
|
564
|
-
[Symbol.dispose]: () =>
|
|
565
|
-
if (isDisposed) return;
|
|
566
|
-
isDisposed = true;
|
|
567
|
-
|
|
568
|
-
receive.refCount -= 1;
|
|
569
|
-
if (receive.refCount > 0 || receive.pendingTransferCount > 0) return;
|
|
570
|
-
|
|
571
|
-
receive.closed = true;
|
|
572
|
-
receive.handler = null;
|
|
573
|
-
receive.flushScheduled = false;
|
|
574
|
-
receive.queue.length = 0;
|
|
575
|
-
},
|
|
717
|
+
[Symbol.dispose]: () => disposables.dispose(),
|
|
576
718
|
};
|
|
577
719
|
};
|
|
578
720
|
|
package/src/index.ts
CHANGED
package/src/local-first/Db.ts
CHANGED
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
type RandomBytesDep,
|
|
19
19
|
} from "../Crypto.js";
|
|
20
20
|
import { exhaustiveCheck, lazyFalse, lazyVoid } from "../Function.js";
|
|
21
|
+
import type { LockManagerDep } from "../LockManager.js";
|
|
22
|
+
import { acquireLeaderLock } from "../LockManager.js";
|
|
21
23
|
import { createRecord, getProperty, objectToEntries } from "../Object.js";
|
|
22
24
|
import { ok, type Result } from "../Result.js";
|
|
23
25
|
import type {
|
|
@@ -35,7 +37,7 @@ import {
|
|
|
35
37
|
sqliteQueryStringToSqliteQuery,
|
|
36
38
|
SqliteValue,
|
|
37
39
|
} from "../Sqlite.js";
|
|
38
|
-
import { callback, type
|
|
40
|
+
import { callback, type Task } from "../Task.js";
|
|
39
41
|
import { Millis, millisToDateIso, type TimeDep } from "../Time.js";
|
|
40
42
|
import type { Name } from "../Type.js";
|
|
41
43
|
import {
|
|
@@ -47,6 +49,7 @@ import {
|
|
|
47
49
|
} from "../Type.js";
|
|
48
50
|
import type { ExtractType } from "../Types.js";
|
|
49
51
|
import type {
|
|
52
|
+
CreateBroadcastChannelDep,
|
|
50
53
|
NativeMessagePort,
|
|
51
54
|
Worker,
|
|
52
55
|
WorkerDeps,
|
|
@@ -72,7 +75,14 @@ import {
|
|
|
72
75
|
getEvoluSqliteSchema,
|
|
73
76
|
systemColumns,
|
|
74
77
|
} from "./Schema.js";
|
|
75
|
-
import type {
|
|
78
|
+
import type {
|
|
79
|
+
ConsoleEntryOrError,
|
|
80
|
+
DbWorkerInput,
|
|
81
|
+
DbWorkerOutput,
|
|
82
|
+
DbWorkerQueuedResponse,
|
|
83
|
+
DbWorkerRequest,
|
|
84
|
+
} from "./Shared.js";
|
|
85
|
+
import { consoleEntryOrErrorBroadcastChannelName } from "./Shared.js";
|
|
76
86
|
import {
|
|
77
87
|
createBaseSqliteStorage,
|
|
78
88
|
createBaseSqliteStorageTables,
|
|
@@ -105,7 +115,7 @@ import {
|
|
|
105
115
|
export type DbWorker = Worker<DbWorkerInit>;
|
|
106
116
|
|
|
107
117
|
export interface DbWorkerInit {
|
|
108
|
-
readonly type: "
|
|
118
|
+
readonly type: "DbWorkerInit";
|
|
109
119
|
readonly name: Name;
|
|
110
120
|
readonly consoleLevel: ConsoleLevel;
|
|
111
121
|
readonly sqliteSchema: SqliteSchema;
|
|
@@ -120,18 +130,22 @@ export interface CreateDbWorkerDep {
|
|
|
120
130
|
readonly createDbWorker: CreateDbWorker;
|
|
121
131
|
}
|
|
122
132
|
|
|
123
|
-
export type DbWorkerDeps = WorkerDeps &
|
|
133
|
+
export type DbWorkerDeps = WorkerDeps &
|
|
134
|
+
CreateBroadcastChannelDep &
|
|
135
|
+
LockManagerDep &
|
|
136
|
+
CreateSqliteDriverDep;
|
|
124
137
|
|
|
125
138
|
export const startDbWorker =
|
|
126
139
|
(self: WorkerSelf<DbWorkerInit>): Task<void, never, DbWorkerDeps> =>
|
|
127
|
-
async (
|
|
128
|
-
await using
|
|
129
|
-
|
|
130
|
-
const
|
|
140
|
+
async (run) => {
|
|
141
|
+
await using disposer = new AsyncDisposableStack();
|
|
142
|
+
disposer.use(self);
|
|
143
|
+
const dbWorkerRun = disposer.use(run.create());
|
|
144
|
+
const { deps } = dbWorkerRun;
|
|
131
145
|
|
|
132
146
|
let initialized = false;
|
|
133
147
|
|
|
134
|
-
const initMessage = await
|
|
148
|
+
const initMessage = await dbWorkerRun.orThrow(
|
|
135
149
|
callback<DbWorkerInit>(({ ok }) => {
|
|
136
150
|
self.onMessage = (message) => {
|
|
137
151
|
assert(!initialized, "DbWorker must be initialized only once");
|
|
@@ -143,26 +157,36 @@ export const startDbWorker =
|
|
|
143
157
|
|
|
144
158
|
const console = deps.console.child(initMessage.name).child("DbWorker");
|
|
145
159
|
console.setLevel(initMessage.consoleLevel);
|
|
146
|
-
console.info("start DbWorker");
|
|
147
160
|
|
|
148
|
-
const port =
|
|
161
|
+
const port = disposer.use(
|
|
149
162
|
deps.createMessagePort<DbWorkerOutput, DbWorkerInput>(initMessage.port),
|
|
150
163
|
);
|
|
164
|
+
const consoleEntryOrErrorBroadcastChannel = disposer.use(
|
|
165
|
+
deps.createBroadcastChannel<ConsoleEntryOrError>(
|
|
166
|
+
consoleEntryOrErrorBroadcastChannelName,
|
|
167
|
+
),
|
|
168
|
+
);
|
|
151
169
|
|
|
152
|
-
|
|
170
|
+
disposer.defer(
|
|
153
171
|
deps.consoleStoreOutputEntry.subscribe(() => {
|
|
154
172
|
const entry = deps.consoleStoreOutputEntry.get();
|
|
155
|
-
if (entry)
|
|
173
|
+
if (entry)
|
|
174
|
+
consoleEntryOrErrorBroadcastChannel.postMessage({
|
|
175
|
+
type: "ConsoleEntry",
|
|
176
|
+
entry,
|
|
177
|
+
});
|
|
156
178
|
}),
|
|
157
179
|
);
|
|
180
|
+
console.debug("createDbWorker");
|
|
158
181
|
|
|
159
|
-
|
|
160
|
-
|
|
182
|
+
disposer.use(
|
|
183
|
+
await dbWorkerRun.orThrow(acquireLeaderLock(initMessage.name)),
|
|
184
|
+
);
|
|
161
185
|
|
|
162
186
|
port.postMessage({ type: "LeaderAcquired", name: initMessage.name });
|
|
163
187
|
|
|
164
|
-
const sqlite =
|
|
165
|
-
await
|
|
188
|
+
const sqlite = disposer.use(
|
|
189
|
+
await dbWorkerRun.orThrow(
|
|
166
190
|
createSqlite(
|
|
167
191
|
initMessage.name,
|
|
168
192
|
initMessage.memoryOnly
|
|
@@ -173,10 +197,13 @@ export const startDbWorker =
|
|
|
173
197
|
);
|
|
174
198
|
console.debug("SQLite created");
|
|
175
199
|
|
|
176
|
-
const baseSqliteStorage = createBaseSqliteStorage({
|
|
200
|
+
const baseSqliteStorage = createBaseSqliteStorage({
|
|
201
|
+
sqlite,
|
|
202
|
+
...dbWorkerRun.deps,
|
|
203
|
+
});
|
|
177
204
|
|
|
178
205
|
const dbDeps = {
|
|
179
|
-
...
|
|
206
|
+
...dbWorkerRun.deps,
|
|
180
207
|
sqlite,
|
|
181
208
|
sqliteSchema: initMessage.sqliteSchema,
|
|
182
209
|
baseSqliteStorage,
|
|
@@ -195,33 +222,26 @@ export const startDbWorker =
|
|
|
195
222
|
|
|
196
223
|
const storage = createClientStorage({ ...dbDeps, clock })({
|
|
197
224
|
onError: (error) => {
|
|
198
|
-
|
|
225
|
+
consoleEntryOrErrorBroadcastChannel.postMessage({
|
|
226
|
+
type: "Error",
|
|
227
|
+
error,
|
|
228
|
+
});
|
|
199
229
|
},
|
|
200
230
|
});
|
|
201
231
|
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
const runWithStorage = run.addDeps({ storage });
|
|
232
|
+
const disposables = disposer.move();
|
|
233
|
+
const dbWorkerRunWithStorage = dbWorkerRun.addDeps({ storage });
|
|
205
234
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
* memory pressure shows up; even millions of short callback IDs are
|
|
213
|
-
* acceptable here, and naive eviction could let a still-retried request run
|
|
214
|
-
* again.
|
|
215
|
-
*/
|
|
216
|
-
const processedRequestIds = new Set<Id>();
|
|
235
|
+
port.onMessage = (input) => {
|
|
236
|
+
if (input.type === "Dispose") {
|
|
237
|
+
if (!disposables.disposed) console.debug("disposeDbWorker");
|
|
238
|
+
void disposables.disposeAsync();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
217
241
|
|
|
218
|
-
|
|
219
|
-
if (processedRequestIds.has(callbackId)) return;
|
|
220
|
-
processedRequestIds.add(callbackId);
|
|
242
|
+
const { callbackId, request } = input;
|
|
221
243
|
|
|
222
|
-
const postQueuedResponse = (
|
|
223
|
-
response: ExtractType<DbWorkerOutput, "OnQueuedResponse">["response"],
|
|
224
|
-
): void => {
|
|
244
|
+
const postQueuedResponse = (response: DbWorkerQueuedResponse): void => {
|
|
225
245
|
port.postMessage(
|
|
226
246
|
{ type: "OnQueuedResponse", callbackId, response },
|
|
227
247
|
response.type === "ForEvolu" && response.message.type === "Export"
|
|
@@ -238,11 +258,14 @@ export const startDbWorker =
|
|
|
238
258
|
request.message,
|
|
239
259
|
);
|
|
240
260
|
if (!result.ok) {
|
|
241
|
-
|
|
261
|
+
consoleEntryOrErrorBroadcastChannel.postMessage({
|
|
262
|
+
type: "Error",
|
|
263
|
+
error: result.error,
|
|
264
|
+
});
|
|
242
265
|
} else {
|
|
243
266
|
postQueuedResponse({
|
|
244
267
|
type: "ForEvolu",
|
|
245
|
-
|
|
268
|
+
id: request.id,
|
|
246
269
|
message: result.value,
|
|
247
270
|
});
|
|
248
271
|
}
|
|
@@ -252,7 +275,7 @@ export const startDbWorker =
|
|
|
252
275
|
case "Query":
|
|
253
276
|
postQueuedResponse({
|
|
254
277
|
type: "ForEvolu",
|
|
255
|
-
|
|
278
|
+
id: request.id,
|
|
256
279
|
message: {
|
|
257
280
|
type: "Query",
|
|
258
281
|
rowsByQuery: loadQueries(dbDeps)(request.message.queries),
|
|
@@ -263,7 +286,7 @@ export const startDbWorker =
|
|
|
263
286
|
case "Export":
|
|
264
287
|
postQueuedResponse({
|
|
265
288
|
type: "ForEvolu",
|
|
266
|
-
|
|
289
|
+
id: request.id,
|
|
267
290
|
message: {
|
|
268
291
|
type: "Export",
|
|
269
292
|
file: dbDeps.sqlite.export(),
|
|
@@ -307,7 +330,7 @@ export const startDbWorker =
|
|
|
307
330
|
case "ApplySyncMessage": {
|
|
308
331
|
const { owner, inputMessage } = request.message;
|
|
309
332
|
|
|
310
|
-
|
|
333
|
+
dbWorkerRunWithStorage<void, never>(async (run) => {
|
|
311
334
|
storage.setRequestContext(owner.encryptionKey);
|
|
312
335
|
|
|
313
336
|
const result = await run(
|
|
@@ -777,7 +800,7 @@ const handleMutation =
|
|
|
777
800
|
) =>
|
|
778
801
|
(
|
|
779
802
|
message: ExtractType<
|
|
780
|
-
ExtractType<
|
|
803
|
+
ExtractType<DbWorkerRequest, "ForEvolu">["message"],
|
|
781
804
|
"Mutate"
|
|
782
805
|
>,
|
|
783
806
|
): Result<
|