@mmstack/worker 21.0.0 → 21.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -20
- package/fesm2022/mmstack-worker-host.mjs +43 -87
- package/fesm2022/mmstack-worker-host.mjs.map +1 -1
- package/fesm2022/mmstack-worker-protocol.mjs +0 -1
- package/fesm2022/mmstack-worker-protocol.mjs.map +1 -1
- package/fesm2022/mmstack-worker.mjs +141 -115
- package/fesm2022/mmstack-worker.mjs.map +1 -1
- package/package.json +2 -2
- package/types/mmstack-worker-host.d.ts +7 -0
- package/types/mmstack-worker-protocol.d.ts +6 -23
- package/types/mmstack-worker.d.ts +19 -12
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { generateId, deserializeError, closePort, takeTransferables } from '@mmstack/worker/protocol';
|
|
2
2
|
export * from '@mmstack/worker/protocol';
|
|
3
3
|
import { inject, Injector, runInInjectionContext, PLATFORM_ID, signal, DestroyRef, computed, effect, untracked } from '@angular/core';
|
|
4
|
-
import { injectTransitionScope, toStore,
|
|
4
|
+
import { injectTransitionScope, toStore, opSync } from '@mmstack/primitives';
|
|
5
|
+
import { createWatch } from '@angular/core/primitives/signals';
|
|
5
6
|
|
|
6
7
|
/** Thrown into a task/write promise when it is aborted. `name` is `'AbortError'` (DOM convention). */
|
|
7
8
|
class WorkerAbortError extends Error {
|
|
@@ -43,16 +44,16 @@ function build$2(spawn, opt) {
|
|
|
43
44
|
let attempt = 0;
|
|
44
45
|
let runIdSeq = 0;
|
|
45
46
|
let destroyed = false;
|
|
46
|
-
let crashed = false;
|
|
47
|
+
let crashed = false;
|
|
47
48
|
let port = null;
|
|
48
49
|
const send = (msg, transfer) => {
|
|
49
50
|
if (port)
|
|
50
|
-
port.postMessage(msg, transfer);
|
|
51
|
+
port.postMessage(msg, transfer);
|
|
51
52
|
};
|
|
52
53
|
const onMessage = (msg) => {
|
|
53
54
|
switch (msg.type) {
|
|
54
55
|
case 'ready':
|
|
55
|
-
attempt = 0;
|
|
56
|
+
attempt = 0;
|
|
56
57
|
crashed = false;
|
|
57
58
|
manifest.set({
|
|
58
59
|
hostId: msg.hostId,
|
|
@@ -81,10 +82,9 @@ function build$2(spawn, opt) {
|
|
|
81
82
|
return;
|
|
82
83
|
}
|
|
83
84
|
case 'task:aborted':
|
|
84
|
-
taskPending.delete(msg.runId);
|
|
85
|
+
taskPending.delete(msg.runId);
|
|
85
86
|
return;
|
|
86
87
|
default:
|
|
87
|
-
// store:* traffic → replicas
|
|
88
88
|
for (const h of storeHandlers)
|
|
89
89
|
h(msg);
|
|
90
90
|
}
|
|
@@ -94,7 +94,6 @@ function build$2(spawn, opt) {
|
|
|
94
94
|
return;
|
|
95
95
|
crashed = true;
|
|
96
96
|
connected.set(false);
|
|
97
|
-
// in-flight tasks can't survive a crash; store replicas hold their value and re-hydrate on reconnect
|
|
98
97
|
for (const [, p] of taskPending)
|
|
99
98
|
p.reject(new WorkerCrashedError());
|
|
100
99
|
taskPending.clear();
|
|
@@ -109,14 +108,11 @@ function build$2(spawn, opt) {
|
|
|
109
108
|
}, delay);
|
|
110
109
|
};
|
|
111
110
|
const openPort = () => {
|
|
112
|
-
// terminate the previous worker before respawning, so a transient/manual restart never orphans
|
|
113
|
-
// a live thread (a genuine crash already killed it; terminate() is then a harmless no-op)
|
|
114
111
|
if (port)
|
|
115
112
|
closePort(port);
|
|
116
113
|
port = spawn();
|
|
117
114
|
port.onmessage = (ev) => onMessage(ev.data);
|
|
118
|
-
//
|
|
119
|
-
// MessagePort's onmessage delivery). A real `Worker` has onerror; onmessageerror where present.
|
|
115
|
+
// crash detection via property setters, not addEventListener (perturbs node MessagePort delivery)
|
|
120
116
|
const evt = port;
|
|
121
117
|
if ('onerror' in evt)
|
|
122
118
|
evt.onerror = handleCrash;
|
|
@@ -124,7 +120,6 @@ function build$2(spawn, opt) {
|
|
|
124
120
|
evt.onmessageerror = handleCrash;
|
|
125
121
|
send({ type: 'hello', proto: 1, clientId });
|
|
126
122
|
};
|
|
127
|
-
// no workers on the server — connected stays false, runTask rejects, replicas render their default
|
|
128
123
|
if (!isServer)
|
|
129
124
|
openPort();
|
|
130
125
|
inject(DestroyRef).onDestroy(() => teardown());
|
|
@@ -134,8 +129,6 @@ function build$2(spawn, opt) {
|
|
|
134
129
|
for (const [, p] of taskPending)
|
|
135
130
|
p.reject(new WorkerAbortError('worker connection destroyed'));
|
|
136
131
|
taskPending.clear();
|
|
137
|
-
// same contract as a crash: anything pending on the connection (replica writes) settles NOW
|
|
138
|
-
// rather than dangling — a destroyed port can never deliver the echo that would resolve them
|
|
139
132
|
for (const fn of disconnectHooks)
|
|
140
133
|
fn();
|
|
141
134
|
if (port)
|
|
@@ -150,9 +143,6 @@ function build$2(spawn, opt) {
|
|
|
150
143
|
return Promise.reject(new WorkerCrashedError('no worker on the server'));
|
|
151
144
|
if (destroyed)
|
|
152
145
|
return Promise.reject(new WorkerAbortError('worker connection destroyed'));
|
|
153
|
-
// between a crash and the respawned handshake the port is dead — a message posted into it
|
|
154
|
-
// vanishes and the promise would never settle. Reject honestly; the caller retries/reloads.
|
|
155
|
-
// (Before the FIRST handshake this is false: a starting worker buffers messages, so we send.)
|
|
156
146
|
if (crashed)
|
|
157
147
|
return Promise.reject(new WorkerCrashedError('worker is restarting'));
|
|
158
148
|
if (o?.signal?.aborted)
|
|
@@ -207,7 +197,7 @@ function build$1(params, options) {
|
|
|
207
197
|
const { worker, task } = options;
|
|
208
198
|
const runTask = (input, signal) => worker.runTask(task, input, { signal });
|
|
209
199
|
const value = signal(options.defaultValue, { ...(ngDevMode ? { debugName: "value" } : /* istanbul ignore next */ {}), equal: userEqual
|
|
210
|
-
? (a, b) =>
|
|
200
|
+
? (a, b) => a === undefined || b === undefined ? a === b : userEqual(a, b)
|
|
211
201
|
: undefined });
|
|
212
202
|
const status = signal('idle', ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
213
203
|
const error = signal(undefined, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
@@ -233,7 +223,7 @@ function build$1(params, options) {
|
|
|
233
223
|
if (inFlight === ac)
|
|
234
224
|
inFlight = null;
|
|
235
225
|
if (myEpoch !== epoch)
|
|
236
|
-
return;
|
|
226
|
+
return;
|
|
237
227
|
value.set(result);
|
|
238
228
|
status.set('resolved');
|
|
239
229
|
}, (err) => {
|
|
@@ -241,7 +231,8 @@ function build$1(params, options) {
|
|
|
241
231
|
inFlight = null;
|
|
242
232
|
if (myEpoch !== epoch)
|
|
243
233
|
return;
|
|
244
|
-
if (err instanceof WorkerAbortError ||
|
|
234
|
+
if (err instanceof WorkerAbortError ||
|
|
235
|
+
err?.name === 'AbortError')
|
|
245
236
|
return;
|
|
246
237
|
error.set(err);
|
|
247
238
|
status.set('error');
|
|
@@ -251,10 +242,9 @@ function build$1(params, options) {
|
|
|
251
242
|
const ref = effect(() => {
|
|
252
243
|
const i = input();
|
|
253
244
|
if (isServer || i === PAUSED)
|
|
254
|
-
return;
|
|
245
|
+
return;
|
|
255
246
|
if (i === undefined)
|
|
256
|
-
return;
|
|
257
|
-
// dedup: a resume to the same input (e.g. after a pause) does not re-run
|
|
247
|
+
return;
|
|
258
248
|
if (Object.is(i, lastInput) && hasRun && untracked(status) !== 'error')
|
|
259
249
|
return;
|
|
260
250
|
untracked(() => start(i));
|
|
@@ -277,11 +267,11 @@ function build$1(params, options) {
|
|
|
277
267
|
},
|
|
278
268
|
abort: () => {
|
|
279
269
|
if (!inFlight)
|
|
280
|
-
return;
|
|
270
|
+
return;
|
|
281
271
|
epoch++;
|
|
282
272
|
inFlight.abort();
|
|
283
273
|
inFlight = null;
|
|
284
|
-
status.set('local');
|
|
274
|
+
status.set('local');
|
|
285
275
|
},
|
|
286
276
|
destroy: () => {
|
|
287
277
|
epoch++;
|
|
@@ -292,105 +282,133 @@ function build$1(params, options) {
|
|
|
292
282
|
if (options.register) {
|
|
293
283
|
const scope = injectTransitionScope();
|
|
294
284
|
scope.add(self, { suspends: options.register === 'suspend' });
|
|
295
|
-
|
|
285
|
+
let removed = false;
|
|
286
|
+
const remove = () => {
|
|
287
|
+
if (removed)
|
|
288
|
+
return;
|
|
289
|
+
removed = true;
|
|
290
|
+
scope.remove(self);
|
|
291
|
+
};
|
|
292
|
+
inject(DestroyRef).onDestroy(remove);
|
|
293
|
+
const destroy = self.destroy;
|
|
294
|
+
self.destroy = () => {
|
|
295
|
+
remove();
|
|
296
|
+
destroy();
|
|
297
|
+
};
|
|
296
298
|
}
|
|
297
299
|
return self;
|
|
298
300
|
}
|
|
299
301
|
|
|
302
|
+
// An injector-free opLog driver (schedules emission off the microtask queue via `createWatch`), so
|
|
303
|
+
// the replica's `opSync` emits without depending on an application tick. Writes force a synchronous
|
|
304
|
+
// `flush()` at the call site; between writes, owner envelopes drive apply, never local emission.
|
|
305
|
+
const microtaskDriver = () => (run) => {
|
|
306
|
+
let scheduled = false;
|
|
307
|
+
const watch = createWatch(() => run(), (w) => {
|
|
308
|
+
if (scheduled)
|
|
309
|
+
return;
|
|
310
|
+
scheduled = true;
|
|
311
|
+
queueMicrotask(() => {
|
|
312
|
+
scheduled = false;
|
|
313
|
+
w.run();
|
|
314
|
+
});
|
|
315
|
+
}, false);
|
|
316
|
+
watch.notify();
|
|
317
|
+
return { destroy: () => watch.destroy() };
|
|
318
|
+
};
|
|
300
319
|
function workerStore(worker, key, opt) {
|
|
301
320
|
const injector = opt?.injector ?? inject(Injector);
|
|
302
321
|
return runInInjectionContext(injector, () => build(worker, key, opt));
|
|
303
322
|
}
|
|
304
323
|
function build(worker, key, opt) {
|
|
305
|
-
const injector = inject(Injector);
|
|
324
|
+
const injector = inject(Injector);
|
|
306
325
|
const root = signal(opt?.defaultValue, ...(ngDevMode ? [{ debugName: "root" }] : /* istanbul ignore next */ []));
|
|
307
|
-
|
|
308
|
-
const replica = toStore(root, {
|
|
326
|
+
const s = toStore(root, {
|
|
309
327
|
injector,
|
|
310
328
|
});
|
|
329
|
+
// The replica runs its own opSync over the worker transport: local writes emit stamped envelopes
|
|
330
|
+
// routed to the owner, owner envelopes fold in convergently. A driver (not the injector) so
|
|
331
|
+
// emission stays synchronous under an explicit `flush()`, matching the DI-free worker model.
|
|
332
|
+
const sync = opSync(root, {
|
|
333
|
+
writer: worker.clientId,
|
|
334
|
+
driver: microtaskDriver(),
|
|
335
|
+
onGap: () => resync(),
|
|
336
|
+
});
|
|
311
337
|
const status = signal('loading', ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
312
338
|
const error = signal(undefined, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
313
339
|
const isLoading = computed(() => {
|
|
314
|
-
const
|
|
315
|
-
return
|
|
340
|
+
const st = status();
|
|
341
|
+
return st === 'loading' || st === 'reloading';
|
|
316
342
|
}, ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
|
|
317
343
|
let hasSnapshot = false;
|
|
318
|
-
let
|
|
319
|
-
|
|
320
|
-
let writeSeq = 0;
|
|
344
|
+
let lastEmitted = 0; // highest own-origin version emitted; the write() ack key
|
|
345
|
+
// own writes awaiting the owner's echo (resolve), plus the raw envelopes to resend after a resync
|
|
321
346
|
const writePending = new Map();
|
|
347
|
+
const unacked = new Map();
|
|
322
348
|
const hasValue = () => hasSnapshot;
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
for (const [
|
|
326
|
-
if (
|
|
327
|
-
writePending.delete(
|
|
349
|
+
const isOwned = () => worker.manifest()?.stores.includes(key) ?? false;
|
|
350
|
+
const ackUpTo = (version) => {
|
|
351
|
+
for (const [v, p] of writePending) {
|
|
352
|
+
if (v <= version) {
|
|
353
|
+
writePending.delete(v);
|
|
328
354
|
p.resolve();
|
|
329
355
|
}
|
|
330
356
|
}
|
|
357
|
+
for (const v of [...unacked.keys()])
|
|
358
|
+
if (v <= version)
|
|
359
|
+
unacked.delete(v);
|
|
331
360
|
};
|
|
361
|
+
const shipUnsub = sync.subscribe((env) => {
|
|
362
|
+
lastEmitted = env.version;
|
|
363
|
+
unacked.set(env.version, env);
|
|
364
|
+
if (isOwned() && untracked(worker.connected)) {
|
|
365
|
+
worker._send({ type: 'store:sync', store: key, env });
|
|
366
|
+
}
|
|
367
|
+
});
|
|
332
368
|
const onStoreMessage = (msg) => {
|
|
333
369
|
if (!('store' in msg) || msg.store !== key)
|
|
334
370
|
return;
|
|
335
371
|
switch (msg.type) {
|
|
336
|
-
case 'store:
|
|
337
|
-
|
|
338
|
-
|
|
372
|
+
case 'store:checkpoint': {
|
|
373
|
+
// rebase from the full unacked outbox, not opSync's bounded recent-local ring, so a burst
|
|
374
|
+
// of writes larger than that ring is never dropped from the local rebase on re-hydrate
|
|
375
|
+
sync.hydrate(msg.checkpoint, [
|
|
376
|
+
...unacked.values(),
|
|
377
|
+
]);
|
|
339
378
|
hasSnapshot = true;
|
|
340
|
-
resyncing = false; // a fresh snapshot ends any resync
|
|
341
379
|
status.set('resolved');
|
|
342
380
|
error.set(undefined);
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
return; // stale/duplicate (at-least-once transport)
|
|
351
|
-
if (msg.batch.version > lastVersion + 1) {
|
|
352
|
-
// GAP: a batch was lost — re-hydrate from a fresh snapshot rather than apply out of order
|
|
353
|
-
resync();
|
|
354
|
-
return;
|
|
381
|
+
// writes the owner already applied are covered by the checkpoint watermark; resolve them,
|
|
382
|
+
// then resend any still-unacked tail so a write made before the (re)hydrate is never lost
|
|
383
|
+
ackUpTo(msg.checkpoint.wm?.[sync.origin] ?? 0);
|
|
384
|
+
if (isOwned() && untracked(worker.connected)) {
|
|
385
|
+
for (const env of [...unacked.values()].sort((a, b) => a.version - b.version)) {
|
|
386
|
+
worker._send({ type: 'store:sync', store: key, env });
|
|
387
|
+
}
|
|
355
388
|
}
|
|
356
|
-
root.set(applyOps(untracked(root), msg.batch.ops));
|
|
357
|
-
lastVersion = msg.batch.version;
|
|
358
|
-
settleWrites();
|
|
359
389
|
return;
|
|
360
390
|
}
|
|
361
|
-
case 'store:
|
|
362
|
-
const
|
|
363
|
-
if (
|
|
391
|
+
case 'store:sync': {
|
|
392
|
+
const env = msg.env;
|
|
393
|
+
if (env.origin === sync.origin) {
|
|
394
|
+
ackUpTo(env.version); // the owner echoed our own write back: acknowledgement
|
|
364
395
|
return;
|
|
365
|
-
if (lastVersion >= msg.version) {
|
|
366
|
-
writePending.delete(msg.writeId);
|
|
367
|
-
p.resolve(); // the authoritative batch already landed (FIFO: ops precede the ack)
|
|
368
|
-
}
|
|
369
|
-
else {
|
|
370
|
-
p.version = msg.version; // resolve once a batch reaches this version
|
|
371
|
-
}
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
case 'store:write:error': {
|
|
375
|
-
const p = writePending.get(msg.writeId);
|
|
376
|
-
if (p) {
|
|
377
|
-
writePending.delete(msg.writeId);
|
|
378
|
-
p.reject(deserializeError(msg.error));
|
|
379
396
|
}
|
|
397
|
+
sync.receive(env); // owner or peer envelope; a version gap triggers onGap -> resync
|
|
380
398
|
return;
|
|
381
399
|
}
|
|
382
400
|
case 'store:status': {
|
|
383
|
-
// rung 3: a published derivation's remote status → this replica's status, so an in-flight
|
|
384
|
-
// worker computation shows as pending on the main thread (holding the last value)
|
|
385
401
|
switch (msg.status) {
|
|
386
402
|
case 'error':
|
|
387
|
-
error.set(msg.error
|
|
403
|
+
error.set(msg.error
|
|
404
|
+
? deserializeError(msg.error)
|
|
405
|
+
: new Error('remote computation error'));
|
|
388
406
|
status.set('error');
|
|
389
407
|
return;
|
|
390
408
|
case 'loading':
|
|
391
409
|
case 'reloading':
|
|
392
410
|
error.set(undefined);
|
|
393
|
-
status.set(hasSnapshot ? 'reloading' : 'loading');
|
|
411
|
+
status.set(hasSnapshot ? 'reloading' : 'loading');
|
|
394
412
|
return;
|
|
395
413
|
case 'resolved':
|
|
396
414
|
error.set(undefined);
|
|
@@ -405,29 +423,33 @@ function build(worker, key, opt) {
|
|
|
405
423
|
};
|
|
406
424
|
const subscribe = () => {
|
|
407
425
|
if (hasSnapshot)
|
|
408
|
-
status.set('reloading');
|
|
409
|
-
worker._send({
|
|
426
|
+
status.set('reloading');
|
|
427
|
+
worker._send({
|
|
428
|
+
type: 'store:subscribe',
|
|
429
|
+
store: key,
|
|
430
|
+
clientId: worker.clientId,
|
|
431
|
+
});
|
|
410
432
|
};
|
|
411
|
-
// a lost batch left a version gap — hold the stale value and re-subscribe for a fresh snapshot
|
|
412
433
|
const resync = () => {
|
|
413
|
-
resyncing = true;
|
|
414
434
|
status.set('reloading');
|
|
415
|
-
worker._send({
|
|
435
|
+
worker._send({
|
|
436
|
+
type: 'store:subscribe',
|
|
437
|
+
store: key,
|
|
438
|
+
clientId: worker.clientId,
|
|
439
|
+
});
|
|
416
440
|
};
|
|
417
441
|
const unsub = worker._subscribe(onStoreMessage);
|
|
418
|
-
// subscribe on each `ready` — the first connection and every auto-restart re-hydration; if the
|
|
419
|
-
// connection is ALREADY up (created post-handshake), `_onReady` won't fire, so subscribe now
|
|
420
442
|
const offReady = worker._onReady(subscribe);
|
|
421
443
|
if (untracked(worker.connected))
|
|
422
|
-
subscribe();
|
|
423
|
-
// a crash can't be delivered — pending writes reject (the caller holds the value + recipe to retry)
|
|
444
|
+
subscribe(); // already up (created post-handshake): _onReady won't fire
|
|
424
445
|
const offDisconnect = worker._onDisconnect(() => {
|
|
425
446
|
for (const [, p] of writePending)
|
|
426
447
|
p.reject(new WorkerCrashedError());
|
|
427
448
|
writePending.clear();
|
|
449
|
+
unacked.clear();
|
|
428
450
|
});
|
|
429
451
|
const self = {
|
|
430
|
-
store:
|
|
452
|
+
store: s,
|
|
431
453
|
value: root.asReadonly(),
|
|
432
454
|
status,
|
|
433
455
|
error,
|
|
@@ -438,54 +460,58 @@ function build(worker, key, opt) {
|
|
|
438
460
|
const base = untracked(root);
|
|
439
461
|
if (base === undefined)
|
|
440
462
|
return Promise.reject(new Error('[@mmstack/worker] cannot write before the replica has hydrated'));
|
|
441
|
-
// hydrated but disconnected = the worker crashed (or is restarting) — a write posted now
|
|
442
|
-
// vanishes into a dead port and its echo can never arrive. Reject; the caller retries.
|
|
443
463
|
if (!untracked(worker.connected))
|
|
444
464
|
return Promise.reject(new WorkerCrashedError('worker is not connected'));
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const
|
|
453
|
-
if (!ops.length)
|
|
454
|
-
return Promise.resolve();
|
|
455
|
-
const writeId = ++writeSeq;
|
|
465
|
+
if (!isOwned())
|
|
466
|
+
return Promise.reject(new Error(`[@mmstack/worker] store is read-only (published): ${key}`));
|
|
467
|
+
recipe(s);
|
|
468
|
+
const before = lastEmitted;
|
|
469
|
+
sync.flush(); // emit the routed write synchronously (driver-backed opSync)
|
|
470
|
+
if (lastEmitted === before)
|
|
471
|
+
return Promise.resolve(); // no-op recipe: nothing shipped
|
|
472
|
+
const version = lastEmitted;
|
|
456
473
|
return new Promise((resolve, reject) => {
|
|
457
|
-
writePending.set(
|
|
458
|
-
worker._send({
|
|
459
|
-
type: 'store:write',
|
|
460
|
-
store: key,
|
|
461
|
-
writeId,
|
|
462
|
-
clientId: worker.clientId,
|
|
463
|
-
ops,
|
|
464
|
-
});
|
|
474
|
+
writePending.set(version, { resolve, reject });
|
|
465
475
|
});
|
|
466
476
|
},
|
|
467
477
|
destroy: () => {
|
|
478
|
+
shipUnsub();
|
|
479
|
+
sync.destroy();
|
|
468
480
|
unsub();
|
|
469
481
|
offReady();
|
|
470
482
|
offDisconnect();
|
|
471
|
-
// acks can no longer be received — settle any in-flight write instead of dangling it
|
|
472
483
|
for (const [, p] of writePending)
|
|
473
484
|
p.reject(new WorkerAbortError('worker store destroyed'));
|
|
474
485
|
writePending.clear();
|
|
475
|
-
|
|
486
|
+
unacked.clear();
|
|
487
|
+
worker._send({
|
|
488
|
+
type: 'store:unsubscribe',
|
|
489
|
+
store: key,
|
|
490
|
+
clientId: worker.clientId,
|
|
491
|
+
});
|
|
476
492
|
},
|
|
477
493
|
};
|
|
478
494
|
inject(DestroyRef).onDestroy(() => self.destroy());
|
|
479
495
|
if (opt?.register) {
|
|
480
496
|
const scope = injectTransitionScope();
|
|
481
497
|
scope.add(self, { suspends: opt.register === 'suspend' });
|
|
482
|
-
|
|
498
|
+
let removed = false;
|
|
499
|
+
const remove = () => {
|
|
500
|
+
if (removed)
|
|
501
|
+
return;
|
|
502
|
+
removed = true;
|
|
503
|
+
scope.remove(self);
|
|
504
|
+
};
|
|
505
|
+
inject(DestroyRef).onDestroy(remove);
|
|
506
|
+
const destroy = self.destroy;
|
|
507
|
+
self.destroy = () => {
|
|
508
|
+
remove();
|
|
509
|
+
destroy();
|
|
510
|
+
};
|
|
483
511
|
}
|
|
484
512
|
return self;
|
|
485
513
|
}
|
|
486
514
|
|
|
487
|
-
// shared wire types, re-exported so main-thread consumers import only from '@mmstack/worker'
|
|
488
|
-
|
|
489
515
|
/**
|
|
490
516
|
* Generated bundle index. Do not edit.
|
|
491
517
|
*/
|