@mmstack/worker 21.0.0 → 21.1.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 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
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](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 read-only replicas and reads them like any signal store, so heavy work makes the UI pending, not frozen.
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
- - **Single writer, provable convergence.** Each store subtree has exactly one owner (the worker). Writes from the main thread route to that owner, which sequences them and fans the authoritative result back to every replica. No optimistic divergence, no CRDTs. Interleaved writes converge to identical state by construction.
14
- - **Honest async.** A replica read is synchronous (the value is always held). Only writes and settles are async, and they surface as `pending` through transition scopes rather than promises you await in the template.
15
+ - **Single sequencer, provable convergence.** Each store subtree has exactly one owner (the worker). Writes apply optimistically on the main thread and route to that owner, which sequences them and fans the authoritative result back to every replica; because every op is idempotent, replicas reconcile without divergence. Owner-authoritative, no CRDTs. Interleaved writes converge to identical state.
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, read-only replica of an owned store, plus `write()` to route a change to the owner,
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
 
@@ -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(); // no-op if the watch is not dirty
30
+ w.run();
32
31
  });
33
32
  }, false);
34
- watch.notify(); // bootstrap: schedule the first run to establish the source dependency
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,8 +88,6 @@ 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
92
  const entry = {
104
93
  read: () => untracked(src),
@@ -106,9 +95,7 @@ function createWorkerHost(options) {
106
95
  version: 0,
107
96
  writable,
108
97
  };
109
- entry.log = opLog(
110
- // a published (read-only) signal is only ever READ by the log — apply() is never called on it
111
- src, { driver: microtaskOpLogDriver(), origin: hostId });
98
+ entry.log = opLog(src, { driver: microtaskOpLogDriver(), origin: hostId });
112
99
  entry.log.subscribe((batch) => {
113
100
  entry.version = batch.version;
114
101
  devAssertCloneable(batch, `store '${key}' update`);
@@ -121,7 +108,6 @@ function createWorkerHost(options) {
121
108
  for (const key of publishedKeys) {
122
109
  const src = publishedSources[key];
123
110
  observe(key, src, null);
124
- // rung 3: a status-bearing derivation (a latest()) propagates its pending/error to replicas
125
111
  const statusSig = src.status;
126
112
  if (statusSig) {
127
113
  publishedStatus.set(key, () => toRemoteStatus(untracked(statusSig)));
@@ -192,9 +178,7 @@ function createWorkerHost(options) {
192
178
  case 'store:subscribe': {
193
179
  const sub = subtrees.get(msg.store);
194
180
  if (!sub)
195
- return; // unknown store — silently ignore (a client typo, not fatal)
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
181
+ return;
198
182
  conn.stores.add(msg.store);
199
183
  const snapshot = sub.read();
200
184
  devAssertCloneable(snapshot, `store '${msg.store}' snapshot`);
@@ -204,8 +188,6 @@ function createWorkerHost(options) {
204
188
  version: sub.version,
205
189
  value: snapshot,
206
190
  });
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
191
  const currentStatus = publishedStatus.get(msg.store);
210
192
  if (currentStatus)
211
193
  conn.port.postMessage({
@@ -251,10 +233,6 @@ function createWorkerHost(options) {
251
233
  return;
252
234
  }
253
235
  try {
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
236
  sub.writable.set(applyOps(sub.read(), msg.ops));
259
237
  sub.log.flush();
260
238
  conn.port.postMessage({
@@ -284,11 +262,10 @@ function createWorkerHost(options) {
284
262
  taskRuns: new Map(),
285
263
  };
286
264
  connections.add(conn);
287
- // assigning onmessage starts a MessagePort; a Worker/self delivers the same way
288
265
  port.onmessage = (ev) => handle(conn, ev.data);
289
266
  return () => {
290
267
  connections.delete(conn);
291
- port.onmessage = null; // a disconnected client must not keep invoking tasks/writes
268
+ port.onmessage = null;
292
269
  for (const ac of conn.taskRuns.values())
293
270
  ac.abort();
294
271
  conn.taskRuns.clear();
@@ -317,10 +294,6 @@ function createWorkerHost(options) {
317
294
  };
318
295
  }
319
296
 
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
297
  let context;
325
298
  /**
326
299
  * The one shared store context for this worker — pass it to every `store`/`toStore` you create in
@@ -337,8 +310,6 @@ function workerStoreContext() {
337
310
  return (context ??= createStoreContext());
338
311
  }
339
312
 
340
- // shared wire types, re-exported so worker code imports only from '@mmstack/worker/host'
341
-
342
313
  /**
343
314
  * Generated bundle index. Do not edit.
344
315
  */
@@ -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 { 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 readonly taskRuns: Map<number, AbortController>;\n};\n\ntype Subtree = {\n readonly read: () => unknown;\n readonly log: OpLog<any>;\n version: number;\n readonly writable: WritableSignal<any> | null;\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: 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 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 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 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 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 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 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 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\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 { OpBatch, StoreOp } 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. Grouped: lifecycle · tasks (rung 1) · stores (rung 2) · remote status (rung 3).\n */\nexport type WorkerEnvelope =\n // ── lifecycle ────────────────────────────────────────────────────────────\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 // ── rung 1: tasks ────────────────────────────────────────────────────────\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 // terminal ack for an aborted run — a non-cooperative handler settles eventually, and this\n // (rather than task:ok/task:error) is what its late settlement reports back as\n | { type: 'task:aborted'; runId: number }\n // ── rung 2: stores ───────────────────────────────────────────────────────\n | { type: 'store:subscribe'; store: string; clientId: string }\n | { type: 'store:snapshot'; store: string; version: number; value: unknown }\n | { type: 'store:ops'; store: string; batch: OpBatch }\n | {\n type: 'store:write';\n store: string;\n writeId: number;\n clientId: string;\n ops: readonly StoreOp[];\n }\n | { type: 'store:write:ack'; store: string; writeId: number; version: number }\n | { type: 'store:write:error'; store: string; writeId: number; error: SerializedError }\n | { type: 'store:unsubscribe'; store: string; clientId: string }\n // ── rung 3: remote status ────────────────────────────────────────────────\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 // `ev` is intentionally `any`: a real `Worker`/`MessagePort` types this as `(ev: MessageEvent)`,\n // and under `strictFunctionTypes` only an `any`-typed event param keeps those assignable to this\n // shape cast-free (a `{ data }` param fails the contravariance check). Read `ev.data` and narrow\n // it to a `WorkerEnvelope` at the handler.\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\n// values whose reachable Transferables should be MOVED (detached at the sender) rather than cloned\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;;ACeA;AACM,SAAU,SAAS,CAAC,IAAoB,EAAA;AAC5C,IAAA,IAAI,CAAC,SAAS,IAAI;AAClB,IAAA,IAAI,CAAC,KAAK,IAAI;AAChB;AAEA;AACA,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;;ACjDA;;AAEG;;;;"}
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 { OpBatch, StoreOp } 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 | { type: 'store:snapshot'; store: string; version: number; value: unknown }\n | { type: 'store:ops'; store: string; batch: OpBatch }\n | {\n type: 'store:write';\n store: string;\n writeId: number;\n clientId: string;\n ops: readonly StoreOp[];\n }\n | { type: 'store:write:ack'; store: string; writeId: number; version: number }\n | { type: 'store:write:error'; store: string; writeId: number; error: SerializedError }\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;;;;"}
@@ -1,7 +1,7 @@
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, applyOps, diffOps } from '@mmstack/primitives';
4
+ import { injectTransitionScope, toStore, opLog } from '@mmstack/primitives';
5
5
 
6
6
  /** Thrown into a task/write promise when it is aborted. `name` is `'AbortError'` (DOM convention). */
7
7
  class WorkerAbortError extends Error {
@@ -43,16 +43,16 @@ function build$2(spawn, opt) {
43
43
  let attempt = 0;
44
44
  let runIdSeq = 0;
45
45
  let destroyed = false;
46
- let crashed = false; // between a crash and the next successful handshake
46
+ let crashed = false;
47
47
  let port = null;
48
48
  const send = (msg, transfer) => {
49
49
  if (port)
50
- port.postMessage(msg, transfer); // no-op on the server / between crash and respawn
50
+ port.postMessage(msg, transfer);
51
51
  };
52
52
  const onMessage = (msg) => {
53
53
  switch (msg.type) {
54
54
  case 'ready':
55
- attempt = 0; // a successful handshake resets the backoff
55
+ attempt = 0;
56
56
  crashed = false;
57
57
  manifest.set({
58
58
  hostId: msg.hostId,
@@ -81,10 +81,9 @@ function build$2(spawn, opt) {
81
81
  return;
82
82
  }
83
83
  case 'task:aborted':
84
- taskPending.delete(msg.runId); // caller already rejected at abort
84
+ taskPending.delete(msg.runId);
85
85
  return;
86
86
  default:
87
- // store:* traffic → replicas
88
87
  for (const h of storeHandlers)
89
88
  h(msg);
90
89
  }
@@ -94,7 +93,6 @@ function build$2(spawn, opt) {
94
93
  return;
95
94
  crashed = true;
96
95
  connected.set(false);
97
- // in-flight tasks can't survive a crash; store replicas hold their value and re-hydrate on reconnect
98
96
  for (const [, p] of taskPending)
99
97
  p.reject(new WorkerCrashedError());
100
98
  taskPending.clear();
@@ -109,14 +107,11 @@ function build$2(spawn, opt) {
109
107
  }, delay);
110
108
  };
111
109
  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
110
  if (port)
115
111
  closePort(port);
116
112
  port = spawn();
117
113
  port.onmessage = (ev) => onMessage(ev.data);
118
- // duck-typed crash detection via property setters (NOT addEventListener that perturbs a node
119
- // MessagePort's onmessage delivery). A real `Worker` has onerror; onmessageerror where present.
114
+ // crash detection via property setters, not addEventListener (perturbs node MessagePort delivery)
120
115
  const evt = port;
121
116
  if ('onerror' in evt)
122
117
  evt.onerror = handleCrash;
@@ -124,7 +119,6 @@ function build$2(spawn, opt) {
124
119
  evt.onmessageerror = handleCrash;
125
120
  send({ type: 'hello', proto: 1, clientId });
126
121
  };
127
- // no workers on the server — connected stays false, runTask rejects, replicas render their default
128
122
  if (!isServer)
129
123
  openPort();
130
124
  inject(DestroyRef).onDestroy(() => teardown());
@@ -134,8 +128,6 @@ function build$2(spawn, opt) {
134
128
  for (const [, p] of taskPending)
135
129
  p.reject(new WorkerAbortError('worker connection destroyed'));
136
130
  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
131
  for (const fn of disconnectHooks)
140
132
  fn();
141
133
  if (port)
@@ -150,9 +142,6 @@ function build$2(spawn, opt) {
150
142
  return Promise.reject(new WorkerCrashedError('no worker on the server'));
151
143
  if (destroyed)
152
144
  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
145
  if (crashed)
157
146
  return Promise.reject(new WorkerCrashedError('worker is restarting'));
158
147
  if (o?.signal?.aborted)
@@ -207,7 +196,7 @@ function build$1(params, options) {
207
196
  const { worker, task } = options;
208
197
  const runTask = (input, signal) => worker.runTask(task, input, { signal });
209
198
  const value = signal(options.defaultValue, { ...(ngDevMode ? { debugName: "value" } : /* istanbul ignore next */ {}), equal: userEqual
210
- ? (a, b) => (a === undefined || b === undefined ? a === b : userEqual(a, b))
199
+ ? (a, b) => a === undefined || b === undefined ? a === b : userEqual(a, b)
211
200
  : undefined });
212
201
  const status = signal('idle', ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
213
202
  const error = signal(undefined, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
@@ -233,7 +222,7 @@ function build$1(params, options) {
233
222
  if (inFlight === ac)
234
223
  inFlight = null;
235
224
  if (myEpoch !== epoch)
236
- return; // superseded — discard
225
+ return;
237
226
  value.set(result);
238
227
  status.set('resolved');
239
228
  }, (err) => {
@@ -241,7 +230,8 @@ function build$1(params, options) {
241
230
  inFlight = null;
242
231
  if (myEpoch !== epoch)
243
232
  return;
244
- if (err instanceof WorkerAbortError || err?.name === 'AbortError')
233
+ if (err instanceof WorkerAbortError ||
234
+ err?.name === 'AbortError')
245
235
  return;
246
236
  error.set(err);
247
237
  status.set('error');
@@ -251,10 +241,9 @@ function build$1(params, options) {
251
241
  const ref = effect(() => {
252
242
  const i = input();
253
243
  if (isServer || i === PAUSED)
254
- return; // server: never run · paused: hold
244
+ return;
255
245
  if (i === undefined)
256
- return; // disabled — keep current value/status
257
- // dedup: a resume to the same input (e.g. after a pause) does not re-run
246
+ return;
258
247
  if (Object.is(i, lastInput) && hasRun && untracked(status) !== 'error')
259
248
  return;
260
249
  untracked(() => start(i));
@@ -277,11 +266,11 @@ function build$1(params, options) {
277
266
  },
278
267
  abort: () => {
279
268
  if (!inFlight)
280
- return; // nothing in flight (a scope's abortPending must not disturb a settled value)
269
+ return;
281
270
  epoch++;
282
271
  inFlight.abort();
283
272
  inFlight = null;
284
- status.set('local'); // value kept
273
+ status.set('local');
285
274
  },
286
275
  destroy: () => {
287
276
  epoch++;
@@ -292,7 +281,19 @@ function build$1(params, options) {
292
281
  if (options.register) {
293
282
  const scope = injectTransitionScope();
294
283
  scope.add(self, { suspends: options.register === 'suspend' });
295
- inject(DestroyRef).onDestroy(() => scope.remove(self));
284
+ let removed = false;
285
+ const remove = () => {
286
+ if (removed)
287
+ return;
288
+ removed = true;
289
+ scope.remove(self);
290
+ };
291
+ inject(DestroyRef).onDestroy(remove);
292
+ const destroy = self.destroy;
293
+ self.destroy = () => {
294
+ remove();
295
+ destroy();
296
+ };
296
297
  }
297
298
  return self;
298
299
  }
@@ -302,42 +303,56 @@ function workerStore(worker, key, opt) {
302
303
  return runInInjectionContext(injector, () => build(worker, key, opt));
303
304
  }
304
305
  function build(worker, key, opt) {
305
- const injector = inject(Injector); // captured for use in write() (called outside injection context)
306
+ const injector = inject(Injector);
306
307
  const root = signal(opt?.defaultValue, ...(ngDevMode ? [{ debugName: "root" }] : /* istanbul ignore next */ []));
307
- // shape-adaptive: the store reflects `root` even as it goes undefined → object on first snapshot
308
- const replica = toStore(root, {
308
+ const s = toStore(root, {
309
309
  injector,
310
310
  });
311
+ const log = opLog(root, { injector });
311
312
  const status = signal('loading', ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
312
313
  const error = signal(undefined, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
313
314
  const isLoading = computed(() => {
314
- const s = status();
315
- return s === 'loading' || s === 'reloading';
315
+ const st = status();
316
+ return st === 'loading' || st === 'reloading';
316
317
  }, ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
317
318
  let hasSnapshot = false;
318
- let resyncing = false; // awaiting a fresh snapshot after a detected gap
319
+ let resyncing = false;
319
320
  let lastVersion = 0;
320
321
  let writeSeq = 0;
321
322
  const writePending = new Map();
322
323
  const hasValue = () => hasSnapshot;
323
- // resolve any write whose acked authoritative version has now been applied to this replica
324
+ const isOwned = () => worker.manifest()?.stores.includes(key) ?? false;
324
325
  const settleWrites = () => {
325
326
  for (const [id, p] of writePending) {
326
327
  if (p.version !== null && lastVersion >= p.version) {
327
328
  writePending.delete(id);
328
- p.resolve();
329
+ p.resolve?.();
329
330
  }
330
331
  }
331
332
  };
333
+ const shipLocal = (ops) => {
334
+ if (!ops.length || !isOwned() || !untracked(worker.connected))
335
+ return;
336
+ const id = ++writeSeq;
337
+ writePending.set(id, { version: null });
338
+ worker._send({
339
+ type: 'store:write',
340
+ store: key,
341
+ writeId: id,
342
+ clientId: worker.clientId,
343
+ ops,
344
+ });
345
+ };
346
+ const shipUnsub = log.subscribe((batch) => shipLocal(batch.ops));
332
347
  const onStoreMessage = (msg) => {
333
348
  if (!('store' in msg) || msg.store !== key)
334
349
  return;
335
350
  switch (msg.type) {
336
351
  case 'store:snapshot': {
337
- root.set(msg.value);
352
+ log.apply([{ kind: 'set', path: [], next: msg.value }]);
338
353
  lastVersion = msg.version;
339
354
  hasSnapshot = true;
340
- resyncing = false; // a fresh snapshot ends any resync
355
+ resyncing = false;
341
356
  status.set('resolved');
342
357
  error.set(undefined);
343
358
  settleWrites();
@@ -345,15 +360,14 @@ function build(worker, key, opt) {
345
360
  }
346
361
  case 'store:ops': {
347
362
  if (!hasSnapshot || resyncing)
348
- return; // pre-hydration, or dropping until a fresh snapshot
363
+ return;
349
364
  if (msg.batch.version <= lastVersion)
350
- return; // stale/duplicate (at-least-once transport)
365
+ return;
351
366
  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();
367
+ resync(); // gap: re-hydrate rather than apply out of order
354
368
  return;
355
369
  }
356
- root.set(applyOps(untracked(root), msg.batch.ops));
370
+ log.apply(msg.batch.ops);
357
371
  lastVersion = msg.batch.version;
358
372
  settleWrites();
359
373
  return;
@@ -364,10 +378,10 @@ function build(worker, key, opt) {
364
378
  return;
365
379
  if (lastVersion >= msg.version) {
366
380
  writePending.delete(msg.writeId);
367
- p.resolve(); // the authoritative batch already landed (FIFO: ops precede the ack)
381
+ p.resolve?.();
368
382
  }
369
383
  else {
370
- p.version = msg.version; // resolve once a batch reaches this version
384
+ p.version = msg.version;
371
385
  }
372
386
  return;
373
387
  }
@@ -375,22 +389,22 @@ function build(worker, key, opt) {
375
389
  const p = writePending.get(msg.writeId);
376
390
  if (p) {
377
391
  writePending.delete(msg.writeId);
378
- p.reject(deserializeError(msg.error));
392
+ p.reject?.(deserializeError(msg.error));
379
393
  }
380
394
  return;
381
395
  }
382
396
  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
397
  switch (msg.status) {
386
398
  case 'error':
387
- error.set(msg.error ? deserializeError(msg.error) : new Error('remote computation error'));
399
+ error.set(msg.error
400
+ ? deserializeError(msg.error)
401
+ : new Error('remote computation error'));
388
402
  status.set('error');
389
403
  return;
390
404
  case 'loading':
391
405
  case 'reloading':
392
406
  error.set(undefined);
393
- status.set(hasSnapshot ? 'reloading' : 'loading'); // hold the value while recomputing
407
+ status.set(hasSnapshot ? 'reloading' : 'loading');
394
408
  return;
395
409
  case 'resolved':
396
410
  error.set(undefined);
@@ -405,29 +419,33 @@ function build(worker, key, opt) {
405
419
  };
406
420
  const subscribe = () => {
407
421
  if (hasSnapshot)
408
- status.set('reloading'); // holding stale while the fresh snapshot lands
409
- worker._send({ type: 'store:subscribe', store: key, clientId: worker.clientId });
422
+ status.set('reloading');
423
+ worker._send({
424
+ type: 'store:subscribe',
425
+ store: key,
426
+ clientId: worker.clientId,
427
+ });
410
428
  };
411
- // a lost batch left a version gap — hold the stale value and re-subscribe for a fresh snapshot
412
429
  const resync = () => {
413
430
  resyncing = true;
414
431
  status.set('reloading');
415
- worker._send({ type: 'store:subscribe', store: key, clientId: worker.clientId });
432
+ worker._send({
433
+ type: 'store:subscribe',
434
+ store: key,
435
+ clientId: worker.clientId,
436
+ });
416
437
  };
417
438
  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
439
  const offReady = worker._onReady(subscribe);
421
440
  if (untracked(worker.connected))
422
- subscribe();
423
- // a crash can't be delivered — pending writes reject (the caller holds the value + recipe to retry)
441
+ subscribe(); // already up (created post-handshake): _onReady won't fire
424
442
  const offDisconnect = worker._onDisconnect(() => {
425
443
  for (const [, p] of writePending)
426
- p.reject(new WorkerCrashedError());
444
+ p.reject?.(new WorkerCrashedError());
427
445
  writePending.clear();
428
446
  });
429
447
  const self = {
430
- store: replica,
448
+ store: s,
431
449
  value: root.asReadonly(),
432
450
  status,
433
451
  error,
@@ -438,54 +456,61 @@ function build(worker, key, opt) {
438
456
  const base = untracked(root);
439
457
  if (base === undefined)
440
458
  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
459
  if (!untracked(worker.connected))
444
460
  return Promise.reject(new WorkerCrashedError('worker is not connected'));
445
- // fork-diff: a scratch store seeded with the CURRENT value (same ref → copy-on-write keeps
446
- // untouched subtrees shared), so diffOps against the base is O(changed paths)
447
- const scratch = signal(base, ...(ngDevMode ? [{ debugName: "scratch" }] : /* istanbul ignore next */ []));
448
- const scratchStore = toStore(scratch, {
449
- injector,
450
- });
451
- recipe(scratchStore);
452
- const ops = diffOps(base, untracked(scratch));
453
- if (!ops.length)
461
+ if (!isOwned())
462
+ return Promise.reject(new Error(`[@mmstack/worker] store is read-only (published): ${key}`));
463
+ recipe(s);
464
+ const before = writeSeq;
465
+ log.flush();
466
+ if (writeSeq === before)
454
467
  return Promise.resolve();
455
- const writeId = ++writeSeq;
468
+ const writeId = writeSeq;
456
469
  return new Promise((resolve, reject) => {
457
- writePending.set(writeId, { resolve, reject, version: null });
458
- worker._send({
459
- type: 'store:write',
460
- store: key,
461
- writeId,
462
- clientId: worker.clientId,
463
- ops,
464
- });
470
+ const p = writePending.get(writeId);
471
+ if (!p)
472
+ return resolve();
473
+ p.resolve = resolve;
474
+ p.reject = reject;
465
475
  });
466
476
  },
467
477
  destroy: () => {
478
+ shipUnsub();
479
+ log.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
- p.reject(new WorkerAbortError('worker store destroyed'));
484
+ p.reject?.(new WorkerAbortError('worker store destroyed'));
474
485
  writePending.clear();
475
- worker._send({ type: 'store:unsubscribe', store: key, clientId: worker.clientId });
486
+ worker._send({
487
+ type: 'store:unsubscribe',
488
+ store: key,
489
+ clientId: worker.clientId,
490
+ });
476
491
  },
477
492
  };
478
493
  inject(DestroyRef).onDestroy(() => self.destroy());
479
494
  if (opt?.register) {
480
495
  const scope = injectTransitionScope();
481
496
  scope.add(self, { suspends: opt.register === 'suspend' });
482
- inject(DestroyRef).onDestroy(() => scope.remove(self));
497
+ let removed = false;
498
+ const remove = () => {
499
+ if (removed)
500
+ return;
501
+ removed = true;
502
+ scope.remove(self);
503
+ };
504
+ inject(DestroyRef).onDestroy(remove);
505
+ const destroy = self.destroy;
506
+ self.destroy = () => {
507
+ remove();
508
+ destroy();
509
+ };
483
510
  }
484
511
  return self;
485
512
  }
486
513
 
487
- // shared wire types, re-exported so main-thread consumers import only from '@mmstack/worker'
488
-
489
514
  /**
490
515
  * Generated bundle index. Do not edit.
491
516
  */
@@ -1 +1 @@
1
- {"version":3,"file":"mmstack-worker.mjs","sources":["../../../../packages/worker/src/lib/connect-worker.ts","../../../../packages/worker/src/lib/worker-resource.ts","../../../../packages/worker/src/lib/worker-store.ts","../../../../packages/worker/src/index.ts","../../../../packages/worker/src/mmstack-worker.ts"],"sourcesContent":["import {\n DestroyRef,\n inject,\n Injector,\n PLATFORM_ID,\n runInInjectionContext,\n signal,\n type Signal,\n} from '@angular/core';\nimport {\n closePort,\n deserializeError,\n generateId,\n takeTransferables,\n type HasSchema,\n type SchemaOf,\n type TaskInput,\n type TaskOutput,\n type WorkerEnvelope,\n type WorkerPortLike,\n type WorkerSchema,\n} from '@mmstack/worker/protocol';\n\n/** What the worker advertised in its `ready` handshake. */\nexport type WorkerManifest = {\n readonly hostId: string;\n readonly stores: readonly string[];\n readonly published: readonly string[];\n readonly tasks: readonly string[];\n};\n\n/** Thrown into a task/write promise when it is aborted. `name` is `'AbortError'` (DOM convention). */\nexport class WorkerAbortError extends Error {\n constructor(message = 'The worker task was aborted') {\n super(message);\n this.name = 'AbortError';\n }\n}\n\n/** Thrown into pending tasks/writes when the worker crashes. `name` is `'WorkerCrashedError'`. */\nexport class WorkerCrashedError extends Error {\n constructor(message = 'The worker crashed') {\n super(message);\n this.name = 'WorkerCrashedError';\n }\n}\n\nexport type ConnectWorkerOptions = {\n readonly injector?: Injector;\n /** `'auto'` (default) respawns on crash; `'manual'` surfaces disconnect and stops. */\n readonly restart?: 'auto' | 'manual';\n /** Backoff before respawn attempt `n` (0-based). Default exponential 1s→30s. */\n readonly restartDelay?: (attempt: number) => number;\n};\n\nexport type WorkerRef<M extends WorkerSchema = WorkerSchema> = HasSchema<M> & {\n /** True once the `ready` handshake has completed (and again after an auto-restart). */\n readonly connected: Signal<boolean>;\n /** The worker's advertised manifest, or `null` before the first `ready`. */\n readonly manifest: Signal<WorkerManifest | null>;\n /** Run a named task exposed by the worker host; resolves with its result (typed from the schema). */\n runTask<K extends keyof M['tasks'] & string>(\n task: K,\n input: TaskInput<M, K>,\n opt?: { signal?: AbortSignal; transfer?: Transferable[] },\n ): Promise<TaskOutput<M, K>>;\n destroy(): void;\n\n // ── internal seam for workerStore (phase 4b) — not part of the friendly surface ──\n /** @internal Post an envelope to the worker. */\n _send(msg: WorkerEnvelope, transfer?: Transferable[]): void;\n /** @internal Receive every non-task envelope (store traffic). Returns an unsubscribe. */\n _subscribe(handler: (msg: WorkerEnvelope) => void): () => void;\n /** @internal Run `fn` on every (re)connection — used to re-subscribe stores after a restart. */\n _onReady(fn: () => void): () => void;\n /** @internal Run `fn` when the connection drops (crash) — replicas reject pending writes here. */\n _onDisconnect(fn: () => void): () => void;\n /** @internal This client's identity on the transport. */\n readonly clientId: string;\n};\n\n/**\n * Connects the main thread to a worker over a {@link WorkerPortLike} the caller provides via `spawn`\n * — typically `() => new Worker(new URL('./my.worker', import.meta.url), { type: 'module' })` (the\n * URL literal must live in APP code so the bundler emits the worker chunk). Performs the hello/ready\n * handshake, exposes `connected`/`manifest`, routes named-task runs, and (via internal seams)\n * carries the store-replication traffic for {@link workerStore}. On crash it respawns with backoff\n * when `restart: 'auto'`.\n */\nexport function connectWorker<H = WorkerSchema>(\n spawn: () => WorkerPortLike,\n opt?: ConnectWorkerOptions,\n): WorkerRef<SchemaOf<H>> {\n const injector = opt?.injector ?? inject(Injector);\n return runInInjectionContext(injector, () =>\n build(spawn, opt),\n ) as unknown as WorkerRef<SchemaOf<H>>;\n}\n\nfunction build(\n spawn: () => WorkerPortLike,\n opt?: ConnectWorkerOptions,\n): WorkerRef {\n const isServer = inject(PLATFORM_ID) === 'server';\n const clientId = generateId();\n const connected = signal(false);\n const manifest = signal<WorkerManifest | null>(null);\n\n const taskPending = new Map<number, { resolve: (v: any) => void; reject: (e: unknown) => void }>();\n const storeHandlers = new Set<(msg: WorkerEnvelope) => void>();\n const readyHooks = new Set<() => void>();\n const disconnectHooks = new Set<() => void>();\n const restart = opt?.restart ?? 'auto';\n const restartDelay = opt?.restartDelay ?? ((n: number) => Math.min(30_000, 1000 * 2 ** n));\n let attempt = 0;\n let runIdSeq = 0;\n let destroyed = false;\n let crashed = false; // between a crash and the next successful handshake\n let port: WorkerPortLike = null as unknown as WorkerPortLike;\n\n const send = (msg: WorkerEnvelope, transfer?: Transferable[]): void => {\n if (port) port.postMessage(msg, transfer); // no-op on the server / between crash and respawn\n };\n\n const onMessage = (msg: WorkerEnvelope): void => {\n switch (msg.type) {\n case 'ready':\n attempt = 0; // a successful handshake resets the backoff\n crashed = false;\n manifest.set({\n hostId: msg.hostId,\n stores: msg.stores,\n published: msg.published,\n tasks: msg.tasks,\n });\n connected.set(true);\n for (const hook of readyHooks) hook();\n return;\n case 'task:ok': {\n const p = taskPending.get(msg.runId);\n if (p) {\n taskPending.delete(msg.runId);\n p.resolve(msg.value);\n }\n return;\n }\n case 'task:error': {\n const p = taskPending.get(msg.runId);\n if (p) {\n taskPending.delete(msg.runId);\n p.reject(deserializeError(msg.error));\n }\n return;\n }\n case 'task:aborted':\n taskPending.delete(msg.runId); // caller already rejected at abort\n return;\n default:\n // store:* traffic → replicas\n for (const h of storeHandlers) h(msg);\n }\n };\n\n const handleCrash = (): void => {\n if (destroyed) return;\n crashed = true;\n connected.set(false);\n // in-flight tasks can't survive a crash; store replicas hold their value and re-hydrate on reconnect\n for (const [, p] of taskPending) p.reject(new WorkerCrashedError());\n taskPending.clear();\n for (const fn of disconnectHooks) fn();\n if (restart === 'manual') return;\n const delay = restartDelay(attempt++);\n setTimeout(() => {\n if (!destroyed) openPort();\n }, delay);\n };\n\n const openPort = (): void => {\n // terminate the previous worker before respawning, so a transient/manual restart never orphans\n // a live thread (a genuine crash already killed it; terminate() is then a harmless no-op)\n if (port) closePort(port);\n port = spawn();\n port.onmessage = (ev) => onMessage(ev.data as WorkerEnvelope);\n // duck-typed crash detection via property setters (NOT addEventListener — that perturbs a node\n // MessagePort's onmessage delivery). A real `Worker` has onerror; onmessageerror where present.\n const evt = port as unknown as {\n onerror?: ((e?: unknown) => void) | null;\n onmessageerror?: ((e?: unknown) => void) | null;\n };\n if ('onerror' in evt) evt.onerror = handleCrash;\n if ('onmessageerror' in evt) evt.onmessageerror = handleCrash;\n send({ type: 'hello', proto: 1, clientId });\n };\n\n // no workers on the server — connected stays false, runTask rejects, replicas render their default\n if (!isServer) openPort();\n\n inject(DestroyRef).onDestroy(() => teardown());\n\n const teardown = (): void => {\n destroyed = true;\n connected.set(false);\n for (const [, p] of taskPending) p.reject(new WorkerAbortError('worker connection destroyed'));\n taskPending.clear();\n // same contract as a crash: anything pending on the connection (replica writes) settles NOW\n // rather than dangling — a destroyed port can never deliver the echo that would resolve them\n for (const fn of disconnectHooks) fn();\n if (port) closePort(port);\n };\n\n return {\n connected,\n manifest,\n clientId,\n runTask(\n task: string,\n input: unknown,\n o?: { signal?: AbortSignal; transfer?: Transferable[] },\n ): Promise<unknown> {\n if (isServer) return Promise.reject(new WorkerCrashedError('no worker on the server'));\n if (destroyed) return Promise.reject(new WorkerAbortError('worker connection destroyed'));\n // between a crash and the respawned handshake the port is dead — a message posted into it\n // vanishes and the promise would never settle. Reject honestly; the caller retries/reloads.\n // (Before the FIRST handshake this is false: a starting worker buffers messages, so we send.)\n if (crashed) return Promise.reject(new WorkerCrashedError('worker is restarting'));\n if (o?.signal?.aborted) return Promise.reject(new WorkerAbortError());\n const runId = ++runIdSeq;\n return new Promise<unknown>((resolve, reject) => {\n taskPending.set(runId, { resolve, reject });\n send(\n { type: 'task:run', runId, task, input },\n o?.transfer ?? takeTransferables(input),\n );\n o?.signal?.addEventListener(\n 'abort',\n () => {\n if (!taskPending.has(runId)) return;\n taskPending.delete(runId);\n send({ type: 'task:abort', runId });\n reject(new WorkerAbortError());\n },\n { once: true },\n );\n });\n },\n destroy: teardown,\n _send: send,\n _subscribe(handler) {\n storeHandlers.add(handler);\n return () => storeHandlers.delete(handler);\n },\n _onReady(fn) {\n readyHooks.add(fn);\n return () => readyHooks.delete(fn);\n },\n _onDisconnect(fn) {\n disconnectHooks.add(fn);\n return () => disconnectHooks.delete(fn);\n },\n };\n}\n","import {\n computed,\n DestroyRef,\n effect,\n inject,\n Injector,\n PLATFORM_ID,\n runInInjectionContext,\n signal,\n untracked,\n type ResourceStatus,\n type Signal,\n type ValueEqualityFn,\n} from '@angular/core';\nimport { injectTransitionScope } from '@mmstack/primitives';\nimport { WorkerAbortError, type WorkerRef } from './connect-worker';\n\n/** Returned from a paused `params` fn to HOLD (keep the current value/status, run nothing). */\nexport const PAUSED: unique symbol = Symbol('@mmstack/worker:PAUSED');\n\nexport type WorkerRequestContext = { readonly paused: typeof PAUSED };\n\n/**\n * Reactive input producer. Reads signals to derive the task input; return `undefined` to disable\n * (idle, no run), or `ctx.paused` to hold. Re-runs when the returned input changes (by `Object.is`).\n */\nexport type WorkerParamsFn<TInput> = (\n ctx: WorkerRequestContext,\n) => TInput | undefined | void | typeof PAUSED;\n\nexport type WorkerResourceOptions<TResult> = {\n readonly injector?: Injector;\n /** Equality for the result value — a run resolving equal to the held value emits no notification. */\n readonly equal?: ValueEqualityFn<TResult>;\n readonly defaultValue?: TResult;\n /**\n * Hold the previous value through a re-run (status `'reloading'`) instead of clearing it. Default\n * TRUE — a `workerResource` is an async derivation, not a fetch; flashing empty mid-recompute is\n * rarely wanted.\n */\n readonly keepPrevious?: boolean;\n /** Register into the nearest transition scope: `'indicator'` drives pending, `'suspend'` also gates first paint. */\n readonly register?: false | 'indicator' | 'suspend';\n /** The connected worker whose host exposes the task. */\n readonly worker: WorkerRef;\n /** The name of the task to run (as declared in `createWorkerHost({ tasks })`). */\n readonly task: string;\n};\n\nexport type WorkerResourceRef<T> = {\n /** The latest successfully-computed value (held through re-runs per `keepPrevious`). */\n readonly value: Signal<T | undefined>;\n readonly status: Signal<ResourceStatus>;\n readonly error: Signal<unknown>;\n readonly isLoading: Signal<boolean>;\n hasValue(): boolean;\n /** Re-run with the current input, bypassing input de-duplication. */\n reload(): void;\n /** Cancel the in-flight run; the value is KEPT and status becomes `'local'`. */\n abort(): void;\n destroy(): void;\n};\n\n/**\n * Runs a named task on a connected worker, exposed with the standard resource surface so heavy\n * compute makes the UI *pending*, not *frozen*. `params` reactively derives the input; latest-wins\n * (a changed input supersedes and aborts the in-flight run); the result surfaces as\n * `value`/`status`/`error`, and — with `register` — participates in transition scopes. No-ops on the\n * server.\n */\nexport function workerResource<TInput, TResult>(\n params: WorkerParamsFn<TInput>,\n options: WorkerResourceOptions<TResult>,\n): WorkerResourceRef<TResult> {\n const injector = options.injector ?? inject(Injector);\n return runInInjectionContext(injector, () => build<TInput, TResult>(params, options));\n}\n\nfunction build<TInput, TResult>(\n params: WorkerParamsFn<TInput>,\n options: WorkerResourceOptions<TResult>,\n): WorkerResourceRef<TResult> {\n const isServer = inject(PLATFORM_ID) === 'server';\n const keepPrevious = options.keepPrevious ?? true;\n const userEqual = options.equal;\n const { worker, task } = options;\n\n const runTask = (input: TInput, signal: AbortSignal): Promise<TResult> =>\n worker.runTask(task, input, { signal }) as Promise<TResult>;\n\n const value = signal<TResult | undefined>(options.defaultValue, {\n equal: userEqual\n ? (a, b) => (a === undefined || b === undefined ? a === b : userEqual(a, b))\n : undefined,\n });\n const status = signal<ResourceStatus>('idle');\n const error = signal<unknown>(undefined);\n const isLoading = computed(() => {\n const s = status();\n return s === 'loading' || s === 'reloading';\n });\n const hasValue = () => value() !== undefined;\n\n let epoch = 0;\n let inFlight: AbortController | null = null;\n let lastInput: TInput | undefined;\n let hasRun = false;\n\n const start = (input: TInput): void => {\n const myEpoch = ++epoch;\n inFlight?.abort();\n const ac = new AbortController();\n inFlight = ac;\n lastInput = input;\n hasRun = true;\n status.set(keepPrevious && value() !== undefined ? 'reloading' : 'loading');\n error.set(undefined);\n\n runTask(input, ac.signal).then(\n (result) => {\n if (inFlight === ac) inFlight = null;\n if (myEpoch !== epoch) return; // superseded — discard\n value.set(result);\n status.set('resolved');\n },\n (err) => {\n if (inFlight === ac) inFlight = null;\n if (myEpoch !== epoch) return;\n if (err instanceof WorkerAbortError || (err as { name?: string })?.name === 'AbortError')\n return;\n error.set(err);\n status.set('error');\n },\n );\n };\n\n const input = computed(() => params({ paused: PAUSED }));\n\n const ref = effect(() => {\n const i = input();\n if (isServer || i === PAUSED) return; // server: never run · paused: hold\n if (i === undefined) return; // disabled — keep current value/status\n // dedup: a resume to the same input (e.g. after a pause) does not re-run\n if (Object.is(i, lastInput) && hasRun && untracked(status) !== 'error') return;\n untracked(() => start(i as TInput));\n });\n\n inject(DestroyRef).onDestroy(() => {\n epoch++;\n inFlight?.abort();\n });\n\n const self: WorkerResourceRef<TResult> = {\n value,\n status,\n error,\n isLoading,\n hasValue,\n reload: () => {\n const i = untracked(input);\n if (isServer || i === PAUSED || i === undefined) return;\n start(i as TInput);\n },\n abort: () => {\n if (!inFlight) return; // nothing in flight (a scope's abortPending must not disturb a settled value)\n epoch++;\n inFlight.abort();\n inFlight = null;\n status.set('local'); // value kept\n },\n destroy: () => {\n epoch++;\n inFlight?.abort();\n ref.destroy();\n },\n };\n\n if (options.register) {\n const scope = injectTransitionScope();\n scope.add(self, { suspends: options.register === 'suspend' });\n inject(DestroyRef).onDestroy(() => scope.remove(self));\n }\n\n return self;\n}\n","import {\n computed,\n DestroyRef,\n inject,\n Injector,\n runInInjectionContext,\n signal,\n untracked,\n type ResourceStatus,\n type Signal,\n type WritableSignal,\n} from '@angular/core';\nimport {\n applyOps,\n diffOps,\n injectTransitionScope,\n toStore,\n type SignalStore,\n type WritableSignalStore,\n} from '@mmstack/primitives';\nimport {\n deserializeError,\n type IsWritableKey,\n type StoreKeys,\n type StoreValueOf,\n type WorkerEnvelope,\n type WorkerSchema,\n} from '@mmstack/worker/protocol';\nimport {\n WorkerAbortError,\n WorkerCrashedError,\n type WorkerRef,\n} from './connect-worker';\n\nexport type WorkerStoreOptions<T> = {\n readonly injector?: Injector;\n /** Value the replica holds before the first snapshot arrives. */\n readonly defaultValue?: T;\n /** Register into the nearest transition scope while hydrating/reloading. */\n readonly register?: false | 'indicator' | 'suspend';\n};\n\n/** The write path — present only on OWNED (writable) subtrees; absent on published (read-only) ones. */\nexport type WorkerStoreWrite<T> = {\n /**\n * Route a write to the OWNER: run `recipe` against a writable draft of the current replica, diff\n * it to ops, ship them. Resolves once the owner's authoritative batch carrying this write has been\n * applied to THIS replica (honest async — the value is not applied locally first; for optimistic\n * UI, fork the replica and reconcile on resolve). Rejects if the owner reports a write error.\n */\n write(recipe: (draft: WritableSignalStore<T>) => void): Promise<void>;\n};\n\nexport type WorkerStoreRef<T, W extends boolean = true> = {\n /** The live, READ-ONLY replica — deep per-leaf reads, mirrored from the worker-owned subtree. */\n readonly store: SignalStore<T>;\n /** The replica root value (undefined before hydration). */\n readonly value: Signal<T | undefined>;\n readonly status: Signal<ResourceStatus>;\n readonly error: Signal<unknown>;\n readonly isLoading: Signal<boolean>;\n /** The underlying worker connection's liveness. */\n readonly connected: Signal<boolean>;\n hasValue(): boolean;\n destroy(): void;\n} & (W extends true ? WorkerStoreWrite<T> : object);\n\n/**\n * A live READ-ONLY replica of a store subtree OWNED by the worker. Subscribes over the\n * {@link WorkerRef}, hydrates from the owner's snapshot, then applies each authoritative op batch\n * into a main-thread `store` — one `set` per batch, so a batch of N ops is a single notification\n * wave. Satisfies `ResourceLike`/`UseSource`, so it participates in transition scopes and nests in\n * `latest()`/`use()`. Writes are not local: they route to the owner (added in the next step).\n */\n// manifest-driven: key constrained to the worker's stores/published, value inferred, and `write()`\n// present only for OWNED keys\nexport function workerStore<M extends WorkerSchema, K extends StoreKeys<M>>(\n worker: WorkerRef<M>,\n key: K,\n opt?: WorkerStoreOptions<StoreValueOf<M, K> & object>,\n): WorkerStoreRef<StoreValueOf<M, K> & object, IsWritableKey<M, K>>;\n// explicit-value: for an untyped connection, or to override the inferred type\nexport function workerStore<T extends object>(\n worker: WorkerRef,\n key: string,\n opt?: WorkerStoreOptions<T>,\n): WorkerStoreRef<T, true>;\nexport function workerStore(\n worker: WorkerRef,\n key: string,\n opt?: WorkerStoreOptions<any>,\n): WorkerStoreRef<any, boolean> {\n const injector = opt?.injector ?? inject(Injector);\n return runInInjectionContext(injector, () => build(worker, key, opt));\n}\n\nfunction build<T extends object>(\n worker: WorkerRef,\n key: string,\n opt?: WorkerStoreOptions<T>,\n): WorkerStoreRef<T, true> {\n const injector = inject(Injector); // captured for use in write() (called outside injection context)\n const root = signal<T | undefined>(opt?.defaultValue);\n // shape-adaptive: the store reflects `root` even as it goes undefined → object on first snapshot\n const replica = toStore(root as unknown as WritableSignal<T>, {\n injector,\n }) as SignalStore<T>;\n\n const status = signal<ResourceStatus>('loading');\n const error = signal<unknown>(undefined);\n const isLoading = computed(() => {\n const s = status();\n return s === 'loading' || s === 'reloading';\n });\n let hasSnapshot = false;\n let resyncing = false; // awaiting a fresh snapshot after a detected gap\n let lastVersion = 0;\n let writeSeq = 0;\n const writePending = new Map<\n number,\n { resolve: () => void; reject: (e: unknown) => void; version: number | null }\n >();\n\n const hasValue = () => hasSnapshot;\n\n // resolve any write whose acked authoritative version has now been applied to this replica\n const settleWrites = () => {\n for (const [id, p] of writePending) {\n if (p.version !== null && lastVersion >= p.version) {\n writePending.delete(id);\n p.resolve();\n }\n }\n };\n\n const onStoreMessage = (msg: WorkerEnvelope): void => {\n if (!('store' in msg) || msg.store !== key) return;\n switch (msg.type) {\n case 'store:snapshot': {\n root.set(msg.value as T);\n lastVersion = msg.version;\n hasSnapshot = true;\n resyncing = false; // a fresh snapshot ends any resync\n status.set('resolved');\n error.set(undefined);\n settleWrites();\n return;\n }\n case 'store:ops': {\n if (!hasSnapshot || resyncing) return; // pre-hydration, or dropping until a fresh snapshot\n if (msg.batch.version <= lastVersion) return; // stale/duplicate (at-least-once transport)\n if (msg.batch.version > lastVersion + 1) {\n // GAP: a batch was lost — re-hydrate from a fresh snapshot rather than apply out of order\n resync();\n return;\n }\n root.set(applyOps(untracked(root) as T, msg.batch.ops));\n lastVersion = msg.batch.version;\n settleWrites();\n return;\n }\n case 'store:write:ack': {\n const p = writePending.get(msg.writeId);\n if (!p) return;\n if (lastVersion >= msg.version) {\n writePending.delete(msg.writeId);\n p.resolve(); // the authoritative batch already landed (FIFO: ops precede the ack)\n } else {\n p.version = msg.version; // resolve once a batch reaches this version\n }\n return;\n }\n case 'store:write:error': {\n const p = writePending.get(msg.writeId);\n if (p) {\n writePending.delete(msg.writeId);\n p.reject(deserializeError(msg.error));\n }\n return;\n }\n case 'store:status': {\n // rung 3: a published derivation's remote status → this replica's status, so an in-flight\n // worker computation shows as pending on the main thread (holding the last value)\n switch (msg.status) {\n case 'error':\n error.set(msg.error ? deserializeError(msg.error) : new Error('remote computation error'));\n status.set('error');\n return;\n case 'loading':\n case 'reloading':\n error.set(undefined);\n status.set(hasSnapshot ? 'reloading' : 'loading'); // hold the value while recomputing\n return;\n case 'resolved':\n error.set(undefined);\n if (hasSnapshot) status.set('resolved');\n return;\n default:\n return;\n }\n }\n }\n };\n\n const subscribe = (): void => {\n if (hasSnapshot) status.set('reloading'); // holding stale while the fresh snapshot lands\n worker._send({ type: 'store:subscribe', store: key, clientId: worker.clientId });\n };\n\n // a lost batch left a version gap — hold the stale value and re-subscribe for a fresh snapshot\n const resync = (): void => {\n resyncing = true;\n status.set('reloading');\n worker._send({ type: 'store:subscribe', store: key, clientId: worker.clientId });\n };\n\n const unsub = worker._subscribe(onStoreMessage);\n // subscribe on each `ready` — the first connection and every auto-restart re-hydration; if the\n // connection is ALREADY up (created post-handshake), `_onReady` won't fire, so subscribe now\n const offReady = worker._onReady(subscribe);\n if (untracked(worker.connected)) subscribe();\n // a crash can't be delivered — pending writes reject (the caller holds the value + recipe to retry)\n const offDisconnect = worker._onDisconnect(() => {\n for (const [, p] of writePending) p.reject(new WorkerCrashedError());\n writePending.clear();\n });\n\n const self: WorkerStoreRef<T> = {\n store: replica,\n value: root.asReadonly(),\n status,\n error,\n isLoading,\n connected: worker.connected,\n hasValue,\n write: (recipe) => {\n const base = untracked(root);\n if (base === undefined)\n return Promise.reject(\n new Error('[@mmstack/worker] cannot write before the replica has hydrated'),\n );\n // hydrated but disconnected = the worker crashed (or is restarting) — a write posted now\n // vanishes into a dead port and its echo can never arrive. Reject; the caller retries.\n if (!untracked(worker.connected))\n return Promise.reject(new WorkerCrashedError('worker is not connected'));\n // fork-diff: a scratch store seeded with the CURRENT value (same ref → copy-on-write keeps\n // untouched subtrees shared), so diffOps against the base is O(changed paths)\n const scratch = signal(base);\n const scratchStore = toStore(scratch as unknown as WritableSignal<T>, {\n injector,\n }) as unknown as WritableSignalStore<T>;\n recipe(scratchStore);\n const ops = diffOps(base, untracked(scratch));\n if (!ops.length) return Promise.resolve();\n\n const writeId = ++writeSeq;\n return new Promise<void>((resolve, reject) => {\n writePending.set(writeId, { resolve, reject, version: null });\n worker._send({\n type: 'store:write',\n store: key,\n writeId,\n clientId: worker.clientId,\n ops,\n });\n });\n },\n destroy: () => {\n unsub();\n offReady();\n offDisconnect();\n // acks can no longer be received — settle any in-flight write instead of dangling it\n for (const [, p] of writePending)\n p.reject(new WorkerAbortError('worker store destroyed'));\n writePending.clear();\n worker._send({ type: 'store:unsubscribe', store: key, clientId: worker.clientId });\n },\n };\n\n inject(DestroyRef).onDestroy(() => self.destroy());\n\n if (opt?.register) {\n const scope = injectTransitionScope();\n scope.add(self, { suspends: opt.register === 'suspend' });\n inject(DestroyRef).onDestroy(() => scope.remove(self));\n }\n\n return self;\n}\n","// shared wire types, re-exported so main-thread consumers import only from '@mmstack/worker'\nexport * from '@mmstack/worker/protocol';\n\nexport {\n connectWorker,\n WorkerAbortError,\n WorkerCrashedError,\n type ConnectWorkerOptions,\n type WorkerManifest,\n type WorkerRef,\n} from './lib/connect-worker';\nexport {\n PAUSED,\n workerResource,\n type WorkerParamsFn,\n type WorkerRequestContext,\n type WorkerResourceOptions,\n type WorkerResourceRef,\n} from './lib/worker-resource';\nexport {\n workerStore,\n type WorkerStoreOptions,\n type WorkerStoreRef,\n} from './lib/worker-store';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["build"],"mappings":"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"}
1
+ {"version":3,"file":"mmstack-worker.mjs","sources":["../../../../packages/worker/src/lib/connect-worker.ts","../../../../packages/worker/src/lib/worker-resource.ts","../../../../packages/worker/src/lib/worker-store.ts","../../../../packages/worker/src/mmstack-worker.ts"],"sourcesContent":["import {\n DestroyRef,\n inject,\n Injector,\n PLATFORM_ID,\n runInInjectionContext,\n signal,\n type Signal,\n} from '@angular/core';\nimport {\n closePort,\n deserializeError,\n generateId,\n takeTransferables,\n type HasSchema,\n type SchemaOf,\n type TaskInput,\n type TaskOutput,\n type WorkerEnvelope,\n type WorkerPortLike,\n type WorkerSchema,\n} from '@mmstack/worker/protocol';\n\n/** What the worker advertised in its `ready` handshake. */\nexport type WorkerManifest = {\n readonly hostId: string;\n readonly stores: readonly string[];\n readonly published: readonly string[];\n readonly tasks: readonly string[];\n};\n\n/** Thrown into a task/write promise when it is aborted. `name` is `'AbortError'` (DOM convention). */\nexport class WorkerAbortError extends Error {\n constructor(message = 'The worker task was aborted') {\n super(message);\n this.name = 'AbortError';\n }\n}\n\n/** Thrown into pending tasks/writes when the worker crashes. `name` is `'WorkerCrashedError'`. */\nexport class WorkerCrashedError extends Error {\n constructor(message = 'The worker crashed') {\n super(message);\n this.name = 'WorkerCrashedError';\n }\n}\n\nexport type ConnectWorkerOptions = {\n readonly injector?: Injector;\n /** `'auto'` (default) respawns on crash; `'manual'` surfaces disconnect and stops. */\n readonly restart?: 'auto' | 'manual';\n /** Backoff before respawn attempt `n` (0-based). Default exponential 1s→30s. */\n readonly restartDelay?: (attempt: number) => number;\n};\n\nexport type WorkerRef<M extends WorkerSchema = WorkerSchema> = HasSchema<M> & {\n /** True once the `ready` handshake has completed (and again after an auto-restart). */\n readonly connected: Signal<boolean>;\n /** The worker's advertised manifest, or `null` before the first `ready`. */\n readonly manifest: Signal<WorkerManifest | null>;\n /** Run a named task exposed by the worker host; resolves with its result (typed from the schema). */\n runTask<K extends keyof M['tasks'] & string>(\n task: K,\n input: TaskInput<M, K>,\n opt?: { signal?: AbortSignal; transfer?: Transferable[] },\n ): Promise<TaskOutput<M, K>>;\n destroy(): void;\n\n /** @internal Post an envelope to the worker. */\n _send(msg: WorkerEnvelope, transfer?: Transferable[]): void;\n /** @internal Receive every non-task envelope (store traffic). Returns an unsubscribe. */\n _subscribe(handler: (msg: WorkerEnvelope) => void): () => void;\n /** @internal Run `fn` on every (re)connection — used to re-subscribe stores after a restart. */\n _onReady(fn: () => void): () => void;\n /** @internal Run `fn` when the connection drops (crash) — replicas reject pending writes here. */\n _onDisconnect(fn: () => void): () => void;\n /** @internal This client's identity on the transport. */\n readonly clientId: string;\n};\n\n/**\n * Connects the main thread to a worker over a {@link WorkerPortLike} the caller provides via `spawn`\n * — typically `() => new Worker(new URL('./my.worker', import.meta.url), { type: 'module' })` (the\n * URL literal must live in APP code so the bundler emits the worker chunk). Performs the hello/ready\n * handshake, exposes `connected`/`manifest`, routes named-task runs, and (via internal seams)\n * carries the store-replication traffic for {@link workerStore}. On crash it respawns with backoff\n * when `restart: 'auto'`.\n */\nexport function connectWorker<H = WorkerSchema>(\n spawn: () => WorkerPortLike,\n opt?: ConnectWorkerOptions,\n): WorkerRef<SchemaOf<H>> {\n const injector = opt?.injector ?? inject(Injector);\n return runInInjectionContext(injector, () =>\n build(spawn, opt),\n ) as unknown as WorkerRef<SchemaOf<H>>;\n}\n\nfunction build(\n spawn: () => WorkerPortLike,\n opt?: ConnectWorkerOptions,\n): WorkerRef {\n const isServer = inject(PLATFORM_ID) === 'server';\n const clientId = generateId();\n const connected = signal(false);\n const manifest = signal<WorkerManifest | null>(null);\n\n const taskPending = new Map<number, { resolve: (v: any) => void; reject: (e: unknown) => void }>();\n const storeHandlers = new Set<(msg: WorkerEnvelope) => void>();\n const readyHooks = new Set<() => void>();\n const disconnectHooks = new Set<() => void>();\n const restart = opt?.restart ?? 'auto';\n const restartDelay = opt?.restartDelay ?? ((n: number) => Math.min(30_000, 1000 * 2 ** n));\n let attempt = 0;\n let runIdSeq = 0;\n let destroyed = false;\n let crashed = false;\n let port: WorkerPortLike = null as unknown as WorkerPortLike;\n\n const send = (msg: WorkerEnvelope, transfer?: Transferable[]): void => {\n if (port) port.postMessage(msg, transfer);\n };\n\n const onMessage = (msg: WorkerEnvelope): void => {\n switch (msg.type) {\n case 'ready':\n attempt = 0;\n crashed = false;\n manifest.set({\n hostId: msg.hostId,\n stores: msg.stores,\n published: msg.published,\n tasks: msg.tasks,\n });\n connected.set(true);\n for (const hook of readyHooks) hook();\n return;\n case 'task:ok': {\n const p = taskPending.get(msg.runId);\n if (p) {\n taskPending.delete(msg.runId);\n p.resolve(msg.value);\n }\n return;\n }\n case 'task:error': {\n const p = taskPending.get(msg.runId);\n if (p) {\n taskPending.delete(msg.runId);\n p.reject(deserializeError(msg.error));\n }\n return;\n }\n case 'task:aborted':\n taskPending.delete(msg.runId);\n return;\n default:\n for (const h of storeHandlers) h(msg);\n }\n };\n\n const handleCrash = (): void => {\n if (destroyed) return;\n crashed = true;\n connected.set(false);\n for (const [, p] of taskPending) p.reject(new WorkerCrashedError());\n taskPending.clear();\n for (const fn of disconnectHooks) fn();\n if (restart === 'manual') return;\n const delay = restartDelay(attempt++);\n setTimeout(() => {\n if (!destroyed) openPort();\n }, delay);\n };\n\n const openPort = (): void => {\n if (port) closePort(port);\n port = spawn();\n port.onmessage = (ev) => onMessage(ev.data as WorkerEnvelope);\n // crash detection via property setters, not addEventListener (perturbs node MessagePort delivery)\n const evt = port as unknown as {\n onerror?: ((e?: unknown) => void) | null;\n onmessageerror?: ((e?: unknown) => void) | null;\n };\n if ('onerror' in evt) evt.onerror = handleCrash;\n if ('onmessageerror' in evt) evt.onmessageerror = handleCrash;\n send({ type: 'hello', proto: 1, clientId });\n };\n\n if (!isServer) openPort();\n\n inject(DestroyRef).onDestroy(() => teardown());\n\n const teardown = (): void => {\n destroyed = true;\n connected.set(false);\n for (const [, p] of taskPending) p.reject(new WorkerAbortError('worker connection destroyed'));\n taskPending.clear();\n for (const fn of disconnectHooks) fn();\n if (port) closePort(port);\n };\n\n return {\n connected,\n manifest,\n clientId,\n runTask(\n task: string,\n input: unknown,\n o?: { signal?: AbortSignal; transfer?: Transferable[] },\n ): Promise<unknown> {\n if (isServer) return Promise.reject(new WorkerCrashedError('no worker on the server'));\n if (destroyed) return Promise.reject(new WorkerAbortError('worker connection destroyed'));\n if (crashed) return Promise.reject(new WorkerCrashedError('worker is restarting'));\n if (o?.signal?.aborted) return Promise.reject(new WorkerAbortError());\n const runId = ++runIdSeq;\n return new Promise<unknown>((resolve, reject) => {\n taskPending.set(runId, { resolve, reject });\n send(\n { type: 'task:run', runId, task, input },\n o?.transfer ?? takeTransferables(input),\n );\n o?.signal?.addEventListener(\n 'abort',\n () => {\n if (!taskPending.has(runId)) return;\n taskPending.delete(runId);\n send({ type: 'task:abort', runId });\n reject(new WorkerAbortError());\n },\n { once: true },\n );\n });\n },\n destroy: teardown,\n _send: send,\n _subscribe(handler) {\n storeHandlers.add(handler);\n return () => storeHandlers.delete(handler);\n },\n _onReady(fn) {\n readyHooks.add(fn);\n return () => readyHooks.delete(fn);\n },\n _onDisconnect(fn) {\n disconnectHooks.add(fn);\n return () => disconnectHooks.delete(fn);\n },\n };\n}\n","import {\n computed,\n DestroyRef,\n effect,\n inject,\n Injector,\n PLATFORM_ID,\n runInInjectionContext,\n signal,\n untracked,\n type ResourceStatus,\n type Signal,\n type ValueEqualityFn,\n} from '@angular/core';\nimport { injectTransitionScope } from '@mmstack/primitives';\nimport { WorkerAbortError, type WorkerRef } from './connect-worker';\n\n/** Returned from a paused `params` fn to HOLD (keep the current value/status, run nothing). */\nexport const PAUSED: unique symbol = Symbol('@mmstack/worker:PAUSED');\n\nexport type WorkerRequestContext = { readonly paused: typeof PAUSED };\n\n/**\n * Reactive input producer. Reads signals to derive the task input; return `undefined` to disable\n * (idle, no run), or `ctx.paused` to hold. Re-runs when the returned input changes (by `Object.is`).\n */\nexport type WorkerParamsFn<TInput> = (\n ctx: WorkerRequestContext,\n) => TInput | undefined | void | typeof PAUSED;\n\nexport type WorkerResourceOptions<TResult> = {\n readonly injector?: Injector;\n /** Equality for the result value — a run resolving equal to the held value emits no notification. */\n readonly equal?: ValueEqualityFn<TResult>;\n readonly defaultValue?: TResult;\n /**\n * Hold the previous value through a re-run (status `'reloading'`) instead of clearing it. Default\n * TRUE — a `workerResource` is an async derivation, not a fetch; flashing empty mid-recompute is\n * rarely wanted.\n */\n readonly keepPrevious?: boolean;\n /** Register into the nearest transition scope: `'indicator'` drives pending, `'suspend'` also gates first paint. */\n readonly register?: false | 'indicator' | 'suspend';\n /** The connected worker whose host exposes the task. */\n readonly worker: WorkerRef;\n /** The name of the task to run (as declared in `createWorkerHost({ tasks })`). */\n readonly task: string;\n};\n\nexport type WorkerResourceRef<T> = {\n /** The latest successfully-computed value (held through re-runs per `keepPrevious`). */\n readonly value: Signal<T | undefined>;\n readonly status: Signal<ResourceStatus>;\n readonly error: Signal<unknown>;\n readonly isLoading: Signal<boolean>;\n hasValue(): boolean;\n /** Re-run with the current input, bypassing input de-duplication. */\n reload(): void;\n /** Cancel the in-flight run; the value is KEPT and status becomes `'local'`. */\n abort(): void;\n destroy(): void;\n};\n\n/**\n * Runs a named task on a connected worker, exposed with the standard resource surface so heavy\n * compute makes the UI *pending*, not *frozen*. `params` reactively derives the input; latest-wins\n * (a changed input supersedes and aborts the in-flight run); the result surfaces as\n * `value`/`status`/`error`, and — with `register` — participates in transition scopes. No-ops on the\n * server.\n */\nexport function workerResource<TInput, TResult>(\n params: WorkerParamsFn<TInput>,\n options: WorkerResourceOptions<TResult>,\n): WorkerResourceRef<TResult> {\n const injector = options.injector ?? inject(Injector);\n return runInInjectionContext(injector, () =>\n build<TInput, TResult>(params, options),\n );\n}\n\nfunction build<TInput, TResult>(\n params: WorkerParamsFn<TInput>,\n options: WorkerResourceOptions<TResult>,\n): WorkerResourceRef<TResult> {\n const isServer = inject(PLATFORM_ID) === 'server';\n const keepPrevious = options.keepPrevious ?? true;\n const userEqual = options.equal;\n const { worker, task } = options;\n\n const runTask = (input: TInput, signal: AbortSignal): Promise<TResult> =>\n worker.runTask(task, input, { signal }) as Promise<TResult>;\n\n const value = signal<TResult | undefined>(options.defaultValue, {\n equal: userEqual\n ? (a, b) =>\n a === undefined || b === undefined ? a === b : userEqual(a, b)\n : undefined,\n });\n const status = signal<ResourceStatus>('idle');\n const error = signal<unknown>(undefined);\n const isLoading = computed(() => {\n const s = status();\n return s === 'loading' || s === 'reloading';\n });\n const hasValue = () => value() !== undefined;\n\n let epoch = 0;\n let inFlight: AbortController | null = null;\n let lastInput: TInput | undefined;\n let hasRun = false;\n\n const start = (input: TInput): void => {\n const myEpoch = ++epoch;\n inFlight?.abort();\n const ac = new AbortController();\n inFlight = ac;\n lastInput = input;\n hasRun = true;\n status.set(keepPrevious && value() !== undefined ? 'reloading' : 'loading');\n error.set(undefined);\n\n runTask(input, ac.signal).then(\n (result) => {\n if (inFlight === ac) inFlight = null;\n if (myEpoch !== epoch) return;\n value.set(result);\n status.set('resolved');\n },\n (err) => {\n if (inFlight === ac) inFlight = null;\n if (myEpoch !== epoch) return;\n if (\n err instanceof WorkerAbortError ||\n (err as { name?: string })?.name === 'AbortError'\n )\n return;\n error.set(err);\n status.set('error');\n },\n );\n };\n\n const input = computed(() => params({ paused: PAUSED }));\n\n const ref = effect(() => {\n const i = input();\n if (isServer || i === PAUSED) return;\n if (i === undefined) return;\n if (Object.is(i, lastInput) && hasRun && untracked(status) !== 'error')\n return;\n untracked(() => start(i as TInput));\n });\n\n inject(DestroyRef).onDestroy(() => {\n epoch++;\n inFlight?.abort();\n });\n\n const self: WorkerResourceRef<TResult> = {\n value,\n status,\n error,\n isLoading,\n hasValue,\n reload: () => {\n const i = untracked(input);\n if (isServer || i === PAUSED || i === undefined) return;\n start(i as TInput);\n },\n abort: () => {\n if (!inFlight) return;\n epoch++;\n inFlight.abort();\n inFlight = null;\n status.set('local');\n },\n destroy: () => {\n epoch++;\n inFlight?.abort();\n ref.destroy();\n },\n };\n\n if (options.register) {\n const scope = injectTransitionScope();\n scope.add(self, { suspends: options.register === 'suspend' });\n let removed = false;\n const remove = () => {\n if (removed) return;\n removed = true;\n scope.remove(self);\n };\n inject(DestroyRef).onDestroy(remove);\n const destroy = self.destroy;\n self.destroy = () => {\n remove();\n destroy();\n };\n }\n\n return self;\n}\n","import {\n computed,\n DestroyRef,\n inject,\n Injector,\n runInInjectionContext,\n signal,\n untracked,\n type ResourceStatus,\n type Signal,\n type WritableSignal,\n} from '@angular/core';\nimport {\n injectTransitionScope,\n opLog,\n toStore,\n type SignalStore,\n type StoreOp,\n type WritableSignalStore,\n} from '@mmstack/primitives';\nimport {\n deserializeError,\n type IsWritableKey,\n type StoreKeys,\n type StoreValueOf,\n type WorkerEnvelope,\n type WorkerSchema,\n} from '@mmstack/worker/protocol';\nimport {\n WorkerAbortError,\n WorkerCrashedError,\n type WorkerRef,\n} from './connect-worker';\n\nexport type WorkerStoreOptions<T> = {\n readonly injector?: Injector;\n /** Value the replica holds before the first snapshot arrives. */\n readonly defaultValue?: T;\n /** Register into the nearest transition scope while hydrating/reloading. */\n readonly register?: false | 'indicator' | 'suspend';\n};\n\n/** The write path — present only on OWNED (writable) subtrees; absent on published (read-only) ones. */\nexport type WorkerStoreWrite<T> = {\n /**\n * Route a write to the OWNER: `recipe` applies optimistically to the store, its diff ships as ops,\n * and the owner sequences and re-emits it. Resolves once that authoritative batch reconciles this\n * replica. To hide the value until the owner confirms it, fork the store and reveal on resolve.\n * Rejects if the owner reports a write error.\n */\n write(recipe: (draft: WritableSignalStore<T>) => void): Promise<void>;\n};\n\nexport type WorkerStoreRef<T, W extends boolean = true> = {\n /**\n * The live store. For an OWNED subtree it is a full {@link WritableSignalStore}: writes apply\n * optimistically and route to the owner, and other op-log readers (`meshSync`, `persist`) can\n * attach to it. For a PUBLISHED subtree it is a read-only {@link SignalStore} mirror.\n */\n readonly store: W extends true ? WritableSignalStore<T> : SignalStore<T>;\n /** The replica root value (undefined before hydration). */\n readonly value: Signal<T | undefined>;\n readonly status: Signal<ResourceStatus>;\n readonly error: Signal<unknown>;\n readonly isLoading: Signal<boolean>;\n /** The underlying worker connection's liveness. */\n readonly connected: Signal<boolean>;\n hasValue(): boolean;\n destroy(): void;\n} & (W extends true ? WorkerStoreWrite<T> : object);\n\n/**\n * A live replica of a store subtree OWNED by the worker. Subscribes over the {@link WorkerRef},\n * hydrates from the owner's snapshot, then folds each authoritative op batch into a main-thread\n * `store` through an echo-free `opLog.apply`. For an owned subtree the store is WRITABLE: a write\n * applies optimistically and its diff routes to the owner (which reconciles it back), and any\n * op-log reader — `meshSync`, `persist` — can attach to the same store, so a persisted, meshed,\n * worker-owned graph is just extra readers. Satisfies `ResourceLike`/`UseSource`, so it participates\n * in transition scopes and nests in `latest()`/`use()`.\n */\nexport function workerStore<M extends WorkerSchema, K extends StoreKeys<M>>(\n worker: WorkerRef<M>,\n key: K,\n opt?: WorkerStoreOptions<StoreValueOf<M, K> & object>,\n): WorkerStoreRef<StoreValueOf<M, K> & object, IsWritableKey<M, K>>;\nexport function workerStore<T extends object>(\n worker: WorkerRef,\n key: string,\n opt?: WorkerStoreOptions<T>,\n): WorkerStoreRef<T, true>;\nexport function workerStore(\n worker: WorkerRef,\n key: string,\n opt?: WorkerStoreOptions<any>,\n): WorkerStoreRef<any, boolean> {\n const injector = opt?.injector ?? inject(Injector);\n return runInInjectionContext(injector, () => build(worker, key, opt));\n}\n\nfunction build<T extends object>(\n worker: WorkerRef,\n key: string,\n opt?: WorkerStoreOptions<T>,\n): WorkerStoreRef<T, true> {\n const injector = inject(Injector);\n const root = signal<T | undefined>(opt?.defaultValue);\n const s = toStore(root as unknown as WritableSignal<T>, {\n injector,\n }) as unknown as WritableSignalStore<T>;\n const log = opLog(root as unknown as WritableSignal<T>, { injector });\n\n const status = signal<ResourceStatus>('loading');\n const error = signal<unknown>(undefined);\n const isLoading = computed(() => {\n const st = status();\n return st === 'loading' || st === 'reloading';\n });\n let hasSnapshot = false;\n let resyncing = false;\n let lastVersion = 0;\n let writeSeq = 0;\n const writePending = new Map<\n number,\n {\n version: number | null;\n resolve?: () => void;\n reject?: (e: unknown) => void;\n }\n >();\n\n const hasValue = () => hasSnapshot;\n\n const isOwned = () => worker.manifest()?.stores.includes(key) ?? false;\n\n const settleWrites = () => {\n for (const [id, p] of writePending) {\n if (p.version !== null && lastVersion >= p.version) {\n writePending.delete(id);\n p.resolve?.();\n }\n }\n };\n\n const shipLocal = (ops: readonly StoreOp[]): void => {\n if (!ops.length || !isOwned() || !untracked(worker.connected)) return;\n const id = ++writeSeq;\n writePending.set(id, { version: null });\n worker._send({\n type: 'store:write',\n store: key,\n writeId: id,\n clientId: worker.clientId,\n ops,\n });\n };\n const shipUnsub = log.subscribe((batch) => shipLocal(batch.ops));\n\n const onStoreMessage = (msg: WorkerEnvelope): void => {\n if (!('store' in msg) || msg.store !== key) return;\n switch (msg.type) {\n case 'store:snapshot': {\n log.apply([{ kind: 'set', path: [], next: msg.value }]);\n lastVersion = msg.version;\n hasSnapshot = true;\n resyncing = false;\n status.set('resolved');\n error.set(undefined);\n settleWrites();\n return;\n }\n case 'store:ops': {\n if (!hasSnapshot || resyncing) return;\n if (msg.batch.version <= lastVersion) return;\n if (msg.batch.version > lastVersion + 1) {\n resync(); // gap: re-hydrate rather than apply out of order\n return;\n }\n log.apply(msg.batch.ops);\n lastVersion = msg.batch.version;\n settleWrites();\n return;\n }\n case 'store:write:ack': {\n const p = writePending.get(msg.writeId);\n if (!p) return;\n if (lastVersion >= msg.version) {\n writePending.delete(msg.writeId);\n p.resolve?.();\n } else {\n p.version = msg.version;\n }\n return;\n }\n case 'store:write:error': {\n const p = writePending.get(msg.writeId);\n if (p) {\n writePending.delete(msg.writeId);\n p.reject?.(deserializeError(msg.error));\n }\n return;\n }\n case 'store:status': {\n switch (msg.status) {\n case 'error':\n error.set(\n msg.error\n ? deserializeError(msg.error)\n : new Error('remote computation error'),\n );\n status.set('error');\n return;\n case 'loading':\n case 'reloading':\n error.set(undefined);\n status.set(hasSnapshot ? 'reloading' : 'loading');\n return;\n case 'resolved':\n error.set(undefined);\n if (hasSnapshot) status.set('resolved');\n return;\n default:\n return;\n }\n }\n }\n };\n\n const subscribe = (): void => {\n if (hasSnapshot) status.set('reloading');\n worker._send({\n type: 'store:subscribe',\n store: key,\n clientId: worker.clientId,\n });\n };\n\n const resync = (): void => {\n resyncing = true;\n status.set('reloading');\n worker._send({\n type: 'store:subscribe',\n store: key,\n clientId: worker.clientId,\n });\n };\n\n const unsub = worker._subscribe(onStoreMessage);\n const offReady = worker._onReady(subscribe);\n if (untracked(worker.connected)) subscribe(); // already up (created post-handshake): _onReady won't fire\n const offDisconnect = worker._onDisconnect(() => {\n for (const [, p] of writePending) p.reject?.(new WorkerCrashedError());\n writePending.clear();\n });\n\n const self: WorkerStoreRef<T> = {\n store: s,\n value: root.asReadonly(),\n status,\n error,\n isLoading,\n connected: worker.connected,\n hasValue,\n write: (recipe) => {\n const base = untracked(root);\n if (base === undefined)\n return Promise.reject(\n new Error(\n '[@mmstack/worker] cannot write before the replica has hydrated',\n ),\n );\n if (!untracked(worker.connected))\n return Promise.reject(\n new WorkerCrashedError('worker is not connected'),\n );\n if (!isOwned())\n return Promise.reject(\n new Error(`[@mmstack/worker] store is read-only (published): ${key}`),\n );\n recipe(s);\n const before = writeSeq;\n log.flush();\n if (writeSeq === before) return Promise.resolve();\n const writeId = writeSeq;\n return new Promise<void>((resolve, reject) => {\n const p = writePending.get(writeId);\n if (!p) return resolve();\n p.resolve = resolve;\n p.reject = reject;\n });\n },\n destroy: () => {\n shipUnsub();\n log.destroy();\n unsub();\n offReady();\n offDisconnect();\n for (const [, p] of writePending)\n p.reject?.(new WorkerAbortError('worker store destroyed'));\n writePending.clear();\n worker._send({\n type: 'store:unsubscribe',\n store: key,\n clientId: worker.clientId,\n });\n },\n };\n\n inject(DestroyRef).onDestroy(() => self.destroy());\n\n if (opt?.register) {\n const scope = injectTransitionScope();\n scope.add(self, { suspends: opt.register === 'suspend' });\n let removed = false;\n const remove = () => {\n if (removed) return;\n removed = true;\n scope.remove(self);\n };\n inject(DestroyRef).onDestroy(remove);\n const destroy = self.destroy;\n self.destroy = () => {\n remove();\n destroy();\n };\n }\n\n return self;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["build"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmstack/worker",
3
- "version": "21.0.0",
3
+ "version": "21.1.0",
4
4
  "keywords": [
5
5
  "angular",
6
6
  "signals",
@@ -19,7 +19,7 @@
19
19
  "peerDependencies": {
20
20
  "@angular/core": ">=21 <22",
21
21
  "@angular/common": ">=21 <22",
22
- "@mmstack/primitives": ">=21.5.1 <22"
22
+ "@mmstack/primitives": ">=21.6 <22"
23
23
  },
24
24
  "sideEffects": false,
25
25
  "module": "fesm2022/mmstack-worker.mjs",
@@ -24,7 +24,7 @@ type RemoteStatus = 'idle' | 'loading' | 'reloading' | 'resolved' | 'error';
24
24
  /**
25
25
  * Every message exchanged over a {@link WorkerPortLike}. One discriminated union shared by the
26
26
  * main-thread client and the worker host (and, later, `@mmstack/mesh`), so the two sides can never
27
- * drift. Grouped: lifecycle · tasks (rung 1) · stores (rung 2) · remote status (rung 3).
27
+ * drift.
28
28
  */
29
29
  type WorkerEnvelope = {
30
30
  type: 'hello';
@@ -1,7 +1,7 @@
1
1
  import { WorkerSchema, HasSchema, TaskInput, TaskOutput, WorkerEnvelope, WorkerPortLike, SchemaOf, StoreKeys, StoreValueOf, IsWritableKey } from '@mmstack/worker/protocol';
2
2
  export * from '@mmstack/worker/protocol';
3
3
  import { Injector, Signal, ValueEqualityFn, ResourceStatus } from '@angular/core';
4
- import { SignalStore, WritableSignalStore } from '@mmstack/primitives';
4
+ import { WritableSignalStore, SignalStore } from '@mmstack/primitives';
5
5
 
6
6
  /** What the worker advertised in its `ready` handshake. */
7
7
  type WorkerManifest = {
@@ -117,16 +117,20 @@ type WorkerStoreOptions<T> = {
117
117
  /** The write path — present only on OWNED (writable) subtrees; absent on published (read-only) ones. */
118
118
  type WorkerStoreWrite<T> = {
119
119
  /**
120
- * Route a write to the OWNER: run `recipe` against a writable draft of the current replica, diff
121
- * it to ops, ship them. Resolves once the owner's authoritative batch carrying this write has been
122
- * applied to THIS replica (honest async the value is not applied locally first; for optimistic
123
- * UI, fork the replica and reconcile on resolve). Rejects if the owner reports a write error.
120
+ * Route a write to the OWNER: `recipe` applies optimistically to the store, its diff ships as ops,
121
+ * and the owner sequences and re-emits it. Resolves once that authoritative batch reconciles this
122
+ * replica. To hide the value until the owner confirms it, fork the store and reveal on resolve.
123
+ * Rejects if the owner reports a write error.
124
124
  */
125
125
  write(recipe: (draft: WritableSignalStore<T>) => void): Promise<void>;
126
126
  };
127
127
  type WorkerStoreRef<T, W extends boolean = true> = {
128
- /** The live, READ-ONLY replica — deep per-leaf reads, mirrored from the worker-owned subtree. */
129
- readonly store: SignalStore<T>;
128
+ /**
129
+ * The live store. For an OWNED subtree it is a full {@link WritableSignalStore}: writes apply
130
+ * optimistically and route to the owner, and other op-log readers (`meshSync`, `persist`) can
131
+ * attach to it. For a PUBLISHED subtree it is a read-only {@link SignalStore} mirror.
132
+ */
133
+ readonly store: W extends true ? WritableSignalStore<T> : SignalStore<T>;
130
134
  /** The replica root value (undefined before hydration). */
131
135
  readonly value: Signal<T | undefined>;
132
136
  readonly status: Signal<ResourceStatus>;
@@ -138,11 +142,13 @@ type WorkerStoreRef<T, W extends boolean = true> = {
138
142
  destroy(): void;
139
143
  } & (W extends true ? WorkerStoreWrite<T> : object);
140
144
  /**
141
- * A live READ-ONLY replica of a store subtree OWNED by the worker. Subscribes over the
142
- * {@link WorkerRef}, hydrates from the owner's snapshot, then applies each authoritative op batch
143
- * into a main-thread `store` one `set` per batch, so a batch of N ops is a single notification
144
- * wave. Satisfies `ResourceLike`/`UseSource`, so it participates in transition scopes and nests in
145
- * `latest()`/`use()`. Writes are not local: they route to the owner (added in the next step).
145
+ * A live replica of a store subtree OWNED by the worker. Subscribes over the {@link WorkerRef},
146
+ * hydrates from the owner's snapshot, then folds each authoritative op batch into a main-thread
147
+ * `store` through an echo-free `opLog.apply`. For an owned subtree the store is WRITABLE: a write
148
+ * applies optimistically and its diff routes to the owner (which reconciles it back), and any
149
+ * op-log reader `meshSync`, `persist` can attach to the same store, so a persisted, meshed,
150
+ * worker-owned graph is just extra readers. Satisfies `ResourceLike`/`UseSource`, so it participates
151
+ * in transition scopes and nests in `latest()`/`use()`.
146
152
  */
147
153
  declare function workerStore<M extends WorkerSchema, K extends StoreKeys<M>>(worker: WorkerRef<M>, key: K, opt?: WorkerStoreOptions<StoreValueOf<M, K> & object>): WorkerStoreRef<StoreValueOf<M, K> & object, IsWritableKey<M, K>>;
148
154
  declare function workerStore<T extends object>(worker: WorkerRef, key: string, opt?: WorkerStoreOptions<T>): WorkerStoreRef<T, true>;