@mmstack/worker 22.0.0 → 22.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 {
@@ -45,16 +45,16 @@ function build$2(spawn, opt) {
45
45
  let attempt = 0;
46
46
  let runIdSeq = 0;
47
47
  let destroyed = false;
48
- let crashed = false; // between a crash and the next successful handshake
48
+ let crashed = false;
49
49
  let port = null;
50
50
  const send = (msg, transfer) => {
51
51
  if (port)
52
- port.postMessage(msg, transfer); // no-op on the server / between crash and respawn
52
+ port.postMessage(msg, transfer);
53
53
  };
54
54
  const onMessage = (msg) => {
55
55
  switch (msg.type) {
56
56
  case 'ready':
57
- attempt = 0; // a successful handshake resets the backoff
57
+ attempt = 0;
58
58
  crashed = false;
59
59
  manifest.set({
60
60
  hostId: msg.hostId,
@@ -83,10 +83,9 @@ function build$2(spawn, opt) {
83
83
  return;
84
84
  }
85
85
  case 'task:aborted':
86
- taskPending.delete(msg.runId); // caller already rejected at abort
86
+ taskPending.delete(msg.runId);
87
87
  return;
88
88
  default:
89
- // store:* traffic → replicas
90
89
  for (const h of storeHandlers)
91
90
  h(msg);
92
91
  }
@@ -96,7 +95,6 @@ function build$2(spawn, opt) {
96
95
  return;
97
96
  crashed = true;
98
97
  connected.set(false);
99
- // in-flight tasks can't survive a crash; store replicas hold their value and re-hydrate on reconnect
100
98
  for (const [, p] of taskPending)
101
99
  p.reject(new WorkerCrashedError());
102
100
  taskPending.clear();
@@ -111,14 +109,11 @@ function build$2(spawn, opt) {
111
109
  }, delay);
112
110
  };
113
111
  const openPort = () => {
114
- // terminate the previous worker before respawning, so a transient/manual restart never orphans
115
- // a live thread (a genuine crash already killed it; terminate() is then a harmless no-op)
116
112
  if (port)
117
113
  closePort(port);
118
114
  port = spawn();
119
115
  port.onmessage = (ev) => onMessage(ev.data);
120
- // duck-typed crash detection via property setters (NOT addEventListener that perturbs a node
121
- // MessagePort's onmessage delivery). A real `Worker` has onerror; onmessageerror where present.
116
+ // crash detection via property setters, not addEventListener (perturbs node MessagePort delivery)
122
117
  const evt = port;
123
118
  if ('onerror' in evt)
124
119
  evt.onerror = handleCrash;
@@ -126,7 +121,6 @@ function build$2(spawn, opt) {
126
121
  evt.onmessageerror = handleCrash;
127
122
  send({ type: 'hello', proto: 1, clientId });
128
123
  };
129
- // no workers on the server — connected stays false, runTask rejects, replicas render their default
130
124
  if (!isServer)
131
125
  openPort();
132
126
  inject(DestroyRef).onDestroy(() => teardown());
@@ -136,8 +130,6 @@ function build$2(spawn, opt) {
136
130
  for (const [, p] of taskPending)
137
131
  p.reject(new WorkerAbortError('worker connection destroyed'));
138
132
  taskPending.clear();
139
- // same contract as a crash: anything pending on the connection (replica writes) settles NOW
140
- // rather than dangling — a destroyed port can never deliver the echo that would resolve them
141
133
  for (const fn of disconnectHooks)
142
134
  fn();
143
135
  if (port)
@@ -152,9 +144,6 @@ function build$2(spawn, opt) {
152
144
  return Promise.reject(new WorkerCrashedError('no worker on the server'));
153
145
  if (destroyed)
154
146
  return Promise.reject(new WorkerAbortError('worker connection destroyed'));
155
- // between a crash and the respawned handshake the port is dead — a message posted into it
156
- // vanishes and the promise would never settle. Reject honestly; the caller retries/reloads.
157
- // (Before the FIRST handshake this is false: a starting worker buffers messages, so we send.)
158
147
  if (crashed)
159
148
  return Promise.reject(new WorkerCrashedError('worker is restarting'));
160
149
  if (o?.signal?.aborted)
@@ -238,7 +227,7 @@ function build$1(params, options) {
238
227
  if (inFlight === ac)
239
228
  inFlight = null;
240
229
  if (myEpoch !== epoch)
241
- return; // superseded — discard
230
+ return;
242
231
  value.set(result);
243
232
  status.set('resolved');
244
233
  }, (err) => {
@@ -257,10 +246,9 @@ function build$1(params, options) {
257
246
  const ref = effect(() => {
258
247
  const i = input();
259
248
  if (isServer || i === PAUSED)
260
- return; // server: never run · paused: hold
249
+ return;
261
250
  if (i === undefined)
262
- return; // disabled — keep current value/status
263
- // dedup: a resume to the same input (e.g. after a pause) does not re-run
251
+ return;
264
252
  if (Object.is(i, lastInput) && hasRun && untracked(status) !== 'error')
265
253
  return;
266
254
  untracked(() => start(i));
@@ -284,11 +272,11 @@ function build$1(params, options) {
284
272
  },
285
273
  abort: () => {
286
274
  if (!inFlight)
287
- return; // nothing in flight (a scope's abortPending must not disturb a settled value)
275
+ return;
288
276
  epoch++;
289
277
  inFlight.abort();
290
278
  inFlight = null;
291
- status.set('local'); // value kept
279
+ status.set('local');
292
280
  },
293
281
  destroy: () => {
294
282
  epoch++;
@@ -299,7 +287,19 @@ function build$1(params, options) {
299
287
  if (options.register) {
300
288
  const scope = injectTransitionScope();
301
289
  scope.add(self, { suspends: options.register === 'suspend' });
302
- inject(DestroyRef).onDestroy(() => scope.remove(self));
290
+ let removed = false;
291
+ const remove = () => {
292
+ if (removed)
293
+ return;
294
+ removed = true;
295
+ scope.remove(self);
296
+ };
297
+ inject(DestroyRef).onDestroy(remove);
298
+ const destroy = self.destroy;
299
+ self.destroy = () => {
300
+ remove();
301
+ destroy();
302
+ };
303
303
  }
304
304
  return self;
305
305
  }
@@ -309,46 +309,60 @@ function workerStore(worker, key, opt) {
309
309
  return runInInjectionContext(injector, () => build(worker, key, opt));
310
310
  }
311
311
  function build(worker, key, opt) {
312
- const injector = inject(Injector); // captured for use in write() (called outside injection context)
312
+ const injector = inject(Injector);
313
313
  const root = signal(opt?.defaultValue, /* @ts-ignore */
314
314
  ...(ngDevMode ? [{ debugName: "root" }] : /* istanbul ignore next */ []));
315
- // shape-adaptive: the store reflects `root` even as it goes undefined → object on first snapshot
316
- const replica = toStore(root, {
315
+ const s = toStore(root, {
317
316
  injector,
318
317
  });
318
+ const log = opLog(root, { injector });
319
319
  const status = signal('loading', /* @ts-ignore */
320
320
  ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
321
321
  const error = signal(undefined, /* @ts-ignore */
322
322
  ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
323
323
  const isLoading = computed(() => {
324
- const s = status();
325
- return s === 'loading' || s === 'reloading';
324
+ const st = status();
325
+ return st === 'loading' || st === 'reloading';
326
326
  }, /* @ts-ignore */
327
327
  ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
328
328
  let hasSnapshot = false;
329
- let resyncing = false; // awaiting a fresh snapshot after a detected gap
329
+ let resyncing = false;
330
330
  let lastVersion = 0;
331
331
  let writeSeq = 0;
332
332
  const writePending = new Map();
333
333
  const hasValue = () => hasSnapshot;
334
- // resolve any write whose acked authoritative version has now been applied to this replica
334
+ const isOwned = () => worker.manifest()?.stores.includes(key) ?? false;
335
335
  const settleWrites = () => {
336
336
  for (const [id, p] of writePending) {
337
337
  if (p.version !== null && lastVersion >= p.version) {
338
338
  writePending.delete(id);
339
- p.resolve();
339
+ p.resolve?.();
340
340
  }
341
341
  }
342
342
  };
343
+ const shipLocal = (ops) => {
344
+ if (!ops.length || !isOwned() || !untracked(worker.connected))
345
+ return;
346
+ const id = ++writeSeq;
347
+ writePending.set(id, { version: null });
348
+ worker._send({
349
+ type: 'store:write',
350
+ store: key,
351
+ writeId: id,
352
+ clientId: worker.clientId,
353
+ ops,
354
+ });
355
+ };
356
+ const shipUnsub = log.subscribe((batch) => shipLocal(batch.ops));
343
357
  const onStoreMessage = (msg) => {
344
358
  if (!('store' in msg) || msg.store !== key)
345
359
  return;
346
360
  switch (msg.type) {
347
361
  case 'store:snapshot': {
348
- root.set(msg.value);
362
+ log.apply([{ kind: 'set', path: [], next: msg.value }]);
349
363
  lastVersion = msg.version;
350
364
  hasSnapshot = true;
351
- resyncing = false; // a fresh snapshot ends any resync
365
+ resyncing = false;
352
366
  status.set('resolved');
353
367
  error.set(undefined);
354
368
  settleWrites();
@@ -356,15 +370,14 @@ function build(worker, key, opt) {
356
370
  }
357
371
  case 'store:ops': {
358
372
  if (!hasSnapshot || resyncing)
359
- return; // pre-hydration, or dropping until a fresh snapshot
373
+ return;
360
374
  if (msg.batch.version <= lastVersion)
361
- return; // stale/duplicate (at-least-once transport)
375
+ return;
362
376
  if (msg.batch.version > lastVersion + 1) {
363
- // GAP: a batch was lost — re-hydrate from a fresh snapshot rather than apply out of order
364
- resync();
377
+ resync(); // gap: re-hydrate rather than apply out of order
365
378
  return;
366
379
  }
367
- root.set(applyOps(untracked(root), msg.batch.ops));
380
+ log.apply(msg.batch.ops);
368
381
  lastVersion = msg.batch.version;
369
382
  settleWrites();
370
383
  return;
@@ -375,10 +388,10 @@ function build(worker, key, opt) {
375
388
  return;
376
389
  if (lastVersion >= msg.version) {
377
390
  writePending.delete(msg.writeId);
378
- p.resolve(); // the authoritative batch already landed (FIFO: ops precede the ack)
391
+ p.resolve?.();
379
392
  }
380
393
  else {
381
- p.version = msg.version; // resolve once a batch reaches this version
394
+ p.version = msg.version;
382
395
  }
383
396
  return;
384
397
  }
@@ -386,13 +399,11 @@ function build(worker, key, opt) {
386
399
  const p = writePending.get(msg.writeId);
387
400
  if (p) {
388
401
  writePending.delete(msg.writeId);
389
- p.reject(deserializeError(msg.error));
402
+ p.reject?.(deserializeError(msg.error));
390
403
  }
391
404
  return;
392
405
  }
393
406
  case 'store:status': {
394
- // rung 3: a published derivation's remote status → this replica's status, so an in-flight
395
- // worker computation shows as pending on the main thread (holding the last value)
396
407
  switch (msg.status) {
397
408
  case 'error':
398
409
  error.set(msg.error ? deserializeError(msg.error) : new Error('remote computation error'));
@@ -401,7 +412,7 @@ function build(worker, key, opt) {
401
412
  case 'loading':
402
413
  case 'reloading':
403
414
  error.set(undefined);
404
- status.set(hasSnapshot ? 'reloading' : 'loading'); // hold the value while recomputing
415
+ status.set(hasSnapshot ? 'reloading' : 'loading');
405
416
  return;
406
417
  case 'resolved':
407
418
  error.set(undefined);
@@ -416,29 +427,25 @@ function build(worker, key, opt) {
416
427
  };
417
428
  const subscribe = () => {
418
429
  if (hasSnapshot)
419
- status.set('reloading'); // holding stale while the fresh snapshot lands
430
+ status.set('reloading');
420
431
  worker._send({ type: 'store:subscribe', store: key, clientId: worker.clientId });
421
432
  };
422
- // a lost batch left a version gap — hold the stale value and re-subscribe for a fresh snapshot
423
433
  const resync = () => {
424
434
  resyncing = true;
425
435
  status.set('reloading');
426
436
  worker._send({ type: 'store:subscribe', store: key, clientId: worker.clientId });
427
437
  };
428
438
  const unsub = worker._subscribe(onStoreMessage);
429
- // subscribe on each `ready` — the first connection and every auto-restart re-hydration; if the
430
- // connection is ALREADY up (created post-handshake), `_onReady` won't fire, so subscribe now
431
439
  const offReady = worker._onReady(subscribe);
432
440
  if (untracked(worker.connected))
433
- subscribe();
434
- // 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
435
442
  const offDisconnect = worker._onDisconnect(() => {
436
443
  for (const [, p] of writePending)
437
- p.reject(new WorkerCrashedError());
444
+ p.reject?.(new WorkerCrashedError());
438
445
  writePending.clear();
439
446
  });
440
447
  const self = {
441
- store: replica,
448
+ store: s,
442
449
  value: root.asReadonly(),
443
450
  status,
444
451
  error,
@@ -449,40 +456,32 @@ function build(worker, key, opt) {
449
456
  const base = untracked(root);
450
457
  if (base === undefined)
451
458
  return Promise.reject(new Error('[@mmstack/worker] cannot write before the replica has hydrated'));
452
- // hydrated but disconnected = the worker crashed (or is restarting) — a write posted now
453
- // vanishes into a dead port and its echo can never arrive. Reject; the caller retries.
454
459
  if (!untracked(worker.connected))
455
460
  return Promise.reject(new WorkerCrashedError('worker is not connected'));
456
- // fork-diff: a scratch store seeded with the CURRENT value (same ref → copy-on-write keeps
457
- // untouched subtrees shared), so diffOps against the base is O(changed paths)
458
- const scratch = signal(base, /* @ts-ignore */
459
- ...(ngDevMode ? [{ debugName: "scratch" }] : /* istanbul ignore next */ []));
460
- const scratchStore = toStore(scratch, {
461
- injector,
462
- });
463
- recipe(scratchStore);
464
- const ops = diffOps(base, untracked(scratch));
465
- 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)
466
467
  return Promise.resolve();
467
- const writeId = ++writeSeq;
468
+ const writeId = writeSeq;
468
469
  return new Promise((resolve, reject) => {
469
- writePending.set(writeId, { resolve, reject, version: null });
470
- worker._send({
471
- type: 'store:write',
472
- store: key,
473
- writeId,
474
- clientId: worker.clientId,
475
- ops,
476
- });
470
+ const p = writePending.get(writeId);
471
+ if (!p)
472
+ return resolve();
473
+ p.resolve = resolve;
474
+ p.reject = reject;
477
475
  });
478
476
  },
479
477
  destroy: () => {
478
+ shipUnsub();
479
+ log.destroy();
480
480
  unsub();
481
481
  offReady();
482
482
  offDisconnect();
483
- // acks can no longer be received — settle any in-flight write instead of dangling it
484
483
  for (const [, p] of writePending)
485
- p.reject(new WorkerAbortError('worker store destroyed'));
484
+ p.reject?.(new WorkerAbortError('worker store destroyed'));
486
485
  writePending.clear();
487
486
  worker._send({ type: 'store:unsubscribe', store: key, clientId: worker.clientId });
488
487
  },
@@ -491,13 +490,23 @@ function build(worker, key, opt) {
491
490
  if (opt?.register) {
492
491
  const scope = injectTransitionScope();
493
492
  scope.add(self, { suspends: opt.register === 'suspend' });
494
- inject(DestroyRef).onDestroy(() => scope.remove(self));
493
+ let removed = false;
494
+ const remove = () => {
495
+ if (removed)
496
+ return;
497
+ removed = true;
498
+ scope.remove(self);
499
+ };
500
+ inject(DestroyRef).onDestroy(remove);
501
+ const destroy = self.destroy;
502
+ self.destroy = () => {
503
+ remove();
504
+ destroy();
505
+ };
495
506
  }
496
507
  return self;
497
508
  }
498
509
 
499
- // shared wire types, re-exported so main-thread consumers import only from '@mmstack/worker'
500
-
501
510
  /**
502
511
  * Generated bundle index. Do not edit.
503
512
  */
@@ -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, () => 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;\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;\n if (i === undefined) return;\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;\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 { version: number | null; resolve?: () => void; reject?: (e: unknown) => void }\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(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');\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({ type: 'store:subscribe', store: key, clientId: worker.clientId });\n };\n\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 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('[@mmstack/worker] cannot write before the replica has hydrated'),\n );\n if (!untracked(worker.connected))\n return Promise.reject(new WorkerCrashedError('worker is not connected'));\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({ 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 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": "22.0.0",
3
+ "version": "22.1.0",
4
4
  "keywords": [
5
5
  "angular",
6
6
  "signals",
@@ -19,7 +19,7 @@
19
19
  "peerDependencies": {
20
20
  "@angular/core": ">=22 <23",
21
21
  "@angular/common": ">=22 <23",
22
- "@mmstack/primitives": ">=22.5.1 <23"
22
+ "@mmstack/primitives": ">=22.6 <23"
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>;