@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
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @mmstack/worker
|
|
2
2
|
|
|
3
|
+
> **Experimental.** The API may still change and this package is not yet battle-tested in production. Pin a version and expect some churn.
|
|
4
|
+
|
|
3
5
|
Split-graph state and compute for Angular. Keep the reactive graph on the main thread for rendering, and hand owned state plus heavy computation to a Web Worker that runs its own graph. The main thread reads live replicas and routes writes; the worker owns the data, derives from it, and answers tasks. State stays in sync automatically over minimal deltas, never full snapshots.
|
|
4
6
|
|
|
5
7
|
[](https://github.com/mihajm/mmstack/blob/master/packages/worker/LICENSE)
|
|
@@ -8,10 +10,10 @@ Built on the store op-log from [`@mmstack/primitives`](https://www.npmjs.com/pac
|
|
|
8
10
|
|
|
9
11
|
## Highlights
|
|
10
12
|
|
|
11
|
-
- **Main renders, worker computes.** The worker owns stores and derivations and runs off the main thread. The main thread holds
|
|
13
|
+
- **Main renders, worker computes.** The worker owns stores and derivations and runs off the main thread. The main thread holds a live replica of each owned store (a real, writable signal store), so heavy work makes the UI pending, not frozen.
|
|
12
14
|
- **Deltas, not snapshots.** State mirrors as op batches (one `set`/`delete` per changed path), diffed by reference identity. The initial hydration is the only snapshot; everything after is a minimal delta.
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
+
- **One owner, provable convergence.** Each store subtree has exactly one owner (the worker). Writes apply optimistically on the main thread and route to that owner, which folds them in and echoes them back. The main thread and the worker run the same convergent sync the mesh uses, so interleaved writes from several components land on identical state, and an authoritative owner correction wins wherever it meets a concurrent write.
|
|
16
|
+
- **Optimistic by default, composable.** A write is visible immediately and its promise resolves once the owner confirms; fork the store if you want honest hide-until-confirmed. And because an owned store is a writable op-log endpoint, `persist` and `@mmstack/mesh`'s `meshSync` attach to it directly: a persisted, meshed, worker-owned graph with no bridge.
|
|
15
17
|
- **The resource surface you already know.** `workerResource` runs a function off-main and exposes `value` / `status` / `error` / `isLoading`, so it drops into `latest()` / `use()` and Suspense boundaries with no adapter.
|
|
16
18
|
- **Typed from the worker's contract.** `connectWorker<typeof host>()` infers store keys, value types, task signatures, and whether a subtree is writable, all from the worker you wrote.
|
|
17
19
|
- **SSR safe.** On the server nothing spawns. Replicas render their default value and resolve once the worker connects in the browser.
|
|
@@ -37,7 +39,7 @@ There are two reactive graphs and a wire between them.
|
|
|
37
39
|
|
|
38
40
|
**The main graph** renders. From a component you `connectWorker` (which spawns the worker) and then:
|
|
39
41
|
|
|
40
|
-
1. `workerStore(worker, key)` gives a live
|
|
42
|
+
1. `workerStore(worker, key)` gives a live replica of an owned store (a writable op-log endpoint): read it, `write()` to route a change to the owner (optimistic), and attach `persist`/`meshSync` readers to it,
|
|
41
43
|
2. `workerStore(worker, key)` on a published key gives a read-only mirror of a derivation (no `write()`),
|
|
42
44
|
3. `workerResource(...)` runs a task and exposes it with the standard resource surface.
|
|
43
45
|
|
|
@@ -248,24 +250,20 @@ await todos.write((draft) => {
|
|
|
248
250
|
});
|
|
249
251
|
```
|
|
250
252
|
|
|
251
|
-
`write` runs your recipe against
|
|
253
|
+
`write` runs your recipe against the replica right away, diffs it to minimal ops, and ships them to the owner. The owner folds them in and echoes them back; the promise resolves once that echo lands. The value is on screen immediately, and the round trip is honest in the API rather than hidden behind a `set` you cannot read back.
|
|
252
254
|
|
|
253
|
-
###
|
|
255
|
+
### Hide until confirmed (opt-in)
|
|
254
256
|
|
|
255
|
-
|
|
257
|
+
Optimistic is the default because it matches how the rest of the stack syncs. When you would rather not show a value until the owner accepts it (say the owner can correct or reject the write), fork the store, write to the fork, and reveal it only when the promise resolves:
|
|
256
258
|
|
|
257
259
|
```ts
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
await todos.write((d) => d.set(next));
|
|
266
|
-
} finally {
|
|
267
|
-
optimistic.set(null); // the owner's authoritative batch has landed on the replica
|
|
268
|
-
}
|
|
260
|
+
const draft = forkStore(todos.store);
|
|
261
|
+
draft.store.status.set('done');
|
|
262
|
+
try {
|
|
263
|
+
await todos.write((d) => d.status.set('done'));
|
|
264
|
+
draft.commit(); // accepted: reveal
|
|
265
|
+
} catch {
|
|
266
|
+
draft.discard(); // rejected: drop it
|
|
269
267
|
}
|
|
270
268
|
```
|
|
271
269
|
|
|
@@ -305,9 +303,9 @@ workerResource(() => transfer({ buffer }, [buffer]), { worker, task: 'process' }
|
|
|
305
303
|
|
|
306
304
|
## How it works
|
|
307
305
|
|
|
308
|
-
|
|
306
|
+
The main thread and the worker run the same convergent sync as the mesh (`opSync` from `@mmstack/primitives`) over a `MessageChannel`. Each owned store on the worker runs the owner side; every replica applies incoming envelopes in a single `set` (one notification wave, regardless of how many ops an envelope carries). The first join hydrates from a checkpoint, everything after is a minimal delta, and a version gap re-hydrates.
|
|
309
307
|
|
|
310
|
-
|
|
308
|
+
A write applies optimistically on the writer and routes to the owner, which folds it in and echoes it to every replica; the writer's replica reads its own echo as the acknowledgement. Because it is the same convergent register the mesh uses, interleaved writes from several components land on the same value on every replica. The owner can also correct a value authoritatively, and that correction wins wherever it meets a concurrent write, so a single owner keeps the last word over its subtree without a separate request protocol.
|
|
311
309
|
|
|
312
310
|
The protocol rides a minimal structural port (`postMessage` / `onmessage`), so a `MessageChannel` pair works in tests and the same envelope stream can later run over other transports.
|
|
313
311
|
|
|
@@ -2,7 +2,7 @@ import { generateId, serializeError, PROTO_VERSION } from '@mmstack/worker/proto
|
|
|
2
2
|
export * from '@mmstack/worker/protocol';
|
|
3
3
|
import { isDevMode, untracked } from '@angular/core';
|
|
4
4
|
import { createWatch } from '@angular/core/primitives/signals';
|
|
5
|
-
import {
|
|
5
|
+
import { opSync, createHlcClock, createStoreContext } from '@mmstack/primitives';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* An injector-free {@link OpLogDriver} for `@mmstack/primitives` `opLog`. Schedules the emission
|
|
@@ -21,32 +21,26 @@ import { opLog, applyOps, createStoreContext } from '@mmstack/primitives';
|
|
|
21
21
|
function microtaskOpLogDriver() {
|
|
22
22
|
return (run) => {
|
|
23
23
|
let scheduled = false;
|
|
24
|
-
// run reads the source (tracking) and flushes; it never writes signals, so allowSignalWrites=false
|
|
25
24
|
const watch = createWatch(() => run(), (w) => {
|
|
26
25
|
if (scheduled)
|
|
27
26
|
return;
|
|
28
27
|
scheduled = true;
|
|
29
28
|
queueMicrotask(() => {
|
|
30
29
|
scheduled = false;
|
|
31
|
-
w.run();
|
|
30
|
+
w.run();
|
|
32
31
|
});
|
|
33
32
|
}, false);
|
|
34
|
-
watch.notify();
|
|
33
|
+
watch.notify();
|
|
35
34
|
return { destroy: () => watch.destroy() };
|
|
36
35
|
};
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
/** Maps an Angular ResourceStatus to the wire status; only the states the protocol carries. */
|
|
40
38
|
function toRemoteStatus(s) {
|
|
41
39
|
return s === 'idle' || s === 'loading' || s === 'reloading' || s === 'error'
|
|
42
40
|
? s
|
|
43
41
|
: 'resolved';
|
|
44
42
|
}
|
|
45
|
-
|
|
46
|
-
* Dev-only guard: a store value that is not structured-clonable (a function, a class instance, an
|
|
47
|
-
* `opaque()` value) throws a bare `DataCloneError` from `postMessage` with no context. Catch it
|
|
48
|
-
* before it hits the wire and name the store, so the fix is obvious. Stripped in production.
|
|
49
|
-
*/
|
|
43
|
+
// dev-only: catch a non-cloneable store value before postMessage throws a context-free DataCloneError
|
|
50
44
|
function devAssertCloneable(value, what) {
|
|
51
45
|
if (!isDevMode())
|
|
52
46
|
return;
|
|
@@ -79,7 +73,6 @@ function isWorkerGlobal(g) {
|
|
|
79
73
|
*/
|
|
80
74
|
function createWorkerHost(options) {
|
|
81
75
|
const hostId = generateId();
|
|
82
|
-
// the S/P/T generics drive the RETURN type only; the body works over loose records
|
|
83
76
|
const sources = (options.stores ?? {});
|
|
84
77
|
const publishedSources = (options.published ?? {});
|
|
85
78
|
const tasks = (options.tasks ?? {});
|
|
@@ -88,8 +81,6 @@ function createWorkerHost(options) {
|
|
|
88
81
|
const publishedKeys = Object.keys(publishedSources);
|
|
89
82
|
const subtrees = new Map();
|
|
90
83
|
const connections = new Set();
|
|
91
|
-
// current wire status of each status-bearing published entry, read at subscribe time so a late
|
|
92
|
-
// subscriber sees an in-flight computation as pending instead of waiting for the next transition
|
|
93
84
|
const publishedStatus = new Map();
|
|
94
85
|
const statusWatches = [];
|
|
95
86
|
const fanout = (store, message) => {
|
|
@@ -97,31 +88,28 @@ function createWorkerHost(options) {
|
|
|
97
88
|
if (conn.stores.has(store))
|
|
98
89
|
conn.port.postMessage(message);
|
|
99
90
|
};
|
|
100
|
-
// observe a signal's value → snapshot/ops. Owned stores also route writes; published are read-only.
|
|
101
|
-
// Applied remote writes ride this same emission, echo-free (the owner is the single sequencer).
|
|
102
91
|
const observe = (key, src, writable) => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
entry.log.subscribe((batch) => {
|
|
113
|
-
entry.version = batch.version;
|
|
114
|
-
devAssertCloneable(batch, `store '${key}' update`);
|
|
115
|
-
fanout(key, { type: 'store:ops', store: key, batch });
|
|
92
|
+
// Each subtree runs the owner's opSync (a real origin + HLC): its batches are real envelopes,
|
|
93
|
+
// so the whole main<->worker sync is opSync over the MessageChannel transport, no bespoke
|
|
94
|
+
// sequencer. Owner writes emit host-origin envelopes; received client writes fold in and are
|
|
95
|
+
// relayed on. Emission is driven off the microtask queue (no injector inside a worker).
|
|
96
|
+
const sync = opSync(src, {
|
|
97
|
+
writer: hostId,
|
|
98
|
+
origin: `${hostId}:${key}`,
|
|
99
|
+
driver: microtaskOpLogDriver(),
|
|
100
|
+
clock: createHlcClock(),
|
|
116
101
|
});
|
|
117
|
-
|
|
102
|
+
sync.subscribe((env) => {
|
|
103
|
+
devAssertCloneable(env, `store '${key}' update`);
|
|
104
|
+
fanout(key, { type: 'store:sync', store: key, env });
|
|
105
|
+
});
|
|
106
|
+
subtrees.set(key, { read: () => untracked(src), sync, writable });
|
|
118
107
|
};
|
|
119
108
|
for (const key of storeKeys)
|
|
120
|
-
observe(key, sources[key],
|
|
109
|
+
observe(key, sources[key], true);
|
|
121
110
|
for (const key of publishedKeys) {
|
|
122
111
|
const src = publishedSources[key];
|
|
123
|
-
observe(key, src,
|
|
124
|
-
// rung 3: a status-bearing derivation (a latest()) propagates its pending/error to replicas
|
|
112
|
+
observe(key, src, false);
|
|
125
113
|
const statusSig = src.status;
|
|
126
114
|
if (statusSig) {
|
|
127
115
|
publishedStatus.set(key, () => toRemoteStatus(untracked(statusSig)));
|
|
@@ -192,20 +180,16 @@ function createWorkerHost(options) {
|
|
|
192
180
|
case 'store:subscribe': {
|
|
193
181
|
const sub = subtrees.get(msg.store);
|
|
194
182
|
if (!sub)
|
|
195
|
-
return;
|
|
196
|
-
// synchronous: read snapshot + version and register the client in one turn — no batch can
|
|
197
|
-
// interleave in a single-threaded worker, so every later op carries version > this one
|
|
183
|
+
return;
|
|
198
184
|
conn.stores.add(msg.store);
|
|
199
|
-
|
|
200
|
-
|
|
185
|
+
sub.sync.flush(); // settle any pending owner change into the register state first
|
|
186
|
+
const checkpoint = sub.sync.snapshot();
|
|
187
|
+
devAssertCloneable(checkpoint, `store '${msg.store}' checkpoint`);
|
|
201
188
|
conn.port.postMessage({
|
|
202
|
-
type: 'store:
|
|
189
|
+
type: 'store:checkpoint',
|
|
203
190
|
store: msg.store,
|
|
204
|
-
|
|
205
|
-
value: snapshot,
|
|
191
|
+
checkpoint,
|
|
206
192
|
});
|
|
207
|
-
// a status-bearing published entry also reports its CURRENT status, so a subscriber
|
|
208
|
-
// arriving mid-computation shows pending now, not on the next transition
|
|
209
193
|
const currentStatus = publishedStatus.get(msg.store);
|
|
210
194
|
if (currentStatus)
|
|
211
195
|
conn.port.postMessage({
|
|
@@ -237,41 +221,15 @@ function createWorkerHost(options) {
|
|
|
237
221
|
conn.taskRuns.delete(msg.runId);
|
|
238
222
|
return;
|
|
239
223
|
}
|
|
240
|
-
case 'store:
|
|
224
|
+
case 'store:sync': {
|
|
225
|
+
// a routed client write: fold it into the owner store, then RELAY it to every subscriber
|
|
226
|
+
// (including the sender, whose replica reads its own echo as the write acknowledgement).
|
|
227
|
+
// Writes to a read-only (published) or unknown store are ignored: those never route.
|
|
241
228
|
const sub = subtrees.get(msg.store);
|
|
242
|
-
if (!sub || !sub.writable)
|
|
243
|
-
conn.port.postMessage({
|
|
244
|
-
type: 'store:write:error',
|
|
245
|
-
store: msg.store,
|
|
246
|
-
writeId: msg.writeId,
|
|
247
|
-
error: serializeError(new Error(sub
|
|
248
|
-
? `[@mmstack/worker] store is read-only (published): ${msg.store}`
|
|
249
|
-
: `[@mmstack/worker] unknown store: ${msg.store}`)),
|
|
250
|
-
});
|
|
229
|
+
if (!sub || !sub.writable)
|
|
251
230
|
return;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
// the owner is the single sequencer: apply the routed ops through the store root, then
|
|
255
|
-
// FLUSH so its opLog emits ONE authoritative owner-origin batch (fanned to every client,
|
|
256
|
-
// the writer included — the echo that IS the write's confirmation). No baseline-advance
|
|
257
|
-
// trick: this is a genuine owner write, indistinguishable from the worker writing itself.
|
|
258
|
-
sub.writable.set(applyOps(sub.read(), msg.ops));
|
|
259
|
-
sub.log.flush();
|
|
260
|
-
conn.port.postMessage({
|
|
261
|
-
type: 'store:write:ack',
|
|
262
|
-
store: msg.store,
|
|
263
|
-
writeId: msg.writeId,
|
|
264
|
-
version: sub.version,
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
catch (err) {
|
|
268
|
-
conn.port.postMessage({
|
|
269
|
-
type: 'store:write:error',
|
|
270
|
-
store: msg.store,
|
|
271
|
-
writeId: msg.writeId,
|
|
272
|
-
error: serializeError(err),
|
|
273
|
-
});
|
|
274
|
-
}
|
|
231
|
+
sub.sync.receive(msg.env);
|
|
232
|
+
fanout(msg.store, { type: 'store:sync', store: msg.store, env: msg.env });
|
|
275
233
|
return;
|
|
276
234
|
}
|
|
277
235
|
}
|
|
@@ -284,11 +242,10 @@ function createWorkerHost(options) {
|
|
|
284
242
|
taskRuns: new Map(),
|
|
285
243
|
};
|
|
286
244
|
connections.add(conn);
|
|
287
|
-
// assigning onmessage starts a MessagePort; a Worker/self delivers the same way
|
|
288
245
|
port.onmessage = (ev) => handle(conn, ev.data);
|
|
289
246
|
return () => {
|
|
290
247
|
connections.delete(conn);
|
|
291
|
-
port.onmessage = null;
|
|
248
|
+
port.onmessage = null;
|
|
292
249
|
for (const ac of conn.taskRuns.values())
|
|
293
250
|
ac.abort();
|
|
294
251
|
conn.taskRuns.clear();
|
|
@@ -303,13 +260,18 @@ function createWorkerHost(options) {
|
|
|
303
260
|
return {
|
|
304
261
|
hostId,
|
|
305
262
|
connect,
|
|
263
|
+
override(store, fn) {
|
|
264
|
+
const sub = subtrees.get(store);
|
|
265
|
+
if (sub?.writable)
|
|
266
|
+
sub.sync.override(fn);
|
|
267
|
+
},
|
|
306
268
|
flush() {
|
|
307
|
-
for (const {
|
|
308
|
-
|
|
269
|
+
for (const { sync } of subtrees.values())
|
|
270
|
+
sync.flush();
|
|
309
271
|
},
|
|
310
272
|
dispose() {
|
|
311
|
-
for (const {
|
|
312
|
-
|
|
273
|
+
for (const { sync } of subtrees.values())
|
|
274
|
+
sync.destroy();
|
|
313
275
|
for (const w of statusWatches)
|
|
314
276
|
w.destroy();
|
|
315
277
|
connections.clear();
|
|
@@ -317,10 +279,6 @@ function createWorkerHost(options) {
|
|
|
317
279
|
};
|
|
318
280
|
}
|
|
319
281
|
|
|
320
|
-
// Memoized at THIS module's scope. `@mmstack/worker/host` only ever loads inside a worker, so a
|
|
321
|
-
// module-scope singleton here is a per-thread singleton — the worker equivalent of an app's root
|
|
322
|
-
// injector (providedIn: 'root'). All stores in the worker share it; it is the graph's single
|
|
323
|
-
// proxy-identity + GC-coordination point.
|
|
324
282
|
let context;
|
|
325
283
|
/**
|
|
326
284
|
* The one shared store context for this worker — pass it to every `store`/`toStore` you create in
|
|
@@ -337,8 +295,6 @@ function workerStoreContext() {
|
|
|
337
295
|
return (context ??= createStoreContext());
|
|
338
296
|
}
|
|
339
297
|
|
|
340
|
-
// shared wire types, re-exported so worker code imports only from '@mmstack/worker/host'
|
|
341
|
-
|
|
342
298
|
/**
|
|
343
299
|
* Generated bundle index. Do not edit.
|
|
344
300
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mmstack-worker-host.mjs","sources":["../../../../packages/worker/host/src/lib/microtask-driver.ts","../../../../packages/worker/host/src/lib/create-worker-host.ts","../../../../packages/worker/host/src/lib/worker-store-context.ts","../../../../packages/worker/host/src/index.ts","../../../../packages/worker/host/src/mmstack-worker-host.ts"],"sourcesContent":["import { createWatch } from '@angular/core/primitives/signals';\nimport type { OpLogDriver } from '@mmstack/primitives';\n\n/**\n * An injector-free {@link OpLogDriver} for `@mmstack/primitives` `opLog`. Schedules the emission\n * reaction on the microtask queue via `createWatch` (Angular's renderer-independent watch\n * primitive) instead of an `effect`, so an opLog can run where there is no Angular injector: the\n * worker side of the worker-graph, whose store has no application tick driving it.\n *\n * This lives in `@mmstack/worker/host` rather than in primitives on purpose. `createWatch` is a\n * lower-tier signals-primitives export; keeping it out of `@mmstack/primitives` lets that package\n * back-port to older Angular majors that may not export it. The worker package tracks a newer\n * Angular where `createWatch` is available.\n *\n * Batching is per-microtask rather than per-app-tick; the worker protocol orders by\n * `(origin, version)`, never tick alignment, so that is safe.\n */\nexport function microtaskOpLogDriver(): OpLogDriver {\n return (run) => {\n let scheduled = false;\n // run reads the source (tracking) and flushes; it never writes signals, so allowSignalWrites=false\n const watch = createWatch(\n () => run(),\n (w) => {\n if (scheduled) return;\n scheduled = true;\n queueMicrotask(() => {\n scheduled = false;\n w.run(); // no-op if the watch is not dirty\n });\n },\n false,\n );\n watch.notify(); // bootstrap: schedule the first run to establish the source dependency\n return { destroy: () => watch.destroy() };\n };\n}\n","import {\n isDevMode,\n untracked,\n type ResourceStatus,\n type Signal,\n type WritableSignal,\n} from '@angular/core';\nimport { createWatch } from '@angular/core/primitives/signals';\nimport { applyOps, opLog, type OpLog } from '@mmstack/primitives';\nimport {\n generateId,\n PROTO_VERSION,\n serializeError,\n type HasSchema,\n type SignalValueOf,\n type WorkerEnvelope,\n type WorkerPortLike,\n type WorkerSchema,\n} from '@mmstack/worker/protocol';\nimport { microtaskOpLogDriver } from './microtask-driver';\n\n/** A named unit of compute a worker exposes (rung 1). `ctx.signal` aborts when the caller cancels. */\nexport type WorkerTaskHandler<I = any, O = any> = (\n input: I,\n ctx: { readonly signal: AbortSignal },\n) => O | Promise<O>;\n\n/** A published derivation: a plain signal, or a status-bearing one (a `latest()`) for pending propagation. */\nexport type PublishedSource<T> =\n | Signal<T>\n | (Signal<T | undefined> & { readonly status: Signal<ResourceStatus> });\n\nexport type CreateWorkerHostOptions = {\n /**\n * Store subtrees this worker OWNS — the single writer. Keys are the wire identifiers. Typed as\n * the plain writable-signal interface (any copy-on-write `WritableSignal<object>` qualifies, per\n * `opLog`); a concrete `store<T>` satisfies it without the mapped-store variance friction that\n * `WritableSignalStore<any>` would introduce.\n */\n readonly stores?: Record<string, WritableSignal<any>>;\n /**\n * Read-only DERIVED subtrees the worker PUBLISHES (rung 3) — a `Signal<T>` (e.g. a `computed` or\n * `projection`) or a status-bearing async derivation (a `latest()`). Main-thread `workerStore`s\n * mirror them like owned stores but cannot write; a status-bearing entry also propagates its\n * pending/error to the replica, so an in-flight worker computation shows as pending on the main\n * thread.\n */\n readonly published?: Record<string, PublishedSource<any>>;\n /** Named tasks callable from the main thread (rung 1). */\n readonly tasks?: Record<string, WorkerTaskHandler>;\n /**\n * Transport to serve on. Defaults to the worker global scope (`self`) when running in a real\n * Worker, so `my.worker.ts` never touches `self`. Pass an explicit port for tests (a\n * `MessageChannel` port) or a SharedWorker fan-in.\n */\n readonly port?: WorkerPortLike;\n};\n\nexport type WorkerHost<M extends WorkerSchema = WorkerSchema> = HasSchema<M> & {\n /** This host's transport identity — the `origin` of every store batch it emits. */\n readonly hostId: string;\n /** Serve an additional transport (multi-client / tests). Returns a disconnect handle. */\n connect(port: WorkerPortLike): () => void;\n /**\n * Synchronously emit any pending owned-store changes to subscribers NOW, instead of waiting for\n * the microtask driver. Makes the mirror deterministic (tests call it before asserting) and is\n * the honest settle point before applying a routed write (phase 4). No-op when nothing is pending.\n */\n flush(): void;\n /** Stop every owned opLog and drop all connections. */\n dispose(): void;\n};\n\ntype Connection = {\n readonly port: WorkerPortLike;\n clientId: string | null;\n readonly stores: Set<string>;\n /** In-flight named-task runs for THIS client, keyed by its runId (client runIds aren't global). */\n readonly taskRuns: Map<number, AbortController>;\n};\n\ntype Subtree = {\n /** Untracked read of the current root value (for snapshots). */\n readonly read: () => unknown;\n readonly log: OpLog<any>;\n /** The version of the last batch emitted — a fresh subscriber's snapshot carries it. */\n version: number;\n /** The writable store for an OWNED subtree; `null` for a PUBLISHED (read-only) one. */\n readonly writable: WritableSignal<any> | null;\n};\n\n/** Maps an Angular ResourceStatus to the wire status; only the states the protocol carries. */\nfunction toRemoteStatus(\n s: ResourceStatus,\n): 'idle' | 'loading' | 'reloading' | 'resolved' | 'error' {\n return s === 'idle' || s === 'loading' || s === 'reloading' || s === 'error'\n ? s\n : 'resolved';\n}\n\n/**\n * Dev-only guard: a store value that is not structured-clonable (a function, a class instance, an\n * `opaque()` value) throws a bare `DataCloneError` from `postMessage` with no context. Catch it\n * before it hits the wire and name the store, so the fix is obvious. Stripped in production.\n */\nfunction devAssertCloneable(value: unknown, what: string): void {\n if (!isDevMode()) return;\n try {\n structuredClone(value);\n } catch (err) {\n throw new Error(\n `[@mmstack/worker] ${what} holds a value that cannot be sent across the worker boundary. ` +\n `Synced state must be structured-clonable: functions, class instances, and opaque() values ` +\n `do not serialize. Keep it to plain JSON-like data.`,\n { cause: err },\n );\n }\n}\n\nfunction isWorkerGlobal(g: unknown): g is WorkerPortLike {\n const scope = (globalThis as { WorkerGlobalScope?: unknown })\n .WorkerGlobalScope;\n return (\n typeof scope === 'function' &&\n g instanceof (scope as new () => unknown) &&\n typeof (g as { postMessage?: unknown }).postMessage === 'function'\n );\n}\n\n/**\n * The worker-side runtime: owns store subtrees, hosts tasks, and mirrors owned state to every\n * connected client as op batches. Plain functions + signals — NO Angular DI (there is none in a\n * worker); each owned store is observed by a real {@link opLog} driven off the microtask queue.\n *\n * ```ts\n * // my.worker.ts\n * const todos = store<Todo[]>([], workerStoreContext());\n * createWorkerHost({ stores: { todos } }); // serves `self` by default\n * ```\n */\nexport function createWorkerHost<\n S extends Record<string, WritableSignal<any>> = Record<string, never>,\n P extends Record<string, PublishedSource<any>> = Record<string, never>,\n T extends Record<string, WorkerTaskHandler> = Record<string, never>,\n>(options: {\n readonly stores?: S;\n readonly published?: P;\n readonly tasks?: T;\n readonly port?: WorkerPortLike;\n}): WorkerHost<{\n readonly stores: { [K in keyof S]: SignalValueOf<S[K]> };\n readonly published: { [K in keyof P]: SignalValueOf<P[K]> };\n readonly tasks: T;\n}> {\n const hostId = generateId();\n // the S/P/T generics drive the RETURN type only; the body works over loose records\n const sources = (options.stores ?? {}) as Record<string, WritableSignal<any>>;\n const publishedSources = (options.published ?? {}) as Record<\n string,\n PublishedSource<any>\n >;\n const tasks = (options.tasks ?? {}) as Record<string, WorkerTaskHandler>;\n const taskNames = Object.keys(tasks);\n const storeKeys = Object.keys(sources);\n const publishedKeys = Object.keys(publishedSources);\n\n const subtrees = new Map<string, Subtree>();\n const connections = new Set<Connection>();\n // current wire status of each status-bearing published entry, read at subscribe time so a late\n // subscriber sees an in-flight computation as pending instead of waiting for the next transition\n const publishedStatus = new Map<\n string,\n () => 'idle' | 'loading' | 'reloading' | 'resolved' | 'error'\n >();\n const statusWatches: { destroy(): void }[] = [];\n\n const fanout = (store: string, message: WorkerEnvelope) => {\n for (const conn of Array.from(connections))\n if (conn.stores.has(store)) conn.port.postMessage(message);\n };\n\n // observe a signal's value → snapshot/ops. Owned stores also route writes; published are read-only.\n // Applied remote writes ride this same emission, echo-free (the owner is the single sequencer).\n const observe = (\n key: string,\n src: Signal<unknown>,\n writable: WritableSignal<any> | null,\n ): void => {\n const entry: Subtree = {\n read: () => untracked(src),\n log: null as unknown as OpLog<any>,\n version: 0,\n writable,\n };\n (entry as { log: OpLog<any> }).log = opLog(\n // a published (read-only) signal is only ever READ by the log — apply() is never called on it\n src as unknown as WritableSignal<any>,\n { driver: microtaskOpLogDriver(), origin: hostId },\n );\n entry.log.subscribe((batch) => {\n entry.version = batch.version;\n devAssertCloneable(batch, `store '${key}' update`);\n fanout(key, { type: 'store:ops', store: key, batch });\n });\n subtrees.set(key, entry);\n };\n\n for (const key of storeKeys) observe(key, sources[key], sources[key]);\n\n for (const key of publishedKeys) {\n const src = publishedSources[key];\n observe(key, src as Signal<unknown>, null);\n // rung 3: a status-bearing derivation (a latest()) propagates its pending/error to replicas\n const statusSig = (src as { status?: Signal<ResourceStatus> }).status;\n if (statusSig) {\n publishedStatus.set(key, () => toRemoteStatus(untracked(statusSig)));\n let scheduled = false;\n let last: ResourceStatus | null = null;\n const w = createWatch(\n () => {\n const s = statusSig();\n if (s === last) return;\n last = s;\n fanout(key, {\n type: 'store:status',\n store: key,\n status: toRemoteStatus(s),\n });\n },\n (watch) => {\n if (scheduled) return;\n scheduled = true;\n queueMicrotask(() => {\n scheduled = false;\n watch.run();\n });\n },\n false,\n );\n w.notify();\n statusWatches.push(w);\n }\n }\n\n const runTask = (\n conn: Connection,\n runId: number,\n handler: WorkerTaskHandler,\n input: unknown,\n ): void => {\n const ac = new AbortController();\n conn.taskRuns.set(runId, ac);\n Promise.resolve()\n .then(() => handler(input, { signal: ac.signal }))\n .then(\n (value) => {\n if (ac.signal.aborted)\n conn.port.postMessage({ type: 'task:aborted', runId });\n else conn.port.postMessage({ type: 'task:ok', runId, value });\n },\n (err) => {\n if (ac.signal.aborted)\n conn.port.postMessage({ type: 'task:aborted', runId });\n else\n conn.port.postMessage({\n type: 'task:error',\n runId,\n error: serializeError(err),\n });\n },\n )\n .finally(() => {\n if (conn.taskRuns.get(runId) === ac) conn.taskRuns.delete(runId);\n });\n };\n\n const handle = (conn: Connection, msg: WorkerEnvelope): void => {\n switch (msg.type) {\n case 'hello': {\n conn.clientId = msg.clientId;\n conn.port.postMessage({\n type: 'ready',\n proto: PROTO_VERSION,\n hostId,\n stores: storeKeys,\n published: publishedKeys,\n tasks: taskNames,\n });\n return;\n }\n case 'store:subscribe': {\n const sub = subtrees.get(msg.store);\n if (!sub) return; // unknown store — silently ignore (a client typo, not fatal)\n // synchronous: read snapshot + version and register the client in one turn — no batch can\n // interleave in a single-threaded worker, so every later op carries version > this one\n conn.stores.add(msg.store);\n const snapshot = sub.read();\n devAssertCloneable(snapshot, `store '${msg.store}' snapshot`);\n conn.port.postMessage({\n type: 'store:snapshot',\n store: msg.store,\n version: sub.version,\n value: snapshot,\n });\n // a status-bearing published entry also reports its CURRENT status, so a subscriber\n // arriving mid-computation shows pending now, not on the next transition\n const currentStatus = publishedStatus.get(msg.store);\n if (currentStatus)\n conn.port.postMessage({\n type: 'store:status',\n store: msg.store,\n status: currentStatus(),\n });\n return;\n }\n case 'store:unsubscribe': {\n conn.stores.delete(msg.store);\n return;\n }\n case 'task:run': {\n const handler = tasks[msg.task];\n if (!handler) {\n conn.port.postMessage({\n type: 'task:error',\n runId: msg.runId,\n error: serializeError(\n new Error(`[@mmstack/worker] unknown task: ${msg.task}`),\n ),\n });\n return;\n }\n runTask(conn, msg.runId, handler, msg.input);\n return;\n }\n case 'task:abort': {\n conn.taskRuns.get(msg.runId)?.abort();\n conn.taskRuns.delete(msg.runId);\n return;\n }\n case 'store:write': {\n const sub = subtrees.get(msg.store);\n if (!sub || !sub.writable) {\n conn.port.postMessage({\n type: 'store:write:error',\n store: msg.store,\n writeId: msg.writeId,\n error: serializeError(\n new Error(\n sub\n ? `[@mmstack/worker] store is read-only (published): ${msg.store}`\n : `[@mmstack/worker] unknown store: ${msg.store}`,\n ),\n ),\n });\n return;\n }\n try {\n // the owner is the single sequencer: apply the routed ops through the store root, then\n // FLUSH so its opLog emits ONE authoritative owner-origin batch (fanned to every client,\n // the writer included — the echo that IS the write's confirmation). No baseline-advance\n // trick: this is a genuine owner write, indistinguishable from the worker writing itself.\n sub.writable.set(applyOps(sub.read(), msg.ops));\n sub.log.flush();\n conn.port.postMessage({\n type: 'store:write:ack',\n store: msg.store,\n writeId: msg.writeId,\n version: sub.version,\n });\n } catch (err) {\n conn.port.postMessage({\n type: 'store:write:error',\n store: msg.store,\n writeId: msg.writeId,\n error: serializeError(err),\n });\n }\n return;\n }\n }\n };\n\n const connect = (port: WorkerPortLike): (() => void) => {\n const conn: Connection = {\n port,\n clientId: null,\n stores: new Set(),\n taskRuns: new Map(),\n };\n connections.add(conn);\n // assigning onmessage starts a MessagePort; a Worker/self delivers the same way\n port.onmessage = (ev) => handle(conn, ev.data as WorkerEnvelope);\n return () => {\n connections.delete(conn);\n port.onmessage = null; // a disconnected client must not keep invoking tasks/writes\n for (const ac of conn.taskRuns.values()) ac.abort();\n conn.taskRuns.clear();\n };\n };\n\n const defaultPort: WorkerPortLike | undefined =\n options.port ??\n (isWorkerGlobal(globalThis)\n ? (globalThis as unknown as WorkerPortLike)\n : undefined);\n if (defaultPort) connect(defaultPort);\n\n return {\n hostId,\n connect,\n flush() {\n for (const { log } of subtrees.values()) log.flush();\n },\n dispose() {\n for (const { log } of subtrees.values()) log.destroy();\n for (const w of statusWatches) w.destroy();\n connections.clear();\n },\n };\n}\n","import { createStoreContext, type toStoreOptions } from '@mmstack/primitives';\n\n// Memoized at THIS module's scope. `@mmstack/worker/host` only ever loads inside a worker, so a\n// module-scope singleton here is a per-thread singleton — the worker equivalent of an app's root\n// injector (providedIn: 'root'). All stores in the worker share it; it is the graph's single\n// proxy-identity + GC-coordination point.\nlet context: toStoreOptions | undefined;\n\n/**\n * The one shared store context for this worker — pass it to every `store`/`toStore` you create in\n * the worker so they share proxy identity and cleanup, exactly as `providedIn: 'root'` shares one\n * cache across an app's stores.\n *\n * ```ts\n * // my.worker.ts\n * const todos = store<Todo[]>([], workerStoreContext());\n * const filter = store({ q: '' }, workerStoreContext()); // same context\n * ```\n */\nexport function workerStoreContext(): toStoreOptions {\n return (context ??= createStoreContext());\n}\n","// shared wire types, re-exported so worker code imports only from '@mmstack/worker/host'\nexport * from '@mmstack/worker/protocol';\n\nexport {\n createWorkerHost,\n type CreateWorkerHostOptions,\n type WorkerHost,\n type WorkerTaskHandler,\n} from './lib/create-worker-host';\nexport { microtaskOpLogDriver } from './lib/microtask-driver';\nexport { workerStoreContext } from './lib/worker-store-context';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mmstack-worker-host.mjs","sources":["../../../../packages/worker/host/src/lib/microtask-driver.ts","../../../../packages/worker/host/src/lib/create-worker-host.ts","../../../../packages/worker/host/src/lib/worker-store-context.ts","../../../../packages/worker/host/src/mmstack-worker-host.ts"],"sourcesContent":["import { createWatch } from '@angular/core/primitives/signals';\nimport type { OpLogDriver } from '@mmstack/primitives';\n\n/**\n * An injector-free {@link OpLogDriver} for `@mmstack/primitives` `opLog`. Schedules the emission\n * reaction on the microtask queue via `createWatch` (Angular's renderer-independent watch\n * primitive) instead of an `effect`, so an opLog can run where there is no Angular injector: the\n * worker side of the worker-graph, whose store has no application tick driving it.\n *\n * This lives in `@mmstack/worker/host` rather than in primitives on purpose. `createWatch` is a\n * lower-tier signals-primitives export; keeping it out of `@mmstack/primitives` lets that package\n * back-port to older Angular majors that may not export it. The worker package tracks a newer\n * Angular where `createWatch` is available.\n *\n * Batching is per-microtask rather than per-app-tick; the worker protocol orders by\n * `(origin, version)`, never tick alignment, so that is safe.\n */\nexport function microtaskOpLogDriver(): OpLogDriver {\n return (run) => {\n let scheduled = false;\n const watch = createWatch(\n () => run(),\n (w) => {\n if (scheduled) return;\n scheduled = true;\n queueMicrotask(() => {\n scheduled = false;\n w.run();\n });\n },\n false,\n );\n watch.notify();\n return { destroy: () => watch.destroy() };\n };\n}\n","import {\n isDevMode,\n untracked,\n type ResourceStatus,\n type Signal,\n type WritableSignal,\n} from '@angular/core';\nimport { createWatch } from '@angular/core/primitives/signals';\nimport { createHlcClock, opSync, type OpSync } from '@mmstack/primitives';\nimport {\n generateId,\n PROTO_VERSION,\n serializeError,\n type HasSchema,\n type SignalValueOf,\n type WorkerEnvelope,\n type WorkerPortLike,\n type WorkerSchema,\n} from '@mmstack/worker/protocol';\nimport { microtaskOpLogDriver } from './microtask-driver';\n\n/** A named unit of compute a worker exposes (rung 1). `ctx.signal` aborts when the caller cancels. */\nexport type WorkerTaskHandler<I = any, O = any> = (\n input: I,\n ctx: { readonly signal: AbortSignal },\n) => O | Promise<O>;\n\n/** A published derivation: a plain signal, or a status-bearing one (a `latest()`) for pending propagation. */\nexport type PublishedSource<T> =\n | Signal<T>\n | (Signal<T | undefined> & { readonly status: Signal<ResourceStatus> });\n\nexport type CreateWorkerHostOptions = {\n /**\n * Store subtrees this worker OWNS — the single writer. Keys are the wire identifiers. Typed as\n * the plain writable-signal interface (any copy-on-write `WritableSignal<object>` qualifies, per\n * `opLog`); a concrete `store<T>` satisfies it without the mapped-store variance friction that\n * `WritableSignalStore<any>` would introduce.\n */\n readonly stores?: Record<string, WritableSignal<any>>;\n /**\n * Read-only DERIVED subtrees the worker PUBLISHES (rung 3) — a `Signal<T>` (e.g. a `computed` or\n * `projection`) or a status-bearing async derivation (a `latest()`). Main-thread `workerStore`s\n * mirror them like owned stores but cannot write; a status-bearing entry also propagates its\n * pending/error to the replica, so an in-flight worker computation shows as pending on the main\n * thread.\n */\n readonly published?: Record<string, PublishedSource<any>>;\n /** Named tasks callable from the main thread (rung 1). */\n readonly tasks?: Record<string, WorkerTaskHandler>;\n /**\n * Transport to serve on. Defaults to the worker global scope (`self`) when running in a real\n * Worker, so `my.worker.ts` never touches `self`. Pass an explicit port for tests (a\n * `MessageChannel` port) or a SharedWorker fan-in.\n */\n readonly port?: WorkerPortLike;\n};\n\nexport type WorkerHost<M extends WorkerSchema = WorkerSchema> = HasSchema<M> & {\n /** This host's transport identity — the `origin` of every store batch it emits. */\n readonly hostId: string;\n /** Serve an additional transport (multi-client / tests). Returns a disconnect handle. */\n connect(port: WorkerPortLike): () => void;\n /**\n * Apply an AUTHORITATIVE owner correction to an owned store: writes made inside `fn` are stamped\n * at a bumped epoch, so they deterministically win the merge against any concurrent replica write\n * (owner authority rides the epoch fold). Readers can gate effects on owner-settled values by the\n * winning op's epoch. No-op for a read-only (published) or unknown store.\n */\n override(store: string, fn: () => void): void;\n /**\n * Synchronously emit any pending owned-store changes to subscribers NOW, instead of waiting for\n * the microtask driver. Makes the mirror deterministic (tests call it before asserting) and is\n * the honest settle point before applying a routed write (phase 4). No-op when nothing is pending.\n */\n flush(): void;\n /** Stop every owned opLog and drop all connections. */\n dispose(): void;\n};\n\ntype Connection = {\n readonly port: WorkerPortLike;\n clientId: string | null;\n readonly stores: Set<string>;\n readonly taskRuns: Map<number, AbortController>;\n};\n\ntype Subtree = {\n readonly read: () => unknown;\n readonly sync: OpSync<any>;\n readonly writable: boolean;\n};\n\nfunction toRemoteStatus(\n s: ResourceStatus,\n): 'idle' | 'loading' | 'reloading' | 'resolved' | 'error' {\n return s === 'idle' || s === 'loading' || s === 'reloading' || s === 'error'\n ? s\n : 'resolved';\n}\n\n// dev-only: catch a non-cloneable store value before postMessage throws a context-free DataCloneError\nfunction devAssertCloneable(value: unknown, what: string): void {\n if (!isDevMode()) return;\n try {\n structuredClone(value);\n } catch (err) {\n throw new Error(\n `[@mmstack/worker] ${what} holds a value that cannot be sent across the worker boundary. ` +\n `Synced state must be structured-clonable: functions, class instances, and opaque() values ` +\n `do not serialize. Keep it to plain JSON-like data.`,\n { cause: err },\n );\n }\n}\n\nfunction isWorkerGlobal(g: unknown): g is WorkerPortLike {\n const scope = (globalThis as { WorkerGlobalScope?: unknown })\n .WorkerGlobalScope;\n return (\n typeof scope === 'function' &&\n g instanceof (scope as new () => unknown) &&\n typeof (g as { postMessage?: unknown }).postMessage === 'function'\n );\n}\n\n/**\n * The worker-side runtime: owns store subtrees, hosts tasks, and mirrors owned state to every\n * connected client as op batches. Plain functions + signals — NO Angular DI (there is none in a\n * worker); each owned store is observed by a real {@link opLog} driven off the microtask queue.\n *\n * ```ts\n * // my.worker.ts\n * const todos = store<Todo[]>([], workerStoreContext());\n * createWorkerHost({ stores: { todos } }); // serves `self` by default\n * ```\n */\nexport function createWorkerHost<\n S extends Record<string, WritableSignal<any>> = Record<string, never>,\n P extends Record<string, PublishedSource<any>> = Record<string, never>,\n T extends Record<string, WorkerTaskHandler> = Record<string, never>,\n>(options: {\n readonly stores?: S;\n readonly published?: P;\n readonly tasks?: T;\n readonly port?: WorkerPortLike;\n}): WorkerHost<{\n readonly stores: { [K in keyof S]: SignalValueOf<S[K]> };\n readonly published: { [K in keyof P]: SignalValueOf<P[K]> };\n readonly tasks: T;\n}> {\n const hostId = generateId();\n const sources = (options.stores ?? {}) as Record<string, WritableSignal<any>>;\n const publishedSources = (options.published ?? {}) as Record<\n string,\n PublishedSource<any>\n >;\n const tasks = (options.tasks ?? {}) as Record<string, WorkerTaskHandler>;\n const taskNames = Object.keys(tasks);\n const storeKeys = Object.keys(sources);\n const publishedKeys = Object.keys(publishedSources);\n\n const subtrees = new Map<string, Subtree>();\n const connections = new Set<Connection>();\n const publishedStatus = new Map<\n string,\n () => 'idle' | 'loading' | 'reloading' | 'resolved' | 'error'\n >();\n const statusWatches: { destroy(): void }[] = [];\n\n const fanout = (store: string, message: WorkerEnvelope) => {\n for (const conn of Array.from(connections))\n if (conn.stores.has(store)) conn.port.postMessage(message);\n };\n\n const observe = (\n key: string,\n src: Signal<unknown>,\n writable: boolean,\n ): void => {\n // Each subtree runs the owner's opSync (a real origin + HLC): its batches are real envelopes,\n // so the whole main<->worker sync is opSync over the MessageChannel transport, no bespoke\n // sequencer. Owner writes emit host-origin envelopes; received client writes fold in and are\n // relayed on. Emission is driven off the microtask queue (no injector inside a worker).\n const sync = opSync(src as unknown as WritableSignal<any>, {\n writer: hostId,\n origin: `${hostId}:${key}`,\n driver: microtaskOpLogDriver(),\n clock: createHlcClock(),\n });\n sync.subscribe((env) => {\n devAssertCloneable(env, `store '${key}' update`);\n fanout(key, { type: 'store:sync', store: key, env });\n });\n subtrees.set(key, { read: () => untracked(src), sync, writable });\n };\n\n for (const key of storeKeys) observe(key, sources[key], true);\n\n for (const key of publishedKeys) {\n const src = publishedSources[key];\n observe(key, src as Signal<unknown>, false);\n const statusSig = (src as { status?: Signal<ResourceStatus> }).status;\n if (statusSig) {\n publishedStatus.set(key, () => toRemoteStatus(untracked(statusSig)));\n let scheduled = false;\n let last: ResourceStatus | null = null;\n const w = createWatch(\n () => {\n const s = statusSig();\n if (s === last) return;\n last = s;\n fanout(key, {\n type: 'store:status',\n store: key,\n status: toRemoteStatus(s),\n });\n },\n (watch) => {\n if (scheduled) return;\n scheduled = true;\n queueMicrotask(() => {\n scheduled = false;\n watch.run();\n });\n },\n false,\n );\n w.notify();\n statusWatches.push(w);\n }\n }\n\n const runTask = (\n conn: Connection,\n runId: number,\n handler: WorkerTaskHandler,\n input: unknown,\n ): void => {\n const ac = new AbortController();\n conn.taskRuns.set(runId, ac);\n Promise.resolve()\n .then(() => handler(input, { signal: ac.signal }))\n .then(\n (value) => {\n if (ac.signal.aborted)\n conn.port.postMessage({ type: 'task:aborted', runId });\n else conn.port.postMessage({ type: 'task:ok', runId, value });\n },\n (err) => {\n if (ac.signal.aborted)\n conn.port.postMessage({ type: 'task:aborted', runId });\n else\n conn.port.postMessage({\n type: 'task:error',\n runId,\n error: serializeError(err),\n });\n },\n )\n .finally(() => {\n if (conn.taskRuns.get(runId) === ac) conn.taskRuns.delete(runId);\n });\n };\n\n const handle = (conn: Connection, msg: WorkerEnvelope): void => {\n switch (msg.type) {\n case 'hello': {\n conn.clientId = msg.clientId;\n conn.port.postMessage({\n type: 'ready',\n proto: PROTO_VERSION,\n hostId,\n stores: storeKeys,\n published: publishedKeys,\n tasks: taskNames,\n });\n return;\n }\n case 'store:subscribe': {\n const sub = subtrees.get(msg.store);\n if (!sub) return;\n conn.stores.add(msg.store);\n sub.sync.flush(); // settle any pending owner change into the register state first\n const checkpoint = sub.sync.snapshot();\n devAssertCloneable(checkpoint, `store '${msg.store}' checkpoint`);\n conn.port.postMessage({\n type: 'store:checkpoint',\n store: msg.store,\n checkpoint,\n });\n const currentStatus = publishedStatus.get(msg.store);\n if (currentStatus)\n conn.port.postMessage({\n type: 'store:status',\n store: msg.store,\n status: currentStatus(),\n });\n return;\n }\n case 'store:unsubscribe': {\n conn.stores.delete(msg.store);\n return;\n }\n case 'task:run': {\n const handler = tasks[msg.task];\n if (!handler) {\n conn.port.postMessage({\n type: 'task:error',\n runId: msg.runId,\n error: serializeError(\n new Error(`[@mmstack/worker] unknown task: ${msg.task}`),\n ),\n });\n return;\n }\n runTask(conn, msg.runId, handler, msg.input);\n return;\n }\n case 'task:abort': {\n conn.taskRuns.get(msg.runId)?.abort();\n conn.taskRuns.delete(msg.runId);\n return;\n }\n case 'store:sync': {\n // a routed client write: fold it into the owner store, then RELAY it to every subscriber\n // (including the sender, whose replica reads its own echo as the write acknowledgement).\n // Writes to a read-only (published) or unknown store are ignored: those never route.\n const sub = subtrees.get(msg.store);\n if (!sub || !sub.writable) return;\n sub.sync.receive(msg.env);\n fanout(msg.store, { type: 'store:sync', store: msg.store, env: msg.env });\n return;\n }\n }\n };\n\n const connect = (port: WorkerPortLike): (() => void) => {\n const conn: Connection = {\n port,\n clientId: null,\n stores: new Set(),\n taskRuns: new Map(),\n };\n connections.add(conn);\n port.onmessage = (ev) => handle(conn, ev.data as WorkerEnvelope);\n return () => {\n connections.delete(conn);\n port.onmessage = null;\n for (const ac of conn.taskRuns.values()) ac.abort();\n conn.taskRuns.clear();\n };\n };\n\n const defaultPort: WorkerPortLike | undefined =\n options.port ??\n (isWorkerGlobal(globalThis)\n ? (globalThis as unknown as WorkerPortLike)\n : undefined);\n if (defaultPort) connect(defaultPort);\n\n return {\n hostId,\n connect,\n override(store, fn) {\n const sub = subtrees.get(store);\n if (sub?.writable) sub.sync.override(fn);\n },\n flush() {\n for (const { sync } of subtrees.values()) sync.flush();\n },\n dispose() {\n for (const { sync } of subtrees.values()) sync.destroy();\n for (const w of statusWatches) w.destroy();\n connections.clear();\n },\n };\n}\n","import { createStoreContext, type toStoreOptions } from '@mmstack/primitives';\n\nlet context: toStoreOptions | undefined;\n\n/**\n * The one shared store context for this worker — pass it to every `store`/`toStore` you create in\n * the worker so they share proxy identity and cleanup, exactly as `providedIn: 'root'` shares one\n * cache across an app's stores.\n *\n * ```ts\n * // my.worker.ts\n * const todos = store<Todo[]>([], workerStoreContext());\n * const filter = store({ q: '' }, workerStoreContext()); // same context\n * ```\n */\nexport function workerStoreContext(): toStoreOptions {\n return (context ??= createStoreContext());\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -46,7 +46,6 @@ function closePort(port) {
|
|
|
46
46
|
port.terminate?.();
|
|
47
47
|
port.close?.();
|
|
48
48
|
}
|
|
49
|
-
// values whose reachable Transferables should be MOVED (detached at the sender) rather than cloned
|
|
50
49
|
const TRANSFERABLES = new WeakMap();
|
|
51
50
|
/**
|
|
52
51
|
* Marks `value` so that, when it is sent over a {@link WorkerPortLike}, the listed `Transferable`s
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mmstack-worker-protocol.mjs","sources":["../../../../packages/worker/protocol/src/lib/envelope.ts","../../../../packages/worker/protocol/src/lib/error.ts","../../../../packages/worker/protocol/src/lib/id.ts","../../../../packages/worker/protocol/src/lib/port.ts","../../../../packages/worker/protocol/src/mmstack-worker-protocol.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"mmstack-worker-protocol.mjs","sources":["../../../../packages/worker/protocol/src/lib/envelope.ts","../../../../packages/worker/protocol/src/lib/error.ts","../../../../packages/worker/protocol/src/lib/id.ts","../../../../packages/worker/protocol/src/lib/port.ts","../../../../packages/worker/protocol/src/mmstack-worker-protocol.ts"],"sourcesContent":["import type { OpEnvelope, OpSyncCheckpoint } from '@mmstack/primitives';\nimport type { SerializedError } from './error';\n\n/** Wire-protocol version, negotiated in the hello/ready handshake. Bump on breaking envelope change. */\nexport const PROTO_VERSION = 1;\nexport type ProtoVersion = typeof PROTO_VERSION;\n\n/** The status a remote (worker-owned) computation reports across the boundary — the rung-3 seam. */\nexport type RemoteStatus =\n | 'idle'\n | 'loading'\n | 'reloading'\n | 'resolved'\n | 'error';\n\n/**\n * Every message exchanged over a {@link WorkerPortLike}. One discriminated union shared by the\n * main-thread client and the worker host (and, later, `@mmstack/mesh`), so the two sides can never\n * drift.\n */\nexport type WorkerEnvelope =\n | { type: 'hello'; proto: ProtoVersion; clientId: string }\n | {\n type: 'ready';\n proto: ProtoVersion;\n hostId: string;\n stores: readonly string[];\n published: readonly string[];\n tasks: readonly string[];\n }\n | { type: 'fatal'; error: SerializedError }\n | { type: 'task:run'; runId: number; task: string; input: unknown }\n | { type: 'task:abort'; runId: number }\n | { type: 'task:ok'; runId: number; value: unknown }\n | { type: 'task:error'; runId: number; error: SerializedError }\n | { type: 'task:aborted'; runId: number }\n | { type: 'store:subscribe'; store: string; clientId: string }\n // The owner ships its opSync checkpoint (root + register state + watermark) on subscribe/resync,\n // so a joining replica hydrates through its own fold rather than adopting a bare value.\n | { type: 'store:checkpoint'; store: string; checkpoint: OpSyncCheckpoint }\n // A single op envelope, both directions: owner-emitted ops flow host -> clients, and a client's\n // routed write flows client -> host. The host relays each applied envelope to every subscriber\n // (including the sender, whose replica reads its own echo as the acknowledgement).\n | { type: 'store:sync'; store: string; env: OpEnvelope }\n | { type: 'store:unsubscribe'; store: string; clientId: string }\n | { type: 'store:status'; store: string; status: RemoteStatus; error?: SerializedError };\n\n/** Narrows a raw message to a specific envelope variant by its `type`. */\nexport type EnvelopeOf<K extends WorkerEnvelope['type']> = Extract<\n WorkerEnvelope,\n { type: K }\n>;\n","/**\n * A structured-clone-safe rendering of an `Error` for cross-thread propagation. A worker throw\n * can't cross `postMessage` as an `Error` (methods/prototype are lost), so tasks and store writes\n * serialize failures into this shape and the receiving side rebuilds a real `Error`.\n */\nexport type SerializedError = {\n name: string;\n message: string;\n stack?: string;\n cause?: SerializedError;\n};\n\nfunction stringify(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/** Renders any thrown value (Error or otherwise) into a {@link SerializedError}, chaining `cause`. */\nexport function serializeError(err: unknown): SerializedError {\n if (err instanceof Error) {\n return {\n name: err.name,\n message: err.message,\n stack: err.stack,\n cause: err.cause !== undefined ? serializeError(err.cause) : undefined,\n };\n }\n return { name: 'Error', message: stringify(err) };\n}\n\n/** Rebuilds a real `Error` (name/message/stack/cause chain preserved) from a {@link SerializedError}. */\nexport function deserializeError(e: SerializedError): Error {\n const err = new Error(e.message);\n err.name = e.name;\n if (e.stack !== undefined) err.stack = e.stack;\n if (e.cause !== undefined) err.cause = deserializeError(e.cause);\n return err;\n}\n","/** A short unique id for host/client identity on a shared transport. */\nexport function generateId(): string {\n if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID();\n return Math.random().toString(36).slice(2);\n}\n","/**\n * The minimal structural transport the worker protocol rides on — the intersection of a `Worker`,\n * a `MessagePort`, and a `BroadcastChannel`. Modeled on `@mmstack/resource`'s `MutationSyncChannel`:\n * anything with `postMessage` + assignable `onmessage` qualifies, so a `MessageChannel` port-pair\n * works in tests with zero adapters and a future `@mmstack/mesh` can slot a WS/RTC channel behind\n * the same type.\n */\nexport type WorkerPortLike = {\n postMessage(message: unknown, transfer?: Transferable[]): void;\n // `any` ev: keeps a real Worker/MessagePort assignable under strictFunctionTypes\n onmessage: ((ev: any) => void) | null;\n /** `Worker` has `terminate()`, `MessagePort`/`BroadcastChannel` have `close()` — either, both optional. */\n terminate?(): void;\n close?(): void;\n};\n\n/** Closes a port by whichever teardown method it exposes. */\nexport function closePort(port: WorkerPortLike): void {\n port.terminate?.();\n port.close?.();\n}\n\nconst TRANSFERABLES = new WeakMap<object, Transferable[]>();\n\n/**\n * Marks `value` so that, when it is sent over a {@link WorkerPortLike}, the listed `Transferable`s\n * (e.g. an `ArrayBuffer`) are moved rather than structure-cloned — zero-copy, but detached at the\n * sender. Comlink-idiom. v1 honors this on task input/output only; store traffic always clones (a\n * moved buffer inside a replicated tree would detach the owner's copy).\n *\n * ```ts\n * const buf = new Float64Array(1e6);\n * workerResource(() => transfer({ buf }, [buf.buffer]), { worker, task: 'process' });\n * ```\n */\nexport function transfer<T extends object>(value: T, transferables: Transferable[]): T {\n TRANSFERABLES.set(value, transferables);\n return value;\n}\n\n/** @internal Reads back the Transferables registered on a value via {@link transfer}, if any. */\nexport function takeTransferables(value: unknown): Transferable[] | undefined {\n if (value !== null && typeof value === 'object')\n return TRANSFERABLES.get(value as object);\n return undefined;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"AAGA;AACO,MAAM,aAAa,GAAG;;ACQ7B,SAAS,SAAS,CAAC,KAAc,EAAA;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ;AAAE,QAAA,OAAO,KAAK;AAC3C,IAAA,IAAI;QACF,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC;IAC/C;AAAE,IAAA,MAAM;AACN,QAAA,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB;AACF;AAEA;AACM,SAAU,cAAc,CAAC,GAAY,EAAA;AACzC,IAAA,IAAI,GAAG,YAAY,KAAK,EAAE;QACxB,OAAO;YACL,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,KAAK,EAAE,GAAG,CAAC,KAAK;AAChB,YAAA,KAAK,EAAE,GAAG,CAAC,KAAK,KAAK,SAAS,GAAG,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,SAAS;SACvE;IACH;AACA,IAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE;AACnD;AAEA;AACM,SAAU,gBAAgB,CAAC,CAAkB,EAAA;IACjD,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;AAChC,IAAA,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI;AACjB,IAAA,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;AAAE,QAAA,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK;AAC9C,IAAA,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;QAAE,GAAG,CAAC,KAAK,GAAG,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC;AAChE,IAAA,OAAO,GAAG;AACZ;;ACzCA;SACgB,UAAU,GAAA;AACxB,IAAA,IAAI,UAAU,CAAC,MAAM,EAAE,UAAU;AAAE,QAAA,OAAO,UAAU,CAAC,MAAM,CAAC,UAAU,EAAE;AACxE,IAAA,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AAC5C;;ACYA;AACM,SAAU,SAAS,CAAC,IAAoB,EAAA;AAC5C,IAAA,IAAI,CAAC,SAAS,IAAI;AAClB,IAAA,IAAI,CAAC,KAAK,IAAI;AAChB;AAEA,MAAM,aAAa,GAAG,IAAI,OAAO,EAA0B;AAE3D;;;;;;;;;;AAUG;AACG,SAAU,QAAQ,CAAmB,KAAQ,EAAE,aAA6B,EAAA;AAChF,IAAA,aAAa,CAAC,GAAG,CAAC,KAAK,EAAE,aAAa,CAAC;AACvC,IAAA,OAAO,KAAK;AACd;AAEA;AACM,SAAU,iBAAiB,CAAC,KAAc,EAAA;AAC9C,IAAA,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;AAC7C,QAAA,OAAO,aAAa,CAAC,GAAG,CAAC,KAAe,CAAC;AAC3C,IAAA,OAAO,SAAS;AAClB;;AC7CA;;AAEG;;;;"}
|