@evolu/common 8.0.0-next.2 → 8.0.0-next.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"Worker.d.ts","sourceRoot":"","sources":["../../src/Worker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Worker.d.ts","sourceRoot":"","sources":["../../src/Worker.ts"],"names":[],"mappings":"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"}
@@ -42,18 +42,34 @@ export const createMessageChannel = () => {
42
42
  handler: null,
43
43
  queue: [],
44
44
  flushScheduled: false,
45
+ refCount: 0,
46
+ pendingTransferCount: 0,
47
+ closed: false,
45
48
  };
46
49
  const state2 = {
47
50
  handler: null,
48
51
  queue: [],
49
52
  flushScheduled: false,
53
+ refCount: 0,
54
+ pendingTransferCount: 0,
55
+ closed: false,
50
56
  };
51
57
  const native1 = createNativeMessagePortToken();
52
58
  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);
59
+ const port1Registration = {
60
+ receive: state1,
61
+ peerReceive: state2,
62
+ native: native1,
63
+ };
64
+ const port2Registration = {
65
+ receive: state2,
66
+ peerReceive: state1,
67
+ native: native2,
68
+ };
69
+ const port1 = createTestPort(port1Registration);
70
+ const port2 = createTestPort(port2Registration);
71
+ nativePortRegistry.set(native1, port1Registration);
72
+ nativePortRegistry.set(native2, port2Registration);
57
73
  return {
58
74
  port1,
59
75
  port2,
@@ -70,9 +86,9 @@ export const createMessageChannel = () => {
70
86
  * support. Message delivery through returned ports is asynchronous in-process.
71
87
  */
72
88
  export const createMessagePort = (nativePort) => {
73
- const pair = nativePortRegistry.get(nativePort);
74
- assert(pair, "Unknown native port — did you transfer it?");
75
- return pair;
89
+ const registration = nativePortRegistry.get(nativePort);
90
+ assert(registration, "Unknown native port — did you transfer it?");
91
+ return createTestPort(registration);
76
92
  };
77
93
  /**
78
94
  * Creates a connected {@link TestWorker} for testing.
@@ -184,14 +200,20 @@ const createMemorySharedWorkerPair = () => {
184
200
  };
185
201
  return { worker, self, connect };
186
202
  };
187
- const createPort = (receive, peerReceive, native) => {
203
+ const createPort = ({ receive, peerReceive, native, }) => {
204
+ if (receive.pendingTransferCount > 0)
205
+ receive.pendingTransferCount -= 1;
206
+ receive.refCount += 1;
207
+ let isDisposed = false;
188
208
  const scheduleFlush = (state) => {
189
- if (state.flushScheduled)
209
+ if (state.flushScheduled || state.closed)
190
210
  return;
191
211
  state.flushScheduled = true;
192
212
  // Native worker messages are task-queued; use macrotask timing.
193
213
  setTimeout(() => {
194
214
  state.flushScheduled = false;
215
+ if (state.closed)
216
+ return;
195
217
  const handler = state.handler;
196
218
  if (!handler)
197
219
  return;
@@ -201,26 +223,47 @@ const createPort = (receive, peerReceive, native) => {
201
223
  }, 0);
202
224
  };
203
225
  return {
204
- postMessage: (message) => {
226
+ postMessage: (message, transfer) => {
227
+ for (const transferable of transfer ?? []) {
228
+ if (transferable instanceof globalThis.ArrayBuffer)
229
+ continue;
230
+ const registration = nativePortRegistry.get(transferable);
231
+ if (!registration)
232
+ continue;
233
+ registration.receive.pendingTransferCount += 1;
234
+ registration.receive.closed = false;
235
+ }
236
+ if (peerReceive.closed)
237
+ return;
205
238
  peerReceive.queue.push(message);
206
239
  scheduleFlush(peerReceive);
207
240
  },
208
241
  get onMessage() {
209
- return receive.handler;
242
+ return isDisposed ? null : receive.handler;
210
243
  },
211
244
  set onMessage(fn) {
245
+ if (isDisposed || receive.closed)
246
+ return;
212
247
  receive.handler = fn;
213
248
  if (fn)
214
249
  scheduleFlush(receive);
215
250
  },
216
251
  native,
217
252
  [Symbol.dispose]: () => {
253
+ if (isDisposed)
254
+ return;
255
+ isDisposed = true;
256
+ receive.refCount -= 1;
257
+ if (receive.refCount > 0 || receive.pendingTransferCount > 0)
258
+ return;
259
+ receive.closed = true;
218
260
  receive.handler = null;
219
261
  receive.flushScheduled = false;
262
+ receive.queue.length = 0;
220
263
  },
221
264
  };
222
265
  };
223
- const createTestPort = (receive, peerReceive, native) => createPort(receive, peerReceive, native);
266
+ const createTestPort = (registration) => createPort(registration);
224
267
  const createNativeMessagePortToken = () => ({});
225
268
  /**
226
269
  * Registry mapping native port tokens to their in-memory port counterparts.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolu/common",
3
- "version": "8.0.0-next.2",
3
+ "version": "8.0.0-next.3",
4
4
  "description": "TypeScript library and local-first platform",
5
5
  "keywords": [
6
6
  "evolu",
package/src/Worker.ts CHANGED
@@ -250,21 +250,38 @@ export const createMessageChannel: CreateMessageChannel = <
250
250
  handler: null,
251
251
  queue: [],
252
252
  flushScheduled: false,
253
+ refCount: 0,
254
+ pendingTransferCount: 0,
255
+ closed: false,
253
256
  };
254
257
  const state2: PortState<Input> = {
255
258
  handler: null,
256
259
  queue: [],
257
260
  flushScheduled: false,
261
+ refCount: 0,
262
+ pendingTransferCount: 0,
263
+ closed: false,
258
264
  };
259
265
 
260
266
  const native1 = createNativeMessagePortToken<Input, Output>();
261
267
  const native2 = createNativeMessagePortToken<Output, Input>();
262
268
 
263
- const port1 = createTestPort<Input, Output>(state1, state2, native1);
264
- const port2 = createTestPort<Output, Input>(state2, state1, native2);
269
+ const port1Registration: PortRegistration<Input, Output> = {
270
+ receive: state1,
271
+ peerReceive: state2,
272
+ native: native1,
273
+ };
274
+ const port2Registration: PortRegistration<Output, Input> = {
275
+ receive: state2,
276
+ peerReceive: state1,
277
+ native: native2,
278
+ };
265
279
 
266
- nativePortRegistry.set(native1, port1);
267
- nativePortRegistry.set(native2, port2);
280
+ const port1 = createTestPort(port1Registration);
281
+ const port2 = createTestPort(port2Registration);
282
+
283
+ nativePortRegistry.set(native1, port1Registration);
284
+ nativePortRegistry.set(native2, port2Registration);
268
285
 
269
286
  return {
270
287
  port1,
@@ -285,9 +302,9 @@ export const createMessageChannel: CreateMessageChannel = <
285
302
  export const createMessagePort: CreateMessagePort = <Input, Output = never>(
286
303
  nativePort: NativeMessagePort<Input, Output>,
287
304
  ): MessagePort<Input, Output> => {
288
- const pair = nativePortRegistry.get(nativePort);
289
- assert(pair, "Unknown native port — did you transfer it?");
290
- return pair as MessagePort<Input, Output>;
305
+ const registration = nativePortRegistry.get(nativePort);
306
+ assert(registration, "Unknown native port — did you transfer it?");
307
+ return createTestPort(registration as PortRegistration<Input, Output>);
291
308
  };
292
309
 
293
310
  /**
@@ -481,20 +498,34 @@ interface PortState<T> {
481
498
  handler: ((message: T) => void) | null;
482
499
  readonly queue: Array<T>;
483
500
  flushScheduled: boolean;
501
+ refCount: number;
502
+ pendingTransferCount: number;
503
+ closed: boolean;
484
504
  }
485
505
 
486
- const createPort = <Input, Output>(
487
- receive: PortState<Output>,
488
- peerReceive: PortState<Input>,
489
- native: NativeMessagePort<Input, Output>,
490
- ): MessagePort<Input, Output> => {
506
+ interface PortRegistration<Input, Output> {
507
+ readonly receive: PortState<Output>;
508
+ readonly peerReceive: PortState<Input>;
509
+ readonly native: NativeMessagePort<Input, Output>;
510
+ }
511
+
512
+ const createPort = <Input, Output>({
513
+ receive,
514
+ peerReceive,
515
+ native,
516
+ }: PortRegistration<Input, Output>): MessagePort<Input, Output> => {
517
+ if (receive.pendingTransferCount > 0) receive.pendingTransferCount -= 1;
518
+ receive.refCount += 1;
519
+ let isDisposed = false;
520
+
491
521
  const scheduleFlush = (state: PortState<any>): void => {
492
- if (state.flushScheduled) return;
522
+ if (state.flushScheduled || state.closed) return;
493
523
  state.flushScheduled = true;
494
524
 
495
525
  // Native worker messages are task-queued; use macrotask timing.
496
526
  setTimeout(() => {
497
527
  state.flushScheduled = false;
528
+ if (state.closed) return;
498
529
 
499
530
  const handler = state.handler;
500
531
  if (!handler) return;
@@ -506,30 +537,48 @@ const createPort = <Input, Output>(
506
537
  };
507
538
 
508
539
  return {
509
- postMessage: (message) => {
540
+ postMessage: (message, transfer) => {
541
+ for (const transferable of transfer ?? []) {
542
+ if (transferable instanceof globalThis.ArrayBuffer) continue;
543
+
544
+ const registration = nativePortRegistry.get(transferable);
545
+ if (!registration) continue;
546
+
547
+ registration.receive.pendingTransferCount += 1;
548
+ registration.receive.closed = false;
549
+ }
550
+
551
+ if (peerReceive.closed) return;
510
552
  peerReceive.queue.push(message);
511
553
  scheduleFlush(peerReceive);
512
554
  },
513
555
  get onMessage() {
514
- return receive.handler;
556
+ return isDisposed ? null : receive.handler;
515
557
  },
516
558
  set onMessage(fn) {
559
+ if (isDisposed || receive.closed) return;
517
560
  receive.handler = fn;
518
561
  if (fn) scheduleFlush(receive);
519
562
  },
520
563
  native,
521
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;
522
572
  receive.handler = null;
523
573
  receive.flushScheduled = false;
574
+ receive.queue.length = 0;
524
575
  },
525
576
  };
526
577
  };
527
578
 
528
579
  const createTestPort = <Input, Output>(
529
- receive: PortState<Output>,
530
- peerReceive: PortState<Input>,
531
- native: NativeMessagePort<Input, Output>,
532
- ): MessagePort<Input, Output> => createPort(receive, peerReceive, native);
580
+ registration: PortRegistration<Input, Output>,
581
+ ): MessagePort<Input, Output> => createPort(registration);
533
582
 
534
583
  const createNativeMessagePortToken = <Input, Output>(): NativeMessagePort<
535
584
  Input,
@@ -548,5 +597,5 @@ const createNativeMessagePortToken = <Input, Output>(): NativeMessagePort<
548
597
  */
549
598
  const nativePortRegistry = new WeakMap<
550
599
  NativeMessagePort<any, any>,
551
- MessagePort<any, any>
600
+ PortRegistration<any, any>
552
601
  >();