@mmstack/worker 21.1.0 → 21.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -17
- package/fesm2022/mmstack-worker-host.mjs +38 -53
- package/fesm2022/mmstack-worker-host.mjs.map +1 -1
- package/fesm2022/mmstack-worker-protocol.mjs.map +1 -1
- package/fesm2022/mmstack-worker.mjs +74 -73
- package/fesm2022/mmstack-worker.mjs.map +1 -1
- package/package.json +2 -2
- package/types/mmstack-worker-host.d.ts +7 -0
- package/types/mmstack-worker-protocol.d.ts +5 -22
- package/types/mmstack-worker.d.ts +12 -11
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Built on the store op-log from [`@mmstack/primitives`](https://www.npmjs.com/pac
|
|
|
12
12
|
|
|
13
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.
|
|
14
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.
|
|
15
|
-
- **
|
|
15
|
+
- **One owner, provable convergence.** Each store subtree has exactly one owner (the worker). Writes apply optimistically on the main thread and route to that owner, which folds them in and echoes them back. The main thread and the worker run the same convergent sync the mesh uses, so interleaved writes from several components land on identical state, and an authoritative owner correction wins wherever it meets a concurrent write.
|
|
16
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.
|
|
17
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.
|
|
18
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.
|
|
@@ -250,24 +250,20 @@ await todos.write((draft) => {
|
|
|
250
250
|
});
|
|
251
251
|
```
|
|
252
252
|
|
|
253
|
-
`write` runs your recipe against
|
|
253
|
+
`write` runs your recipe against the replica right away, diffs it to minimal ops, and ships them to the owner. The owner folds them in and echoes them back; the promise resolves once that echo lands. The value is on screen immediately, and the round trip is honest in the API rather than hidden behind a `set` you cannot read back.
|
|
254
254
|
|
|
255
|
-
###
|
|
255
|
+
### Hide until confirmed (opt-in)
|
|
256
256
|
|
|
257
|
-
|
|
257
|
+
Optimistic is the default because it matches how the rest of the stack syncs. When you would rather not show a value until the owner accepts it (say the owner can correct or reject the write), fork the store, write to the fork, and reveal it only when the promise resolves:
|
|
258
258
|
|
|
259
259
|
```ts
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
await todos.write((d) => d.set(next));
|
|
268
|
-
} finally {
|
|
269
|
-
optimistic.set(null); // the owner's authoritative batch has landed on the replica
|
|
270
|
-
}
|
|
260
|
+
const draft = forkStore(todos.store);
|
|
261
|
+
draft.store.status.set('done');
|
|
262
|
+
try {
|
|
263
|
+
await todos.write((d) => d.status.set('done'));
|
|
264
|
+
draft.commit(); // accepted: reveal
|
|
265
|
+
} catch {
|
|
266
|
+
draft.discard(); // rejected: drop it
|
|
271
267
|
}
|
|
272
268
|
```
|
|
273
269
|
|
|
@@ -307,9 +303,9 @@ workerResource(() => transfer({ buffer }, [buffer]), { worker, task: 'process' }
|
|
|
307
303
|
|
|
308
304
|
## How it works
|
|
309
305
|
|
|
310
|
-
|
|
306
|
+
The main thread and the worker run the same convergent sync as the mesh (`opSync` from `@mmstack/primitives`) over a `MessageChannel`. Each owned store on the worker runs the owner side; every replica applies incoming envelopes in a single `set` (one notification wave, regardless of how many ops an envelope carries). The first join hydrates from a checkpoint, everything after is a minimal delta, and a version gap re-hydrates.
|
|
311
307
|
|
|
312
|
-
|
|
308
|
+
A write applies optimistically on the writer and routes to the owner, which folds it in and echoes it to every replica; the writer's replica reads its own echo as the acknowledgement. Because it is the same convergent register the mesh uses, interleaved writes from several components land on the same value on every replica. The owner can also correct a value authoritatively, and that correction wins wherever it meets a concurrent write, so a single owner keeps the last word over its subtree without a separate request protocol.
|
|
313
309
|
|
|
314
310
|
The protocol rides a minimal structural port (`postMessage` / `onmessage`), so a `MessageChannel` pair works in tests and the same envelope stream can later run over other transports.
|
|
315
311
|
|
|
@@ -2,7 +2,7 @@ import { generateId, serializeError, PROTO_VERSION } from '@mmstack/worker/proto
|
|
|
2
2
|
export * from '@mmstack/worker/protocol';
|
|
3
3
|
import { isDevMode, untracked } from '@angular/core';
|
|
4
4
|
import { createWatch } from '@angular/core/primitives/signals';
|
|
5
|
-
import {
|
|
5
|
+
import { opSync, createHlcClock, createStoreContext } from '@mmstack/primitives';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* An injector-free {@link OpLogDriver} for `@mmstack/primitives` `opLog`. Schedules the emission
|
|
@@ -89,25 +89,27 @@ function createWorkerHost(options) {
|
|
|
89
89
|
conn.port.postMessage(message);
|
|
90
90
|
};
|
|
91
91
|
const observe = (key, src, writable) => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
// Each subtree runs the owner's opSync (a real origin + HLC): its batches are real envelopes,
|
|
93
|
+
// so the whole main<->worker sync is opSync over the MessageChannel transport, no bespoke
|
|
94
|
+
// sequencer. Owner writes emit host-origin envelopes; received client writes fold in and are
|
|
95
|
+
// relayed on. Emission is driven off the microtask queue (no injector inside a worker).
|
|
96
|
+
const sync = opSync(src, {
|
|
97
|
+
writer: hostId,
|
|
98
|
+
origin: `${hostId}:${key}`,
|
|
99
|
+
driver: microtaskOpLogDriver(),
|
|
100
|
+
clock: createHlcClock(),
|
|
101
|
+
});
|
|
102
|
+
sync.subscribe((env) => {
|
|
103
|
+
devAssertCloneable(env, `store '${key}' update`);
|
|
104
|
+
fanout(key, { type: 'store:sync', store: key, env });
|
|
103
105
|
});
|
|
104
|
-
subtrees.set(key,
|
|
106
|
+
subtrees.set(key, { read: () => untracked(src), sync, writable });
|
|
105
107
|
};
|
|
106
108
|
for (const key of storeKeys)
|
|
107
|
-
observe(key, sources[key],
|
|
109
|
+
observe(key, sources[key], true);
|
|
108
110
|
for (const key of publishedKeys) {
|
|
109
111
|
const src = publishedSources[key];
|
|
110
|
-
observe(key, src,
|
|
112
|
+
observe(key, src, false);
|
|
111
113
|
const statusSig = src.status;
|
|
112
114
|
if (statusSig) {
|
|
113
115
|
publishedStatus.set(key, () => toRemoteStatus(untracked(statusSig)));
|
|
@@ -180,13 +182,13 @@ function createWorkerHost(options) {
|
|
|
180
182
|
if (!sub)
|
|
181
183
|
return;
|
|
182
184
|
conn.stores.add(msg.store);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
+
sub.sync.flush(); // settle any pending owner change into the register state first
|
|
186
|
+
const checkpoint = sub.sync.snapshot();
|
|
187
|
+
devAssertCloneable(checkpoint, `store '${msg.store}' checkpoint`);
|
|
185
188
|
conn.port.postMessage({
|
|
186
|
-
type: 'store:
|
|
189
|
+
type: 'store:checkpoint',
|
|
187
190
|
store: msg.store,
|
|
188
|
-
|
|
189
|
-
value: snapshot,
|
|
191
|
+
checkpoint,
|
|
190
192
|
});
|
|
191
193
|
const currentStatus = publishedStatus.get(msg.store);
|
|
192
194
|
if (currentStatus)
|
|
@@ -219,37 +221,15 @@ function createWorkerHost(options) {
|
|
|
219
221
|
conn.taskRuns.delete(msg.runId);
|
|
220
222
|
return;
|
|
221
223
|
}
|
|
222
|
-
case 'store:
|
|
224
|
+
case 'store:sync': {
|
|
225
|
+
// a routed client write: fold it into the owner store, then RELAY it to every subscriber
|
|
226
|
+
// (including the sender, whose replica reads its own echo as the write acknowledgement).
|
|
227
|
+
// Writes to a read-only (published) or unknown store are ignored: those never route.
|
|
223
228
|
const sub = subtrees.get(msg.store);
|
|
224
|
-
if (!sub || !sub.writable)
|
|
225
|
-
conn.port.postMessage({
|
|
226
|
-
type: 'store:write:error',
|
|
227
|
-
store: msg.store,
|
|
228
|
-
writeId: msg.writeId,
|
|
229
|
-
error: serializeError(new Error(sub
|
|
230
|
-
? `[@mmstack/worker] store is read-only (published): ${msg.store}`
|
|
231
|
-
: `[@mmstack/worker] unknown store: ${msg.store}`)),
|
|
232
|
-
});
|
|
229
|
+
if (!sub || !sub.writable)
|
|
233
230
|
return;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
sub.writable.set(applyOps(sub.read(), msg.ops));
|
|
237
|
-
sub.log.flush();
|
|
238
|
-
conn.port.postMessage({
|
|
239
|
-
type: 'store:write:ack',
|
|
240
|
-
store: msg.store,
|
|
241
|
-
writeId: msg.writeId,
|
|
242
|
-
version: sub.version,
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
catch (err) {
|
|
246
|
-
conn.port.postMessage({
|
|
247
|
-
type: 'store:write:error',
|
|
248
|
-
store: msg.store,
|
|
249
|
-
writeId: msg.writeId,
|
|
250
|
-
error: serializeError(err),
|
|
251
|
-
});
|
|
252
|
-
}
|
|
231
|
+
sub.sync.receive(msg.env);
|
|
232
|
+
fanout(msg.store, { type: 'store:sync', store: msg.store, env: msg.env });
|
|
253
233
|
return;
|
|
254
234
|
}
|
|
255
235
|
}
|
|
@@ -280,13 +260,18 @@ function createWorkerHost(options) {
|
|
|
280
260
|
return {
|
|
281
261
|
hostId,
|
|
282
262
|
connect,
|
|
263
|
+
override(store, fn) {
|
|
264
|
+
const sub = subtrees.get(store);
|
|
265
|
+
if (sub?.writable)
|
|
266
|
+
sub.sync.override(fn);
|
|
267
|
+
},
|
|
283
268
|
flush() {
|
|
284
|
-
for (const {
|
|
285
|
-
|
|
269
|
+
for (const { sync } of subtrees.values())
|
|
270
|
+
sync.flush();
|
|
286
271
|
},
|
|
287
272
|
dispose() {
|
|
288
|
-
for (const {
|
|
289
|
-
|
|
273
|
+
for (const { sync } of subtrees.values())
|
|
274
|
+
sync.destroy();
|
|
290
275
|
for (const w of statusWatches)
|
|
291
276
|
w.destroy();
|
|
292
277
|
connections.clear();
|
|
@@ -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/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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"}
|
|
1
|
+
{"version":3,"file":"mmstack-worker-host.mjs","sources":["../../../../packages/worker/host/src/lib/microtask-driver.ts","../../../../packages/worker/host/src/lib/create-worker-host.ts","../../../../packages/worker/host/src/lib/worker-store-context.ts","../../../../packages/worker/host/src/mmstack-worker-host.ts"],"sourcesContent":["import { createWatch } from '@angular/core/primitives/signals';\nimport type { OpLogDriver } from '@mmstack/primitives';\n\n/**\n * An injector-free {@link OpLogDriver} for `@mmstack/primitives` `opLog`. Schedules the emission\n * reaction on the microtask queue via `createWatch` (Angular's renderer-independent watch\n * primitive) instead of an `effect`, so an opLog can run where there is no Angular injector: the\n * worker side of the worker-graph, whose store has no application tick driving it.\n *\n * This lives in `@mmstack/worker/host` rather than in primitives on purpose. `createWatch` is a\n * lower-tier signals-primitives export; keeping it out of `@mmstack/primitives` lets that package\n * back-port to older Angular majors that may not export it. The worker package tracks a newer\n * Angular where `createWatch` is available.\n *\n * Batching is per-microtask rather than per-app-tick; the worker protocol orders by\n * `(origin, version)`, never tick alignment, so that is safe.\n */\nexport function microtaskOpLogDriver(): OpLogDriver {\n return (run) => {\n let scheduled = false;\n const watch = createWatch(\n () => run(),\n (w) => {\n if (scheduled) return;\n scheduled = true;\n queueMicrotask(() => {\n scheduled = false;\n w.run();\n });\n },\n false,\n );\n watch.notify();\n return { destroy: () => watch.destroy() };\n };\n}\n","import {\n isDevMode,\n untracked,\n type ResourceStatus,\n type Signal,\n type WritableSignal,\n} from '@angular/core';\nimport { createWatch } from '@angular/core/primitives/signals';\nimport { createHlcClock, opSync, type OpSync } from '@mmstack/primitives';\nimport {\n generateId,\n PROTO_VERSION,\n serializeError,\n type HasSchema,\n type SignalValueOf,\n type WorkerEnvelope,\n type WorkerPortLike,\n type WorkerSchema,\n} from '@mmstack/worker/protocol';\nimport { microtaskOpLogDriver } from './microtask-driver';\n\n/** A named unit of compute a worker exposes (rung 1). `ctx.signal` aborts when the caller cancels. */\nexport type WorkerTaskHandler<I = any, O = any> = (\n input: I,\n ctx: { readonly signal: AbortSignal },\n) => O | Promise<O>;\n\n/** A published derivation: a plain signal, or a status-bearing one (a `latest()`) for pending propagation. */\nexport type PublishedSource<T> =\n | Signal<T>\n | (Signal<T | undefined> & { readonly status: Signal<ResourceStatus> });\n\nexport type CreateWorkerHostOptions = {\n /**\n * Store subtrees this worker OWNS — the single writer. Keys are the wire identifiers. Typed as\n * the plain writable-signal interface (any copy-on-write `WritableSignal<object>` qualifies, per\n * `opLog`); a concrete `store<T>` satisfies it without the mapped-store variance friction that\n * `WritableSignalStore<any>` would introduce.\n */\n readonly stores?: Record<string, WritableSignal<any>>;\n /**\n * Read-only DERIVED subtrees the worker PUBLISHES (rung 3) — a `Signal<T>` (e.g. a `computed` or\n * `projection`) or a status-bearing async derivation (a `latest()`). Main-thread `workerStore`s\n * mirror them like owned stores but cannot write; a status-bearing entry also propagates its\n * pending/error to the replica, so an in-flight worker computation shows as pending on the main\n * thread.\n */\n readonly published?: Record<string, PublishedSource<any>>;\n /** Named tasks callable from the main thread (rung 1). */\n readonly tasks?: Record<string, WorkerTaskHandler>;\n /**\n * Transport to serve on. Defaults to the worker global scope (`self`) when running in a real\n * Worker, so `my.worker.ts` never touches `self`. Pass an explicit port for tests (a\n * `MessageChannel` port) or a SharedWorker fan-in.\n */\n readonly port?: WorkerPortLike;\n};\n\nexport type WorkerHost<M extends WorkerSchema = WorkerSchema> = HasSchema<M> & {\n /** This host's transport identity — the `origin` of every store batch it emits. */\n readonly hostId: string;\n /** Serve an additional transport (multi-client / tests). Returns a disconnect handle. */\n connect(port: WorkerPortLike): () => void;\n /**\n * Apply an AUTHORITATIVE owner correction to an owned store: writes made inside `fn` are stamped\n * at a bumped epoch, so they deterministically win the merge against any concurrent replica write\n * (owner authority rides the epoch fold). Readers can gate effects on owner-settled values by the\n * winning op's epoch. No-op for a read-only (published) or unknown store.\n */\n override(store: string, fn: () => void): void;\n /**\n * Synchronously emit any pending owned-store changes to subscribers NOW, instead of waiting for\n * the microtask driver. Makes the mirror deterministic (tests call it before asserting) and is\n * the honest settle point before applying a routed write (phase 4). No-op when nothing is pending.\n */\n flush(): void;\n /** Stop every owned opLog and drop all connections. */\n dispose(): void;\n};\n\ntype Connection = {\n readonly port: WorkerPortLike;\n clientId: string | null;\n readonly stores: Set<string>;\n readonly taskRuns: Map<number, AbortController>;\n};\n\ntype Subtree = {\n readonly read: () => unknown;\n readonly sync: OpSync<any>;\n readonly writable: boolean;\n};\n\nfunction toRemoteStatus(\n s: ResourceStatus,\n): 'idle' | 'loading' | 'reloading' | 'resolved' | 'error' {\n return s === 'idle' || s === 'loading' || s === 'reloading' || s === 'error'\n ? s\n : 'resolved';\n}\n\n// dev-only: catch a non-cloneable store value before postMessage throws a context-free DataCloneError\nfunction devAssertCloneable(value: unknown, what: string): void {\n if (!isDevMode()) return;\n try {\n structuredClone(value);\n } catch (err) {\n throw new Error(\n `[@mmstack/worker] ${what} holds a value that cannot be sent across the worker boundary. ` +\n `Synced state must be structured-clonable: functions, class instances, and opaque() values ` +\n `do not serialize. Keep it to plain JSON-like data.`,\n { cause: err },\n );\n }\n}\n\nfunction isWorkerGlobal(g: unknown): g is WorkerPortLike {\n const scope = (globalThis as { WorkerGlobalScope?: unknown })\n .WorkerGlobalScope;\n return (\n typeof scope === 'function' &&\n g instanceof (scope as new () => unknown) &&\n typeof (g as { postMessage?: unknown }).postMessage === 'function'\n );\n}\n\n/**\n * The worker-side runtime: owns store subtrees, hosts tasks, and mirrors owned state to every\n * connected client as op batches. Plain functions + signals — NO Angular DI (there is none in a\n * worker); each owned store is observed by a real {@link opLog} driven off the microtask queue.\n *\n * ```ts\n * // my.worker.ts\n * const todos = store<Todo[]>([], workerStoreContext());\n * createWorkerHost({ stores: { todos } }); // serves `self` by default\n * ```\n */\nexport function createWorkerHost<\n S extends Record<string, WritableSignal<any>> = Record<string, never>,\n P extends Record<string, PublishedSource<any>> = Record<string, never>,\n T extends Record<string, WorkerTaskHandler> = Record<string, never>,\n>(options: {\n readonly stores?: S;\n readonly published?: P;\n readonly tasks?: T;\n readonly port?: WorkerPortLike;\n}): WorkerHost<{\n readonly stores: { [K in keyof S]: SignalValueOf<S[K]> };\n readonly published: { [K in keyof P]: SignalValueOf<P[K]> };\n readonly tasks: T;\n}> {\n const hostId = generateId();\n const sources = (options.stores ?? {}) as Record<string, WritableSignal<any>>;\n const publishedSources = (options.published ?? {}) as Record<\n string,\n PublishedSource<any>\n >;\n const tasks = (options.tasks ?? {}) as Record<string, WorkerTaskHandler>;\n const taskNames = Object.keys(tasks);\n const storeKeys = Object.keys(sources);\n const publishedKeys = Object.keys(publishedSources);\n\n const subtrees = new Map<string, Subtree>();\n const connections = new Set<Connection>();\n const publishedStatus = new Map<\n string,\n () => 'idle' | 'loading' | 'reloading' | 'resolved' | 'error'\n >();\n const statusWatches: { destroy(): void }[] = [];\n\n const fanout = (store: string, message: WorkerEnvelope) => {\n for (const conn of Array.from(connections))\n if (conn.stores.has(store)) conn.port.postMessage(message);\n };\n\n const observe = (\n key: string,\n src: Signal<unknown>,\n writable: boolean,\n ): void => {\n // Each subtree runs the owner's opSync (a real origin + HLC): its batches are real envelopes,\n // so the whole main<->worker sync is opSync over the MessageChannel transport, no bespoke\n // sequencer. Owner writes emit host-origin envelopes; received client writes fold in and are\n // relayed on. Emission is driven off the microtask queue (no injector inside a worker).\n const sync = opSync(src as unknown as WritableSignal<any>, {\n writer: hostId,\n origin: `${hostId}:${key}`,\n driver: microtaskOpLogDriver(),\n clock: createHlcClock(),\n });\n sync.subscribe((env) => {\n devAssertCloneable(env, `store '${key}' update`);\n fanout(key, { type: 'store:sync', store: key, env });\n });\n subtrees.set(key, { read: () => untracked(src), sync, writable });\n };\n\n for (const key of storeKeys) observe(key, sources[key], true);\n\n for (const key of publishedKeys) {\n const src = publishedSources[key];\n observe(key, src as Signal<unknown>, false);\n const statusSig = (src as { status?: Signal<ResourceStatus> }).status;\n if (statusSig) {\n publishedStatus.set(key, () => toRemoteStatus(untracked(statusSig)));\n let scheduled = false;\n let last: ResourceStatus | null = null;\n const w = createWatch(\n () => {\n const s = statusSig();\n if (s === last) return;\n last = s;\n fanout(key, {\n type: 'store:status',\n store: key,\n status: toRemoteStatus(s),\n });\n },\n (watch) => {\n if (scheduled) return;\n scheduled = true;\n queueMicrotask(() => {\n scheduled = false;\n watch.run();\n });\n },\n false,\n );\n w.notify();\n statusWatches.push(w);\n }\n }\n\n const runTask = (\n conn: Connection,\n runId: number,\n handler: WorkerTaskHandler,\n input: unknown,\n ): void => {\n const ac = new AbortController();\n conn.taskRuns.set(runId, ac);\n Promise.resolve()\n .then(() => handler(input, { signal: ac.signal }))\n .then(\n (value) => {\n if (ac.signal.aborted)\n conn.port.postMessage({ type: 'task:aborted', runId });\n else conn.port.postMessage({ type: 'task:ok', runId, value });\n },\n (err) => {\n if (ac.signal.aborted)\n conn.port.postMessage({ type: 'task:aborted', runId });\n else\n conn.port.postMessage({\n type: 'task:error',\n runId,\n error: serializeError(err),\n });\n },\n )\n .finally(() => {\n if (conn.taskRuns.get(runId) === ac) conn.taskRuns.delete(runId);\n });\n };\n\n const handle = (conn: Connection, msg: WorkerEnvelope): void => {\n switch (msg.type) {\n case 'hello': {\n conn.clientId = msg.clientId;\n conn.port.postMessage({\n type: 'ready',\n proto: PROTO_VERSION,\n hostId,\n stores: storeKeys,\n published: publishedKeys,\n tasks: taskNames,\n });\n return;\n }\n case 'store:subscribe': {\n const sub = subtrees.get(msg.store);\n if (!sub) return;\n conn.stores.add(msg.store);\n sub.sync.flush(); // settle any pending owner change into the register state first\n const checkpoint = sub.sync.snapshot();\n devAssertCloneable(checkpoint, `store '${msg.store}' checkpoint`);\n conn.port.postMessage({\n type: 'store:checkpoint',\n store: msg.store,\n checkpoint,\n });\n const currentStatus = publishedStatus.get(msg.store);\n if (currentStatus)\n conn.port.postMessage({\n type: 'store:status',\n store: msg.store,\n status: currentStatus(),\n });\n return;\n }\n case 'store:unsubscribe': {\n conn.stores.delete(msg.store);\n return;\n }\n case 'task:run': {\n const handler = tasks[msg.task];\n if (!handler) {\n conn.port.postMessage({\n type: 'task:error',\n runId: msg.runId,\n error: serializeError(\n new Error(`[@mmstack/worker] unknown task: ${msg.task}`),\n ),\n });\n return;\n }\n runTask(conn, msg.runId, handler, msg.input);\n return;\n }\n case 'task:abort': {\n conn.taskRuns.get(msg.runId)?.abort();\n conn.taskRuns.delete(msg.runId);\n return;\n }\n case 'store:sync': {\n // a routed client write: fold it into the owner store, then RELAY it to every subscriber\n // (including the sender, whose replica reads its own echo as the write acknowledgement).\n // Writes to a read-only (published) or unknown store are ignored: those never route.\n const sub = subtrees.get(msg.store);\n if (!sub || !sub.writable) return;\n sub.sync.receive(msg.env);\n fanout(msg.store, { type: 'store:sync', store: msg.store, env: msg.env });\n return;\n }\n }\n };\n\n const connect = (port: WorkerPortLike): (() => void) => {\n const conn: Connection = {\n port,\n clientId: null,\n stores: new Set(),\n taskRuns: new Map(),\n };\n connections.add(conn);\n port.onmessage = (ev) => handle(conn, ev.data as WorkerEnvelope);\n return () => {\n connections.delete(conn);\n port.onmessage = null;\n for (const ac of conn.taskRuns.values()) ac.abort();\n conn.taskRuns.clear();\n };\n };\n\n const defaultPort: WorkerPortLike | undefined =\n options.port ??\n (isWorkerGlobal(globalThis)\n ? (globalThis as unknown as WorkerPortLike)\n : undefined);\n if (defaultPort) connect(defaultPort);\n\n return {\n hostId,\n connect,\n override(store, fn) {\n const sub = subtrees.get(store);\n if (sub?.writable) sub.sync.override(fn);\n },\n flush() {\n for (const { sync } of subtrees.values()) sync.flush();\n },\n dispose() {\n for (const { sync } of subtrees.values()) sync.destroy();\n for (const w of statusWatches) w.destroy();\n connections.clear();\n },\n };\n}\n","import { createStoreContext, type toStoreOptions } from '@mmstack/primitives';\n\nlet context: toStoreOptions | undefined;\n\n/**\n * The one shared store context for this worker — pass it to every `store`/`toStore` you create in\n * the worker so they share proxy identity and cleanup, exactly as `providedIn: 'root'` shares one\n * cache across an app's stores.\n *\n * ```ts\n * // my.worker.ts\n * const todos = store<Todo[]>([], workerStoreContext());\n * const filter = store({ q: '' }, workerStoreContext()); // same context\n * ```\n */\nexport function workerStoreContext(): toStoreOptions {\n return (context ??= createStoreContext());\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mmstack-worker-protocol.mjs","sources":["../../../../packages/worker/protocol/src/lib/envelope.ts","../../../../packages/worker/protocol/src/lib/error.ts","../../../../packages/worker/protocol/src/lib/id.ts","../../../../packages/worker/protocol/src/lib/port.ts","../../../../packages/worker/protocol/src/mmstack-worker-protocol.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"mmstack-worker-protocol.mjs","sources":["../../../../packages/worker/protocol/src/lib/envelope.ts","../../../../packages/worker/protocol/src/lib/error.ts","../../../../packages/worker/protocol/src/lib/id.ts","../../../../packages/worker/protocol/src/lib/port.ts","../../../../packages/worker/protocol/src/mmstack-worker-protocol.ts"],"sourcesContent":["import type { OpEnvelope, OpSyncCheckpoint } from '@mmstack/primitives';\nimport type { SerializedError } from './error';\n\n/** Wire-protocol version, negotiated in the hello/ready handshake. Bump on breaking envelope change. */\nexport const PROTO_VERSION = 1;\nexport type ProtoVersion = typeof PROTO_VERSION;\n\n/** The status a remote (worker-owned) computation reports across the boundary — the rung-3 seam. */\nexport type RemoteStatus =\n | 'idle'\n | 'loading'\n | 'reloading'\n | 'resolved'\n | 'error';\n\n/**\n * Every message exchanged over a {@link WorkerPortLike}. One discriminated union shared by the\n * main-thread client and the worker host (and, later, `@mmstack/mesh`), so the two sides can never\n * drift.\n */\nexport type WorkerEnvelope =\n | { type: 'hello'; proto: ProtoVersion; clientId: string }\n | {\n type: 'ready';\n proto: ProtoVersion;\n hostId: string;\n stores: readonly string[];\n published: readonly string[];\n tasks: readonly string[];\n }\n | { type: 'fatal'; error: SerializedError }\n | { type: 'task:run'; runId: number; task: string; input: unknown }\n | { type: 'task:abort'; runId: number }\n | { type: 'task:ok'; runId: number; value: unknown }\n | { type: 'task:error'; runId: number; error: SerializedError }\n | { type: 'task:aborted'; runId: number }\n | { type: 'store:subscribe'; store: string; clientId: string }\n // The owner ships its opSync checkpoint (root + register state + watermark) on subscribe/resync,\n // so a joining replica hydrates through its own fold rather than adopting a bare value.\n | { type: 'store:checkpoint'; store: string; checkpoint: OpSyncCheckpoint }\n // A single op envelope, both directions: owner-emitted ops flow host -> clients, and a client's\n // routed write flows client -> host. The host relays each applied envelope to every subscriber\n // (including the sender, whose replica reads its own echo as the acknowledgement).\n | { type: 'store:sync'; store: string; env: OpEnvelope }\n | { type: 'store:unsubscribe'; store: string; clientId: string }\n | { type: 'store:status'; store: string; status: RemoteStatus; error?: SerializedError };\n\n/** Narrows a raw message to a specific envelope variant by its `type`. */\nexport type EnvelopeOf<K extends WorkerEnvelope['type']> = Extract<\n WorkerEnvelope,\n { type: K }\n>;\n","/**\n * A structured-clone-safe rendering of an `Error` for cross-thread propagation. A worker throw\n * can't cross `postMessage` as an `Error` (methods/prototype are lost), so tasks and store writes\n * serialize failures into this shape and the receiving side rebuilds a real `Error`.\n */\nexport type SerializedError = {\n name: string;\n message: string;\n stack?: string;\n cause?: SerializedError;\n};\n\nfunction stringify(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/** Renders any thrown value (Error or otherwise) into a {@link SerializedError}, chaining `cause`. */\nexport function serializeError(err: unknown): SerializedError {\n if (err instanceof Error) {\n return {\n name: err.name,\n message: err.message,\n stack: err.stack,\n cause: err.cause !== undefined ? serializeError(err.cause) : undefined,\n };\n }\n return { name: 'Error', message: stringify(err) };\n}\n\n/** Rebuilds a real `Error` (name/message/stack/cause chain preserved) from a {@link SerializedError}. */\nexport function deserializeError(e: SerializedError): Error {\n const err = new Error(e.message);\n err.name = e.name;\n if (e.stack !== undefined) err.stack = e.stack;\n if (e.cause !== undefined) err.cause = deserializeError(e.cause);\n return err;\n}\n","/** A short unique id for host/client identity on a shared transport. */\nexport function generateId(): string {\n if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID();\n return Math.random().toString(36).slice(2);\n}\n","/**\n * The minimal structural transport the worker protocol rides on — the intersection of a `Worker`,\n * a `MessagePort`, and a `BroadcastChannel`. Modeled on `@mmstack/resource`'s `MutationSyncChannel`:\n * anything with `postMessage` + assignable `onmessage` qualifies, so a `MessageChannel` port-pair\n * works in tests with zero adapters and a future `@mmstack/mesh` can slot a WS/RTC channel behind\n * the same type.\n */\nexport type WorkerPortLike = {\n postMessage(message: unknown, transfer?: Transferable[]): void;\n // `any` ev: keeps a real Worker/MessagePort assignable under strictFunctionTypes\n onmessage: ((ev: any) => void) | null;\n /** `Worker` has `terminate()`, `MessagePort`/`BroadcastChannel` have `close()` — either, both optional. */\n terminate?(): void;\n close?(): void;\n};\n\n/** Closes a port by whichever teardown method it exposes. */\nexport function closePort(port: WorkerPortLike): void {\n port.terminate?.();\n port.close?.();\n}\n\nconst TRANSFERABLES = new WeakMap<object, Transferable[]>();\n\n/**\n * Marks `value` so that, when it is sent over a {@link WorkerPortLike}, the listed `Transferable`s\n * (e.g. an `ArrayBuffer`) are moved rather than structure-cloned — zero-copy, but detached at the\n * sender. Comlink-idiom. v1 honors this on task input/output only; store traffic always clones (a\n * moved buffer inside a replicated tree would detach the owner's copy).\n *\n * ```ts\n * const buf = new Float64Array(1e6);\n * workerResource(() => transfer({ buf }, [buf.buffer]), { worker, task: 'process' });\n * ```\n */\nexport function transfer<T extends object>(value: T, transferables: Transferable[]): T {\n TRANSFERABLES.set(value, transferables);\n return value;\n}\n\n/** @internal Reads back the Transferables registered on a value via {@link transfer}, if any. */\nexport function takeTransferables(value: unknown): Transferable[] | undefined {\n if (value !== null && typeof value === 'object')\n return TRANSFERABLES.get(value as object);\n return undefined;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"AAGA;AACO,MAAM,aAAa,GAAG;;ACQ7B,SAAS,SAAS,CAAC,KAAc,EAAA;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ;AAAE,QAAA,OAAO,KAAK;AAC3C,IAAA,IAAI;QACF,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC;IAC/C;AAAE,IAAA,MAAM;AACN,QAAA,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB;AACF;AAEA;AACM,SAAU,cAAc,CAAC,GAAY,EAAA;AACzC,IAAA,IAAI,GAAG,YAAY,KAAK,EAAE;QACxB,OAAO;YACL,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,KAAK,EAAE,GAAG,CAAC,KAAK;AAChB,YAAA,KAAK,EAAE,GAAG,CAAC,KAAK,KAAK,SAAS,GAAG,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,SAAS;SACvE;IACH;AACA,IAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE;AACnD;AAEA;AACM,SAAU,gBAAgB,CAAC,CAAkB,EAAA;IACjD,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;AAChC,IAAA,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI;AACjB,IAAA,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;AAAE,QAAA,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK;AAC9C,IAAA,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;QAAE,GAAG,CAAC,KAAK,GAAG,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC;AAChE,IAAA,OAAO,GAAG;AACZ;;ACzCA;SACgB,UAAU,GAAA;AACxB,IAAA,IAAI,UAAU,CAAC,MAAM,EAAE,UAAU;AAAE,QAAA,OAAO,UAAU,CAAC,MAAM,CAAC,UAAU,EAAE;AACxE,IAAA,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AAC5C;;ACYA;AACM,SAAU,SAAS,CAAC,IAAoB,EAAA;AAC5C,IAAA,IAAI,CAAC,SAAS,IAAI;AAClB,IAAA,IAAI,CAAC,KAAK,IAAI;AAChB;AAEA,MAAM,aAAa,GAAG,IAAI,OAAO,EAA0B;AAE3D;;;;;;;;;;AAUG;AACG,SAAU,QAAQ,CAAmB,KAAQ,EAAE,aAA6B,EAAA;AAChF,IAAA,aAAa,CAAC,GAAG,CAAC,KAAK,EAAE,aAAa,CAAC;AACvC,IAAA,OAAO,KAAK;AACd;AAEA;AACM,SAAU,iBAAiB,CAAC,KAAc,EAAA;AAC9C,IAAA,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;AAC7C,QAAA,OAAO,aAAa,CAAC,GAAG,CAAC,KAAe,CAAC;AAC3C,IAAA,OAAO,SAAS;AAClB;;AC7CA;;AAEG;;;;"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { generateId, deserializeError, closePort, takeTransferables } from '@mmstack/worker/protocol';
|
|
2
2
|
export * from '@mmstack/worker/protocol';
|
|
3
3
|
import { inject, Injector, runInInjectionContext, PLATFORM_ID, signal, DestroyRef, computed, effect, untracked } from '@angular/core';
|
|
4
|
-
import { injectTransitionScope, toStore,
|
|
4
|
+
import { injectTransitionScope, toStore, opSync } from '@mmstack/primitives';
|
|
5
|
+
import { createWatch } from '@angular/core/primitives/signals';
|
|
5
6
|
|
|
6
7
|
/** Thrown into a task/write promise when it is aborted. `name` is `'AbortError'` (DOM convention). */
|
|
7
8
|
class WorkerAbortError extends Error {
|
|
@@ -298,6 +299,23 @@ function build$1(params, options) {
|
|
|
298
299
|
return self;
|
|
299
300
|
}
|
|
300
301
|
|
|
302
|
+
// An injector-free opLog driver (schedules emission off the microtask queue via `createWatch`), so
|
|
303
|
+
// the replica's `opSync` emits without depending on an application tick. Writes force a synchronous
|
|
304
|
+
// `flush()` at the call site; between writes, owner envelopes drive apply, never local emission.
|
|
305
|
+
const microtaskDriver = () => (run) => {
|
|
306
|
+
let scheduled = false;
|
|
307
|
+
const watch = createWatch(() => run(), (w) => {
|
|
308
|
+
if (scheduled)
|
|
309
|
+
return;
|
|
310
|
+
scheduled = true;
|
|
311
|
+
queueMicrotask(() => {
|
|
312
|
+
scheduled = false;
|
|
313
|
+
w.run();
|
|
314
|
+
});
|
|
315
|
+
}, false);
|
|
316
|
+
watch.notify();
|
|
317
|
+
return { destroy: () => watch.destroy() };
|
|
318
|
+
};
|
|
301
319
|
function workerStore(worker, key, opt) {
|
|
302
320
|
const injector = opt?.injector ?? inject(Injector);
|
|
303
321
|
return runInInjectionContext(injector, () => build(worker, key, opt));
|
|
@@ -308,7 +326,14 @@ function build(worker, key, opt) {
|
|
|
308
326
|
const s = toStore(root, {
|
|
309
327
|
injector,
|
|
310
328
|
});
|
|
311
|
-
|
|
329
|
+
// The replica runs its own opSync over the worker transport: local writes emit stamped envelopes
|
|
330
|
+
// routed to the owner, owner envelopes fold in convergently. A driver (not the injector) so
|
|
331
|
+
// emission stays synchronous under an explicit `flush()`, matching the DI-free worker model.
|
|
332
|
+
const sync = opSync(root, {
|
|
333
|
+
writer: worker.clientId,
|
|
334
|
+
driver: microtaskDriver(),
|
|
335
|
+
onGap: () => resync(),
|
|
336
|
+
});
|
|
312
337
|
const status = signal('loading', ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
313
338
|
const error = signal(undefined, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
314
339
|
const isLoading = computed(() => {
|
|
@@ -316,81 +341,60 @@ function build(worker, key, opt) {
|
|
|
316
341
|
return st === 'loading' || st === 'reloading';
|
|
317
342
|
}, ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
|
|
318
343
|
let hasSnapshot = false;
|
|
319
|
-
let
|
|
320
|
-
|
|
321
|
-
let writeSeq = 0;
|
|
344
|
+
let lastEmitted = 0; // highest own-origin version emitted; the write() ack key
|
|
345
|
+
// own writes awaiting the owner's echo (resolve), plus the raw envelopes to resend after a resync
|
|
322
346
|
const writePending = new Map();
|
|
347
|
+
const unacked = new Map();
|
|
323
348
|
const hasValue = () => hasSnapshot;
|
|
324
349
|
const isOwned = () => worker.manifest()?.stores.includes(key) ?? false;
|
|
325
|
-
const
|
|
326
|
-
for (const [
|
|
327
|
-
if (
|
|
328
|
-
writePending.delete(
|
|
329
|
-
p.resolve
|
|
350
|
+
const ackUpTo = (version) => {
|
|
351
|
+
for (const [v, p] of writePending) {
|
|
352
|
+
if (v <= version) {
|
|
353
|
+
writePending.delete(v);
|
|
354
|
+
p.resolve();
|
|
330
355
|
}
|
|
331
356
|
}
|
|
357
|
+
for (const v of [...unacked.keys()])
|
|
358
|
+
if (v <= version)
|
|
359
|
+
unacked.delete(v);
|
|
332
360
|
};
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
store: key,
|
|
341
|
-
writeId: id,
|
|
342
|
-
clientId: worker.clientId,
|
|
343
|
-
ops,
|
|
344
|
-
});
|
|
345
|
-
};
|
|
346
|
-
const shipUnsub = log.subscribe((batch) => shipLocal(batch.ops));
|
|
361
|
+
const shipUnsub = sync.subscribe((env) => {
|
|
362
|
+
lastEmitted = env.version;
|
|
363
|
+
unacked.set(env.version, env);
|
|
364
|
+
if (isOwned() && untracked(worker.connected)) {
|
|
365
|
+
worker._send({ type: 'store:sync', store: key, env });
|
|
366
|
+
}
|
|
367
|
+
});
|
|
347
368
|
const onStoreMessage = (msg) => {
|
|
348
369
|
if (!('store' in msg) || msg.store !== key)
|
|
349
370
|
return;
|
|
350
371
|
switch (msg.type) {
|
|
351
|
-
case 'store:
|
|
352
|
-
|
|
353
|
-
|
|
372
|
+
case 'store:checkpoint': {
|
|
373
|
+
// rebase from the full unacked outbox, not opSync's bounded recent-local ring, so a burst
|
|
374
|
+
// of writes larger than that ring is never dropped from the local rebase on re-hydrate
|
|
375
|
+
sync.hydrate(msg.checkpoint, [
|
|
376
|
+
...unacked.values(),
|
|
377
|
+
]);
|
|
354
378
|
hasSnapshot = true;
|
|
355
|
-
resyncing = false;
|
|
356
379
|
status.set('resolved');
|
|
357
380
|
error.set(undefined);
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return;
|
|
366
|
-
if (msg.batch.version > lastVersion + 1) {
|
|
367
|
-
resync(); // gap: re-hydrate rather than apply out of order
|
|
368
|
-
return;
|
|
381
|
+
// writes the owner already applied are covered by the checkpoint watermark; resolve them,
|
|
382
|
+
// then resend any still-unacked tail so a write made before the (re)hydrate is never lost
|
|
383
|
+
ackUpTo(msg.checkpoint.wm?.[sync.origin] ?? 0);
|
|
384
|
+
if (isOwned() && untracked(worker.connected)) {
|
|
385
|
+
for (const env of [...unacked.values()].sort((a, b) => a.version - b.version)) {
|
|
386
|
+
worker._send({ type: 'store:sync', store: key, env });
|
|
387
|
+
}
|
|
369
388
|
}
|
|
370
|
-
log.apply(msg.batch.ops);
|
|
371
|
-
lastVersion = msg.batch.version;
|
|
372
|
-
settleWrites();
|
|
373
389
|
return;
|
|
374
390
|
}
|
|
375
|
-
case 'store:
|
|
376
|
-
const
|
|
377
|
-
if (
|
|
391
|
+
case 'store:sync': {
|
|
392
|
+
const env = msg.env;
|
|
393
|
+
if (env.origin === sync.origin) {
|
|
394
|
+
ackUpTo(env.version); // the owner echoed our own write back: acknowledgement
|
|
378
395
|
return;
|
|
379
|
-
if (lastVersion >= msg.version) {
|
|
380
|
-
writePending.delete(msg.writeId);
|
|
381
|
-
p.resolve?.();
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
p.version = msg.version;
|
|
385
|
-
}
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
case 'store:write:error': {
|
|
389
|
-
const p = writePending.get(msg.writeId);
|
|
390
|
-
if (p) {
|
|
391
|
-
writePending.delete(msg.writeId);
|
|
392
|
-
p.reject?.(deserializeError(msg.error));
|
|
393
396
|
}
|
|
397
|
+
sync.receive(env); // owner or peer envelope; a version gap triggers onGap -> resync
|
|
394
398
|
return;
|
|
395
399
|
}
|
|
396
400
|
case 'store:status': {
|
|
@@ -427,7 +431,6 @@ function build(worker, key, opt) {
|
|
|
427
431
|
});
|
|
428
432
|
};
|
|
429
433
|
const resync = () => {
|
|
430
|
-
resyncing = true;
|
|
431
434
|
status.set('reloading');
|
|
432
435
|
worker._send({
|
|
433
436
|
type: 'store:subscribe',
|
|
@@ -441,8 +444,9 @@ function build(worker, key, opt) {
|
|
|
441
444
|
subscribe(); // already up (created post-handshake): _onReady won't fire
|
|
442
445
|
const offDisconnect = worker._onDisconnect(() => {
|
|
443
446
|
for (const [, p] of writePending)
|
|
444
|
-
p.reject
|
|
447
|
+
p.reject(new WorkerCrashedError());
|
|
445
448
|
writePending.clear();
|
|
449
|
+
unacked.clear();
|
|
446
450
|
});
|
|
447
451
|
const self = {
|
|
448
452
|
store: s,
|
|
@@ -461,28 +465,25 @@ function build(worker, key, opt) {
|
|
|
461
465
|
if (!isOwned())
|
|
462
466
|
return Promise.reject(new Error(`[@mmstack/worker] store is read-only (published): ${key}`));
|
|
463
467
|
recipe(s);
|
|
464
|
-
const before =
|
|
465
|
-
|
|
466
|
-
if (
|
|
467
|
-
return Promise.resolve();
|
|
468
|
-
const
|
|
468
|
+
const before = lastEmitted;
|
|
469
|
+
sync.flush(); // emit the routed write synchronously (driver-backed opSync)
|
|
470
|
+
if (lastEmitted === before)
|
|
471
|
+
return Promise.resolve(); // no-op recipe: nothing shipped
|
|
472
|
+
const version = lastEmitted;
|
|
469
473
|
return new Promise((resolve, reject) => {
|
|
470
|
-
|
|
471
|
-
if (!p)
|
|
472
|
-
return resolve();
|
|
473
|
-
p.resolve = resolve;
|
|
474
|
-
p.reject = reject;
|
|
474
|
+
writePending.set(version, { resolve, reject });
|
|
475
475
|
});
|
|
476
476
|
},
|
|
477
477
|
destroy: () => {
|
|
478
478
|
shipUnsub();
|
|
479
|
-
|
|
479
|
+
sync.destroy();
|
|
480
480
|
unsub();
|
|
481
481
|
offReady();
|
|
482
482
|
offDisconnect();
|
|
483
483
|
for (const [, p] of writePending)
|
|
484
|
-
p.reject
|
|
484
|
+
p.reject(new WorkerAbortError('worker store destroyed'));
|
|
485
485
|
writePending.clear();
|
|
486
|
+
unacked.clear();
|
|
486
487
|
worker._send({
|
|
487
488
|
type: 'store:unsubscribe',
|
|
488
489
|
store: key,
|
|
@@ -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/mmstack-worker.ts"],"sourcesContent":["import {\n DestroyRef,\n inject,\n Injector,\n PLATFORM_ID,\n runInInjectionContext,\n signal,\n type Signal,\n} from '@angular/core';\nimport {\n closePort,\n deserializeError,\n generateId,\n takeTransferables,\n type HasSchema,\n type SchemaOf,\n type TaskInput,\n type TaskOutput,\n type WorkerEnvelope,\n type WorkerPortLike,\n type WorkerSchema,\n} from '@mmstack/worker/protocol';\n\n/** What the worker advertised in its `ready` handshake. */\nexport type WorkerManifest = {\n readonly hostId: string;\n readonly stores: readonly string[];\n readonly published: readonly string[];\n readonly tasks: readonly string[];\n};\n\n/** Thrown into a task/write promise when it is aborted. `name` is `'AbortError'` (DOM convention). */\nexport class WorkerAbortError extends Error {\n constructor(message = 'The worker task was aborted') {\n super(message);\n this.name = 'AbortError';\n }\n}\n\n/** Thrown into pending tasks/writes when the worker crashes. `name` is `'WorkerCrashedError'`. */\nexport class WorkerCrashedError extends Error {\n constructor(message = 'The worker crashed') {\n super(message);\n this.name = 'WorkerCrashedError';\n }\n}\n\nexport type ConnectWorkerOptions = {\n readonly injector?: Injector;\n /** `'auto'` (default) respawns on crash; `'manual'` surfaces disconnect and stops. */\n readonly restart?: 'auto' | 'manual';\n /** Backoff before respawn attempt `n` (0-based). Default exponential 1s→30s. */\n readonly restartDelay?: (attempt: number) => number;\n};\n\nexport type WorkerRef<M extends WorkerSchema = WorkerSchema> = HasSchema<M> & {\n /** True once the `ready` handshake has completed (and again after an auto-restart). */\n readonly connected: Signal<boolean>;\n /** The worker's advertised manifest, or `null` before the first `ready`. */\n readonly manifest: Signal<WorkerManifest | null>;\n /** Run a named task exposed by the worker host; resolves with its result (typed from the schema). */\n runTask<K extends keyof M['tasks'] & string>(\n task: K,\n input: TaskInput<M, K>,\n opt?: { signal?: AbortSignal; transfer?: Transferable[] },\n ): Promise<TaskOutput<M, K>>;\n destroy(): void;\n\n /** @internal Post an envelope to the worker. */\n _send(msg: WorkerEnvelope, transfer?: Transferable[]): void;\n /** @internal Receive every non-task envelope (store traffic). Returns an unsubscribe. */\n _subscribe(handler: (msg: WorkerEnvelope) => void): () => void;\n /** @internal Run `fn` on every (re)connection — used to re-subscribe stores after a restart. */\n _onReady(fn: () => void): () => void;\n /** @internal Run `fn` when the connection drops (crash) — replicas reject pending writes here. */\n _onDisconnect(fn: () => void): () => void;\n /** @internal This client's identity on the transport. */\n readonly clientId: string;\n};\n\n/**\n * Connects the main thread to a worker over a {@link WorkerPortLike} the caller provides via `spawn`\n * — typically `() => new Worker(new URL('./my.worker', import.meta.url), { type: 'module' })` (the\n * URL literal must live in APP code so the bundler emits the worker chunk). Performs the hello/ready\n * handshake, exposes `connected`/`manifest`, routes named-task runs, and (via internal seams)\n * carries the store-replication traffic for {@link workerStore}. On crash it respawns with backoff\n * when `restart: 'auto'`.\n */\nexport function connectWorker<H = WorkerSchema>(\n spawn: () => WorkerPortLike,\n opt?: ConnectWorkerOptions,\n): WorkerRef<SchemaOf<H>> {\n const injector = opt?.injector ?? inject(Injector);\n return runInInjectionContext(injector, () =>\n build(spawn, opt),\n ) as unknown as WorkerRef<SchemaOf<H>>;\n}\n\nfunction build(\n spawn: () => WorkerPortLike,\n opt?: ConnectWorkerOptions,\n): WorkerRef {\n const isServer = inject(PLATFORM_ID) === 'server';\n const clientId = generateId();\n const connected = signal(false);\n const manifest = signal<WorkerManifest | null>(null);\n\n const taskPending = new Map<number, { resolve: (v: any) => void; reject: (e: unknown) => void }>();\n const storeHandlers = new Set<(msg: WorkerEnvelope) => void>();\n const readyHooks = new Set<() => void>();\n const disconnectHooks = new Set<() => void>();\n const restart = opt?.restart ?? 'auto';\n const restartDelay = opt?.restartDelay ?? ((n: number) => Math.min(30_000, 1000 * 2 ** n));\n let attempt = 0;\n let runIdSeq = 0;\n let destroyed = false;\n let crashed = false;\n let port: WorkerPortLike = null as unknown as WorkerPortLike;\n\n const send = (msg: WorkerEnvelope, transfer?: Transferable[]): void => {\n if (port) port.postMessage(msg, transfer);\n };\n\n const onMessage = (msg: WorkerEnvelope): void => {\n switch (msg.type) {\n case 'ready':\n attempt = 0;\n crashed = false;\n manifest.set({\n hostId: msg.hostId,\n stores: msg.stores,\n published: msg.published,\n tasks: msg.tasks,\n });\n connected.set(true);\n for (const hook of readyHooks) hook();\n return;\n case 'task:ok': {\n const p = taskPending.get(msg.runId);\n if (p) {\n taskPending.delete(msg.runId);\n p.resolve(msg.value);\n }\n return;\n }\n case 'task:error': {\n const p = taskPending.get(msg.runId);\n if (p) {\n taskPending.delete(msg.runId);\n p.reject(deserializeError(msg.error));\n }\n return;\n }\n case 'task:aborted':\n taskPending.delete(msg.runId);\n return;\n default:\n for (const h of storeHandlers) h(msg);\n }\n };\n\n const handleCrash = (): void => {\n if (destroyed) return;\n crashed = true;\n connected.set(false);\n for (const [, p] of taskPending) p.reject(new WorkerCrashedError());\n taskPending.clear();\n for (const fn of disconnectHooks) fn();\n if (restart === 'manual') return;\n const delay = restartDelay(attempt++);\n setTimeout(() => {\n if (!destroyed) openPort();\n }, delay);\n };\n\n const openPort = (): void => {\n if (port) closePort(port);\n port = spawn();\n port.onmessage = (ev) => onMessage(ev.data as WorkerEnvelope);\n // crash detection via property setters, not addEventListener (perturbs node MessagePort delivery)\n const evt = port as unknown as {\n onerror?: ((e?: unknown) => void) | null;\n onmessageerror?: ((e?: unknown) => void) | null;\n };\n if ('onerror' in evt) evt.onerror = handleCrash;\n if ('onmessageerror' in evt) evt.onmessageerror = handleCrash;\n send({ type: 'hello', proto: 1, clientId });\n };\n\n if (!isServer) openPort();\n\n inject(DestroyRef).onDestroy(() => teardown());\n\n const teardown = (): void => {\n destroyed = true;\n connected.set(false);\n for (const [, p] of taskPending) p.reject(new WorkerAbortError('worker connection destroyed'));\n taskPending.clear();\n for (const fn of disconnectHooks) fn();\n if (port) closePort(port);\n };\n\n return {\n connected,\n manifest,\n clientId,\n runTask(\n task: string,\n input: unknown,\n o?: { signal?: AbortSignal; transfer?: Transferable[] },\n ): Promise<unknown> {\n if (isServer) return Promise.reject(new WorkerCrashedError('no worker on the server'));\n if (destroyed) return Promise.reject(new WorkerAbortError('worker connection destroyed'));\n if (crashed) return Promise.reject(new WorkerCrashedError('worker is restarting'));\n if (o?.signal?.aborted) return Promise.reject(new WorkerAbortError());\n const runId = ++runIdSeq;\n return new Promise<unknown>((resolve, reject) => {\n taskPending.set(runId, { resolve, reject });\n send(\n { type: 'task:run', runId, task, input },\n o?.transfer ?? takeTransferables(input),\n );\n o?.signal?.addEventListener(\n 'abort',\n () => {\n if (!taskPending.has(runId)) return;\n taskPending.delete(runId);\n send({ type: 'task:abort', runId });\n reject(new WorkerAbortError());\n },\n { once: true },\n );\n });\n },\n destroy: teardown,\n _send: send,\n _subscribe(handler) {\n storeHandlers.add(handler);\n return () => storeHandlers.delete(handler);\n },\n _onReady(fn) {\n readyHooks.add(fn);\n return () => readyHooks.delete(fn);\n },\n _onDisconnect(fn) {\n disconnectHooks.add(fn);\n return () => disconnectHooks.delete(fn);\n },\n };\n}\n","import {\n computed,\n DestroyRef,\n effect,\n inject,\n Injector,\n PLATFORM_ID,\n runInInjectionContext,\n signal,\n untracked,\n type ResourceStatus,\n type Signal,\n type ValueEqualityFn,\n} from '@angular/core';\nimport { injectTransitionScope } from '@mmstack/primitives';\nimport { WorkerAbortError, type WorkerRef } from './connect-worker';\n\n/** Returned from a paused `params` fn to HOLD (keep the current value/status, run nothing). */\nexport const PAUSED: unique symbol = Symbol('@mmstack/worker:PAUSED');\n\nexport type WorkerRequestContext = { readonly paused: typeof PAUSED };\n\n/**\n * Reactive input producer. Reads signals to derive the task input; return `undefined` to disable\n * (idle, no run), or `ctx.paused` to hold. Re-runs when the returned input changes (by `Object.is`).\n */\nexport type WorkerParamsFn<TInput> = (\n ctx: WorkerRequestContext,\n) => TInput | undefined | void | typeof PAUSED;\n\nexport type WorkerResourceOptions<TResult> = {\n readonly injector?: Injector;\n /** Equality for the result value — a run resolving equal to the held value emits no notification. */\n readonly equal?: ValueEqualityFn<TResult>;\n readonly defaultValue?: TResult;\n /**\n * Hold the previous value through a re-run (status `'reloading'`) instead of clearing it. Default\n * TRUE — a `workerResource` is an async derivation, not a fetch; flashing empty mid-recompute is\n * rarely wanted.\n */\n readonly keepPrevious?: boolean;\n /** Register into the nearest transition scope: `'indicator'` drives pending, `'suspend'` also gates first paint. */\n readonly register?: false | 'indicator' | 'suspend';\n /** The connected worker whose host exposes the task. */\n readonly worker: WorkerRef;\n /** The name of the task to run (as declared in `createWorkerHost({ tasks })`). */\n readonly task: string;\n};\n\nexport type WorkerResourceRef<T> = {\n /** The latest successfully-computed value (held through re-runs per `keepPrevious`). */\n readonly value: Signal<T | undefined>;\n readonly status: Signal<ResourceStatus>;\n readonly error: Signal<unknown>;\n readonly isLoading: Signal<boolean>;\n hasValue(): boolean;\n /** Re-run with the current input, bypassing input de-duplication. */\n reload(): void;\n /** Cancel the in-flight run; the value is KEPT and status becomes `'local'`. */\n abort(): void;\n destroy(): void;\n};\n\n/**\n * Runs a named task on a connected worker, exposed with the standard resource surface so heavy\n * compute makes the UI *pending*, not *frozen*. `params` reactively derives the input; latest-wins\n * (a changed input supersedes and aborts the in-flight run); the result surfaces as\n * `value`/`status`/`error`, and — with `register` — participates in transition scopes. No-ops on the\n * server.\n */\nexport function workerResource<TInput, TResult>(\n params: WorkerParamsFn<TInput>,\n options: WorkerResourceOptions<TResult>,\n): WorkerResourceRef<TResult> {\n const injector = options.injector ?? inject(Injector);\n return runInInjectionContext(injector, () =>\n build<TInput, TResult>(params, options),\n );\n}\n\nfunction build<TInput, TResult>(\n params: WorkerParamsFn<TInput>,\n options: WorkerResourceOptions<TResult>,\n): WorkerResourceRef<TResult> {\n const isServer = inject(PLATFORM_ID) === 'server';\n const keepPrevious = options.keepPrevious ?? true;\n const userEqual = options.equal;\n const { worker, task } = options;\n\n const runTask = (input: TInput, signal: AbortSignal): Promise<TResult> =>\n worker.runTask(task, input, { signal }) as Promise<TResult>;\n\n const value = signal<TResult | undefined>(options.defaultValue, {\n equal: userEqual\n ? (a, b) =>\n a === undefined || b === undefined ? a === b : userEqual(a, b)\n : undefined,\n });\n const status = signal<ResourceStatus>('idle');\n const error = signal<unknown>(undefined);\n const isLoading = computed(() => {\n const s = status();\n return s === 'loading' || s === 'reloading';\n });\n const hasValue = () => value() !== undefined;\n\n let epoch = 0;\n let inFlight: AbortController | null = null;\n let lastInput: TInput | undefined;\n let hasRun = false;\n\n const start = (input: TInput): void => {\n const myEpoch = ++epoch;\n inFlight?.abort();\n const ac = new AbortController();\n inFlight = ac;\n lastInput = input;\n hasRun = true;\n status.set(keepPrevious && value() !== undefined ? 'reloading' : 'loading');\n error.set(undefined);\n\n runTask(input, ac.signal).then(\n (result) => {\n if (inFlight === ac) inFlight = null;\n if (myEpoch !== epoch) return;\n value.set(result);\n status.set('resolved');\n },\n (err) => {\n if (inFlight === ac) inFlight = null;\n if (myEpoch !== epoch) return;\n if (\n err instanceof WorkerAbortError ||\n (err as { name?: string })?.name === 'AbortError'\n )\n return;\n error.set(err);\n status.set('error');\n },\n );\n };\n\n const input = computed(() => params({ paused: PAUSED }));\n\n const ref = effect(() => {\n const i = input();\n if (isServer || i === PAUSED) return;\n if (i === undefined) return;\n if (Object.is(i, lastInput) && hasRun && untracked(status) !== 'error')\n return;\n untracked(() => start(i as TInput));\n });\n\n inject(DestroyRef).onDestroy(() => {\n epoch++;\n inFlight?.abort();\n });\n\n const self: WorkerResourceRef<TResult> = {\n value,\n status,\n error,\n isLoading,\n hasValue,\n reload: () => {\n const i = untracked(input);\n if (isServer || i === PAUSED || i === undefined) return;\n start(i as TInput);\n },\n abort: () => {\n if (!inFlight) return;\n epoch++;\n inFlight.abort();\n inFlight = null;\n status.set('local');\n },\n destroy: () => {\n epoch++;\n inFlight?.abort();\n ref.destroy();\n },\n };\n\n if (options.register) {\n const scope = injectTransitionScope();\n scope.add(self, { suspends: options.register === 'suspend' });\n let removed = false;\n const remove = () => {\n if (removed) return;\n removed = true;\n scope.remove(self);\n };\n inject(DestroyRef).onDestroy(remove);\n const destroy = self.destroy;\n self.destroy = () => {\n remove();\n destroy();\n };\n }\n\n return self;\n}\n","import {\n computed,\n DestroyRef,\n inject,\n Injector,\n runInInjectionContext,\n signal,\n untracked,\n type ResourceStatus,\n type Signal,\n type WritableSignal,\n} from '@angular/core';\nimport {\n injectTransitionScope,\n opLog,\n toStore,\n type SignalStore,\n type StoreOp,\n type WritableSignalStore,\n} from '@mmstack/primitives';\nimport {\n deserializeError,\n type IsWritableKey,\n type StoreKeys,\n type StoreValueOf,\n type WorkerEnvelope,\n type WorkerSchema,\n} from '@mmstack/worker/protocol';\nimport {\n WorkerAbortError,\n WorkerCrashedError,\n type WorkerRef,\n} from './connect-worker';\n\nexport type WorkerStoreOptions<T> = {\n readonly injector?: Injector;\n /** Value the replica holds before the first snapshot arrives. */\n readonly defaultValue?: T;\n /** Register into the nearest transition scope while hydrating/reloading. */\n readonly register?: false | 'indicator' | 'suspend';\n};\n\n/** The write path — present only on OWNED (writable) subtrees; absent on published (read-only) ones. */\nexport type WorkerStoreWrite<T> = {\n /**\n * Route a write to the OWNER: `recipe` applies optimistically to the store, its diff ships as ops,\n * and the owner sequences and re-emits it. Resolves once that authoritative batch reconciles this\n * replica. To hide the value until the owner confirms it, fork the store and reveal on resolve.\n * Rejects if the owner reports a write error.\n */\n write(recipe: (draft: WritableSignalStore<T>) => void): Promise<void>;\n};\n\nexport type WorkerStoreRef<T, W extends boolean = true> = {\n /**\n * The live store. For an OWNED subtree it is a full {@link WritableSignalStore}: writes apply\n * optimistically and route to the owner, and other op-log readers (`meshSync`, `persist`) can\n * attach to it. For a PUBLISHED subtree it is a read-only {@link SignalStore} mirror.\n */\n readonly store: W extends true ? WritableSignalStore<T> : SignalStore<T>;\n /** The replica root value (undefined before hydration). */\n readonly value: Signal<T | undefined>;\n readonly status: Signal<ResourceStatus>;\n readonly error: Signal<unknown>;\n readonly isLoading: Signal<boolean>;\n /** The underlying worker connection's liveness. */\n readonly connected: Signal<boolean>;\n hasValue(): boolean;\n destroy(): void;\n} & (W extends true ? WorkerStoreWrite<T> : object);\n\n/**\n * A live replica of a store subtree OWNED by the worker. Subscribes over the {@link WorkerRef},\n * hydrates from the owner's snapshot, then folds each authoritative op batch into a main-thread\n * `store` through an echo-free `opLog.apply`. For an owned subtree the store is WRITABLE: a write\n * applies optimistically and its diff routes to the owner (which reconciles it back), and any\n * op-log reader — `meshSync`, `persist` — can attach to the same store, so a persisted, meshed,\n * worker-owned graph is just extra readers. Satisfies `ResourceLike`/`UseSource`, so it participates\n * in transition scopes and nests in `latest()`/`use()`.\n */\nexport function workerStore<M extends WorkerSchema, K extends StoreKeys<M>>(\n worker: WorkerRef<M>,\n key: K,\n opt?: WorkerStoreOptions<StoreValueOf<M, K> & object>,\n): WorkerStoreRef<StoreValueOf<M, K> & object, IsWritableKey<M, K>>;\nexport function workerStore<T extends object>(\n worker: WorkerRef,\n key: string,\n opt?: WorkerStoreOptions<T>,\n): WorkerStoreRef<T, true>;\nexport function workerStore(\n worker: WorkerRef,\n key: string,\n opt?: WorkerStoreOptions<any>,\n): WorkerStoreRef<any, boolean> {\n const injector = opt?.injector ?? inject(Injector);\n return runInInjectionContext(injector, () => build(worker, key, opt));\n}\n\nfunction build<T extends object>(\n worker: WorkerRef,\n key: string,\n opt?: WorkerStoreOptions<T>,\n): WorkerStoreRef<T, true> {\n const injector = inject(Injector);\n const root = signal<T | undefined>(opt?.defaultValue);\n const s = toStore(root as unknown as WritableSignal<T>, {\n injector,\n }) as unknown as WritableSignalStore<T>;\n const log = opLog(root as unknown as WritableSignal<T>, { injector });\n\n const status = signal<ResourceStatus>('loading');\n const error = signal<unknown>(undefined);\n const isLoading = computed(() => {\n const st = status();\n return st === 'loading' || st === 'reloading';\n });\n let hasSnapshot = false;\n let resyncing = false;\n let lastVersion = 0;\n let writeSeq = 0;\n const writePending = new Map<\n number,\n {\n version: number | null;\n resolve?: () => void;\n reject?: (e: unknown) => void;\n }\n >();\n\n const hasValue = () => hasSnapshot;\n\n const isOwned = () => worker.manifest()?.stores.includes(key) ?? false;\n\n const settleWrites = () => {\n for (const [id, p] of writePending) {\n if (p.version !== null && lastVersion >= p.version) {\n writePending.delete(id);\n p.resolve?.();\n }\n }\n };\n\n const shipLocal = (ops: readonly StoreOp[]): void => {\n if (!ops.length || !isOwned() || !untracked(worker.connected)) return;\n const id = ++writeSeq;\n writePending.set(id, { version: null });\n worker._send({\n type: 'store:write',\n store: key,\n writeId: id,\n clientId: worker.clientId,\n ops,\n });\n };\n const shipUnsub = log.subscribe((batch) => shipLocal(batch.ops));\n\n const onStoreMessage = (msg: WorkerEnvelope): void => {\n if (!('store' in msg) || msg.store !== key) return;\n switch (msg.type) {\n case 'store:snapshot': {\n log.apply([{ kind: 'set', path: [], next: msg.value }]);\n lastVersion = msg.version;\n hasSnapshot = true;\n resyncing = false;\n status.set('resolved');\n error.set(undefined);\n settleWrites();\n return;\n }\n case 'store:ops': {\n if (!hasSnapshot || resyncing) return;\n if (msg.batch.version <= lastVersion) return;\n if (msg.batch.version > lastVersion + 1) {\n resync(); // gap: re-hydrate rather than apply out of order\n return;\n }\n log.apply(msg.batch.ops);\n lastVersion = msg.batch.version;\n settleWrites();\n return;\n }\n case 'store:write:ack': {\n const p = writePending.get(msg.writeId);\n if (!p) return;\n if (lastVersion >= msg.version) {\n writePending.delete(msg.writeId);\n p.resolve?.();\n } else {\n p.version = msg.version;\n }\n return;\n }\n case 'store:write:error': {\n const p = writePending.get(msg.writeId);\n if (p) {\n writePending.delete(msg.writeId);\n p.reject?.(deserializeError(msg.error));\n }\n return;\n }\n case 'store:status': {\n switch (msg.status) {\n case 'error':\n error.set(\n msg.error\n ? deserializeError(msg.error)\n : new Error('remote computation error'),\n );\n status.set('error');\n return;\n case 'loading':\n case 'reloading':\n error.set(undefined);\n status.set(hasSnapshot ? 'reloading' : 'loading');\n return;\n case 'resolved':\n error.set(undefined);\n if (hasSnapshot) status.set('resolved');\n return;\n default:\n return;\n }\n }\n }\n };\n\n const subscribe = (): void => {\n if (hasSnapshot) status.set('reloading');\n worker._send({\n type: 'store:subscribe',\n store: key,\n clientId: worker.clientId,\n });\n };\n\n const resync = (): void => {\n resyncing = true;\n status.set('reloading');\n worker._send({\n type: 'store:subscribe',\n store: key,\n clientId: worker.clientId,\n });\n };\n\n const unsub = worker._subscribe(onStoreMessage);\n const offReady = worker._onReady(subscribe);\n if (untracked(worker.connected)) subscribe(); // already up (created post-handshake): _onReady won't fire\n const offDisconnect = worker._onDisconnect(() => {\n for (const [, p] of writePending) p.reject?.(new WorkerCrashedError());\n writePending.clear();\n });\n\n const self: WorkerStoreRef<T> = {\n store: s,\n value: root.asReadonly(),\n status,\n error,\n isLoading,\n connected: worker.connected,\n hasValue,\n write: (recipe) => {\n const base = untracked(root);\n if (base === undefined)\n return Promise.reject(\n new Error(\n '[@mmstack/worker] cannot write before the replica has hydrated',\n ),\n );\n if (!untracked(worker.connected))\n return Promise.reject(\n new WorkerCrashedError('worker is not connected'),\n );\n if (!isOwned())\n return Promise.reject(\n new Error(`[@mmstack/worker] store is read-only (published): ${key}`),\n );\n recipe(s);\n const before = writeSeq;\n log.flush();\n if (writeSeq === before) return Promise.resolve();\n const writeId = writeSeq;\n return new Promise<void>((resolve, reject) => {\n const p = writePending.get(writeId);\n if (!p) return resolve();\n p.resolve = resolve;\n p.reject = reject;\n });\n },\n destroy: () => {\n shipUnsub();\n log.destroy();\n unsub();\n offReady();\n offDisconnect();\n for (const [, p] of writePending)\n p.reject?.(new WorkerAbortError('worker store destroyed'));\n writePending.clear();\n worker._send({\n type: 'store:unsubscribe',\n store: key,\n clientId: worker.clientId,\n });\n },\n };\n\n inject(DestroyRef).onDestroy(() => self.destroy());\n\n if (opt?.register) {\n const scope = injectTransitionScope();\n scope.add(self, { suspends: opt.register === 'suspend' });\n let removed = false;\n const remove = () => {\n if (removed) return;\n removed = true;\n scope.remove(self);\n };\n inject(DestroyRef).onDestroy(remove);\n const destroy = self.destroy;\n self.destroy = () => {\n remove();\n destroy();\n };\n }\n\n return self;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["build"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mmstack-worker.mjs","sources":["../../../../packages/worker/src/lib/connect-worker.ts","../../../../packages/worker/src/lib/worker-resource.ts","../../../../packages/worker/src/lib/worker-store.ts","../../../../packages/worker/src/mmstack-worker.ts"],"sourcesContent":["import {\n DestroyRef,\n inject,\n Injector,\n PLATFORM_ID,\n runInInjectionContext,\n signal,\n type Signal,\n} from '@angular/core';\nimport {\n closePort,\n deserializeError,\n generateId,\n takeTransferables,\n type HasSchema,\n type SchemaOf,\n type TaskInput,\n type TaskOutput,\n type WorkerEnvelope,\n type WorkerPortLike,\n type WorkerSchema,\n} from '@mmstack/worker/protocol';\n\n/** What the worker advertised in its `ready` handshake. */\nexport type WorkerManifest = {\n readonly hostId: string;\n readonly stores: readonly string[];\n readonly published: readonly string[];\n readonly tasks: readonly string[];\n};\n\n/** Thrown into a task/write promise when it is aborted. `name` is `'AbortError'` (DOM convention). */\nexport class WorkerAbortError extends Error {\n constructor(message = 'The worker task was aborted') {\n super(message);\n this.name = 'AbortError';\n }\n}\n\n/** Thrown into pending tasks/writes when the worker crashes. `name` is `'WorkerCrashedError'`. */\nexport class WorkerCrashedError extends Error {\n constructor(message = 'The worker crashed') {\n super(message);\n this.name = 'WorkerCrashedError';\n }\n}\n\nexport type ConnectWorkerOptions = {\n readonly injector?: Injector;\n /** `'auto'` (default) respawns on crash; `'manual'` surfaces disconnect and stops. */\n readonly restart?: 'auto' | 'manual';\n /** Backoff before respawn attempt `n` (0-based). Default exponential 1s→30s. */\n readonly restartDelay?: (attempt: number) => number;\n};\n\nexport type WorkerRef<M extends WorkerSchema = WorkerSchema> = HasSchema<M> & {\n /** True once the `ready` handshake has completed (and again after an auto-restart). */\n readonly connected: Signal<boolean>;\n /** The worker's advertised manifest, or `null` before the first `ready`. */\n readonly manifest: Signal<WorkerManifest | null>;\n /** Run a named task exposed by the worker host; resolves with its result (typed from the schema). */\n runTask<K extends keyof M['tasks'] & string>(\n task: K,\n input: TaskInput<M, K>,\n opt?: { signal?: AbortSignal; transfer?: Transferable[] },\n ): Promise<TaskOutput<M, K>>;\n destroy(): void;\n\n /** @internal Post an envelope to the worker. */\n _send(msg: WorkerEnvelope, transfer?: Transferable[]): void;\n /** @internal Receive every non-task envelope (store traffic). Returns an unsubscribe. */\n _subscribe(handler: (msg: WorkerEnvelope) => void): () => void;\n /** @internal Run `fn` on every (re)connection — used to re-subscribe stores after a restart. */\n _onReady(fn: () => void): () => void;\n /** @internal Run `fn` when the connection drops (crash) — replicas reject pending writes here. */\n _onDisconnect(fn: () => void): () => void;\n /** @internal This client's identity on the transport. */\n readonly clientId: string;\n};\n\n/**\n * Connects the main thread to a worker over a {@link WorkerPortLike} the caller provides via `spawn`\n * — typically `() => new Worker(new URL('./my.worker', import.meta.url), { type: 'module' })` (the\n * URL literal must live in APP code so the bundler emits the worker chunk). Performs the hello/ready\n * handshake, exposes `connected`/`manifest`, routes named-task runs, and (via internal seams)\n * carries the store-replication traffic for {@link workerStore}. On crash it respawns with backoff\n * when `restart: 'auto'`.\n */\nexport function connectWorker<H = WorkerSchema>(\n spawn: () => WorkerPortLike,\n opt?: ConnectWorkerOptions,\n): WorkerRef<SchemaOf<H>> {\n const injector = opt?.injector ?? inject(Injector);\n return runInInjectionContext(injector, () =>\n build(spawn, opt),\n ) as unknown as WorkerRef<SchemaOf<H>>;\n}\n\nfunction build(\n spawn: () => WorkerPortLike,\n opt?: ConnectWorkerOptions,\n): WorkerRef {\n const isServer = inject(PLATFORM_ID) === 'server';\n const clientId = generateId();\n const connected = signal(false);\n const manifest = signal<WorkerManifest | null>(null);\n\n const taskPending = new Map<number, { resolve: (v: any) => void; reject: (e: unknown) => void }>();\n const storeHandlers = new Set<(msg: WorkerEnvelope) => void>();\n const readyHooks = new Set<() => void>();\n const disconnectHooks = new Set<() => void>();\n const restart = opt?.restart ?? 'auto';\n const restartDelay = opt?.restartDelay ?? ((n: number) => Math.min(30_000, 1000 * 2 ** n));\n let attempt = 0;\n let runIdSeq = 0;\n let destroyed = false;\n let crashed = false;\n let port: WorkerPortLike = null as unknown as WorkerPortLike;\n\n const send = (msg: WorkerEnvelope, transfer?: Transferable[]): void => {\n if (port) port.postMessage(msg, transfer);\n };\n\n const onMessage = (msg: WorkerEnvelope): void => {\n switch (msg.type) {\n case 'ready':\n attempt = 0;\n crashed = false;\n manifest.set({\n hostId: msg.hostId,\n stores: msg.stores,\n published: msg.published,\n tasks: msg.tasks,\n });\n connected.set(true);\n for (const hook of readyHooks) hook();\n return;\n case 'task:ok': {\n const p = taskPending.get(msg.runId);\n if (p) {\n taskPending.delete(msg.runId);\n p.resolve(msg.value);\n }\n return;\n }\n case 'task:error': {\n const p = taskPending.get(msg.runId);\n if (p) {\n taskPending.delete(msg.runId);\n p.reject(deserializeError(msg.error));\n }\n return;\n }\n case 'task:aborted':\n taskPending.delete(msg.runId);\n return;\n default:\n for (const h of storeHandlers) h(msg);\n }\n };\n\n const handleCrash = (): void => {\n if (destroyed) return;\n crashed = true;\n connected.set(false);\n for (const [, p] of taskPending) p.reject(new WorkerCrashedError());\n taskPending.clear();\n for (const fn of disconnectHooks) fn();\n if (restart === 'manual') return;\n const delay = restartDelay(attempt++);\n setTimeout(() => {\n if (!destroyed) openPort();\n }, delay);\n };\n\n const openPort = (): void => {\n if (port) closePort(port);\n port = spawn();\n port.onmessage = (ev) => onMessage(ev.data as WorkerEnvelope);\n // crash detection via property setters, not addEventListener (perturbs node MessagePort delivery)\n const evt = port as unknown as {\n onerror?: ((e?: unknown) => void) | null;\n onmessageerror?: ((e?: unknown) => void) | null;\n };\n if ('onerror' in evt) evt.onerror = handleCrash;\n if ('onmessageerror' in evt) evt.onmessageerror = handleCrash;\n send({ type: 'hello', proto: 1, clientId });\n };\n\n if (!isServer) openPort();\n\n inject(DestroyRef).onDestroy(() => teardown());\n\n const teardown = (): void => {\n destroyed = true;\n connected.set(false);\n for (const [, p] of taskPending) p.reject(new WorkerAbortError('worker connection destroyed'));\n taskPending.clear();\n for (const fn of disconnectHooks) fn();\n if (port) closePort(port);\n };\n\n return {\n connected,\n manifest,\n clientId,\n runTask(\n task: string,\n input: unknown,\n o?: { signal?: AbortSignal; transfer?: Transferable[] },\n ): Promise<unknown> {\n if (isServer) return Promise.reject(new WorkerCrashedError('no worker on the server'));\n if (destroyed) return Promise.reject(new WorkerAbortError('worker connection destroyed'));\n if (crashed) return Promise.reject(new WorkerCrashedError('worker is restarting'));\n if (o?.signal?.aborted) return Promise.reject(new WorkerAbortError());\n const runId = ++runIdSeq;\n return new Promise<unknown>((resolve, reject) => {\n taskPending.set(runId, { resolve, reject });\n send(\n { type: 'task:run', runId, task, input },\n o?.transfer ?? takeTransferables(input),\n );\n o?.signal?.addEventListener(\n 'abort',\n () => {\n if (!taskPending.has(runId)) return;\n taskPending.delete(runId);\n send({ type: 'task:abort', runId });\n reject(new WorkerAbortError());\n },\n { once: true },\n );\n });\n },\n destroy: teardown,\n _send: send,\n _subscribe(handler) {\n storeHandlers.add(handler);\n return () => storeHandlers.delete(handler);\n },\n _onReady(fn) {\n readyHooks.add(fn);\n return () => readyHooks.delete(fn);\n },\n _onDisconnect(fn) {\n disconnectHooks.add(fn);\n return () => disconnectHooks.delete(fn);\n },\n };\n}\n","import {\n computed,\n DestroyRef,\n effect,\n inject,\n Injector,\n PLATFORM_ID,\n runInInjectionContext,\n signal,\n untracked,\n type ResourceStatus,\n type Signal,\n type ValueEqualityFn,\n} from '@angular/core';\nimport { injectTransitionScope } from '@mmstack/primitives';\nimport { WorkerAbortError, type WorkerRef } from './connect-worker';\n\n/** Returned from a paused `params` fn to HOLD (keep the current value/status, run nothing). */\nexport const PAUSED: unique symbol = Symbol('@mmstack/worker:PAUSED');\n\nexport type WorkerRequestContext = { readonly paused: typeof PAUSED };\n\n/**\n * Reactive input producer. Reads signals to derive the task input; return `undefined` to disable\n * (idle, no run), or `ctx.paused` to hold. Re-runs when the returned input changes (by `Object.is`).\n */\nexport type WorkerParamsFn<TInput> = (\n ctx: WorkerRequestContext,\n) => TInput | undefined | void | typeof PAUSED;\n\nexport type WorkerResourceOptions<TResult> = {\n readonly injector?: Injector;\n /** Equality for the result value — a run resolving equal to the held value emits no notification. */\n readonly equal?: ValueEqualityFn<TResult>;\n readonly defaultValue?: TResult;\n /**\n * Hold the previous value through a re-run (status `'reloading'`) instead of clearing it. Default\n * TRUE — a `workerResource` is an async derivation, not a fetch; flashing empty mid-recompute is\n * rarely wanted.\n */\n readonly keepPrevious?: boolean;\n /** Register into the nearest transition scope: `'indicator'` drives pending, `'suspend'` also gates first paint. */\n readonly register?: false | 'indicator' | 'suspend';\n /** The connected worker whose host exposes the task. */\n readonly worker: WorkerRef;\n /** The name of the task to run (as declared in `createWorkerHost({ tasks })`). */\n readonly task: string;\n};\n\nexport type WorkerResourceRef<T> = {\n /** The latest successfully-computed value (held through re-runs per `keepPrevious`). */\n readonly value: Signal<T | undefined>;\n readonly status: Signal<ResourceStatus>;\n readonly error: Signal<unknown>;\n readonly isLoading: Signal<boolean>;\n hasValue(): boolean;\n /** Re-run with the current input, bypassing input de-duplication. */\n reload(): void;\n /** Cancel the in-flight run; the value is KEPT and status becomes `'local'`. */\n abort(): void;\n destroy(): void;\n};\n\n/**\n * Runs a named task on a connected worker, exposed with the standard resource surface so heavy\n * compute makes the UI *pending*, not *frozen*. `params` reactively derives the input; latest-wins\n * (a changed input supersedes and aborts the in-flight run); the result surfaces as\n * `value`/`status`/`error`, and — with `register` — participates in transition scopes. No-ops on the\n * server.\n */\nexport function workerResource<TInput, TResult>(\n params: WorkerParamsFn<TInput>,\n options: WorkerResourceOptions<TResult>,\n): WorkerResourceRef<TResult> {\n const injector = options.injector ?? inject(Injector);\n return runInInjectionContext(injector, () =>\n build<TInput, TResult>(params, options),\n );\n}\n\nfunction build<TInput, TResult>(\n params: WorkerParamsFn<TInput>,\n options: WorkerResourceOptions<TResult>,\n): WorkerResourceRef<TResult> {\n const isServer = inject(PLATFORM_ID) === 'server';\n const keepPrevious = options.keepPrevious ?? true;\n const userEqual = options.equal;\n const { worker, task } = options;\n\n const runTask = (input: TInput, signal: AbortSignal): Promise<TResult> =>\n worker.runTask(task, input, { signal }) as Promise<TResult>;\n\n const value = signal<TResult | undefined>(options.defaultValue, {\n equal: userEqual\n ? (a, b) =>\n a === undefined || b === undefined ? a === b : userEqual(a, b)\n : undefined,\n });\n const status = signal<ResourceStatus>('idle');\n const error = signal<unknown>(undefined);\n const isLoading = computed(() => {\n const s = status();\n return s === 'loading' || s === 'reloading';\n });\n const hasValue = () => value() !== undefined;\n\n let epoch = 0;\n let inFlight: AbortController | null = null;\n let lastInput: TInput | undefined;\n let hasRun = false;\n\n const start = (input: TInput): void => {\n const myEpoch = ++epoch;\n inFlight?.abort();\n const ac = new AbortController();\n inFlight = ac;\n lastInput = input;\n hasRun = true;\n status.set(keepPrevious && value() !== undefined ? 'reloading' : 'loading');\n error.set(undefined);\n\n runTask(input, ac.signal).then(\n (result) => {\n if (inFlight === ac) inFlight = null;\n if (myEpoch !== epoch) return;\n value.set(result);\n status.set('resolved');\n },\n (err) => {\n if (inFlight === ac) inFlight = null;\n if (myEpoch !== epoch) return;\n if (\n err instanceof WorkerAbortError ||\n (err as { name?: string })?.name === 'AbortError'\n )\n return;\n error.set(err);\n status.set('error');\n },\n );\n };\n\n const input = computed(() => params({ paused: PAUSED }));\n\n const ref = effect(() => {\n const i = input();\n if (isServer || i === PAUSED) return;\n if (i === undefined) return;\n if (Object.is(i, lastInput) && hasRun && untracked(status) !== 'error')\n return;\n untracked(() => start(i as TInput));\n });\n\n inject(DestroyRef).onDestroy(() => {\n epoch++;\n inFlight?.abort();\n });\n\n const self: WorkerResourceRef<TResult> = {\n value,\n status,\n error,\n isLoading,\n hasValue,\n reload: () => {\n const i = untracked(input);\n if (isServer || i === PAUSED || i === undefined) return;\n start(i as TInput);\n },\n abort: () => {\n if (!inFlight) return;\n epoch++;\n inFlight.abort();\n inFlight = null;\n status.set('local');\n },\n destroy: () => {\n epoch++;\n inFlight?.abort();\n ref.destroy();\n },\n };\n\n if (options.register) {\n const scope = injectTransitionScope();\n scope.add(self, { suspends: options.register === 'suspend' });\n let removed = false;\n const remove = () => {\n if (removed) return;\n removed = true;\n scope.remove(self);\n };\n inject(DestroyRef).onDestroy(remove);\n const destroy = self.destroy;\n self.destroy = () => {\n remove();\n destroy();\n };\n }\n\n return self;\n}\n","import {\n computed,\n DestroyRef,\n inject,\n Injector,\n runInInjectionContext,\n signal,\n untracked,\n type ResourceStatus,\n type Signal,\n type WritableSignal,\n} from '@angular/core';\nimport { createWatch } from '@angular/core/primitives/signals';\nimport {\n injectTransitionScope,\n opSync,\n toStore,\n type OpEnvelope,\n type OpLogDriver,\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\n// An injector-free opLog driver (schedules emission off the microtask queue via `createWatch`), so\n// the replica's `opSync` emits without depending on an application tick. Writes force a synchronous\n// `flush()` at the call site; between writes, owner envelopes drive apply, never local emission.\nconst microtaskDriver = (): OpLogDriver => (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\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 a\n * stamped op envelope, and the owner folds it in and echoes it back. Resolves once the owner has\n * acknowledged it (its echo reconciles this replica). The owner can override with a higher-epoch\n * correction that wins the merge. To hide the value until the owner confirms it, fork the store\n * and reveal on resolve. Rejects if the worker is disconnected or the store is read-only.\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. It runs its own `opSync` over the worker\n * transport: it hydrates from the owner's checkpoint, then folds each owner (or peer) envelope into\n * a main-thread `store` convergently. For an owned subtree the store is WRITABLE: a write applies\n * optimistically and its stamped envelope routes to the owner (which folds it and echoes it back),\n * and any op-log reader — `meshSync`, `persist` — can attach to the same store, so a persisted,\n * meshed, worker-owned graph is just extra readers. Satisfies `ResourceLike`/`UseSource`, so it\n * participates 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 // The replica runs its own opSync over the worker transport: local writes emit stamped envelopes\n // routed to the owner, owner envelopes fold in convergently. A driver (not the injector) so\n // emission stays synchronous under an explicit `flush()`, matching the DI-free worker model.\n const sync = opSync(root as unknown as WritableSignal<T>, {\n writer: worker.clientId,\n driver: microtaskDriver(),\n onGap: () => resync(),\n });\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 lastEmitted = 0; // highest own-origin version emitted; the write() ack key\n // own writes awaiting the owner's echo (resolve), plus the raw envelopes to resend after a resync\n const writePending = new Map<\n number,\n { resolve: () => void; reject: (e: unknown) => void }\n >();\n const unacked = new Map<number, OpEnvelope>();\n\n const hasValue = () => hasSnapshot;\n\n const isOwned = () => worker.manifest()?.stores.includes(key) ?? false;\n\n const ackUpTo = (version: number): void => {\n for (const [v, p] of writePending) {\n if (v <= version) {\n writePending.delete(v);\n p.resolve();\n }\n }\n for (const v of [...unacked.keys()]) if (v <= version) unacked.delete(v);\n };\n\n const shipUnsub = sync.subscribe((env) => {\n lastEmitted = env.version;\n unacked.set(env.version, env);\n if (isOwned() && untracked(worker.connected)) {\n worker._send({ type: 'store:sync', store: key, env });\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:checkpoint': {\n // rebase from the full unacked outbox, not opSync's bounded recent-local ring, so a burst\n // of writes larger than that ring is never dropped from the local rebase on re-hydrate\n sync.hydrate(msg.checkpoint as Parameters<typeof sync.hydrate>[0], [\n ...unacked.values(),\n ]);\n hasSnapshot = true;\n status.set('resolved');\n error.set(undefined);\n // writes the owner already applied are covered by the checkpoint watermark; resolve them,\n // then resend any still-unacked tail so a write made before the (re)hydrate is never lost\n ackUpTo(msg.checkpoint.wm?.[sync.origin] ?? 0);\n if (isOwned() && untracked(worker.connected)) {\n for (const env of [...unacked.values()].sort(\n (a, b) => a.version - b.version,\n )) {\n worker._send({ type: 'store:sync', store: key, env });\n }\n }\n return;\n }\n case 'store:sync': {\n const env = msg.env;\n if (env.origin === sync.origin) {\n ackUpTo(env.version); // the owner echoed our own write back: acknowledgement\n return;\n }\n sync.receive(env); // owner or peer envelope; a version gap triggers onGap -> resync\n return;\n }\n case 'store:status': {\n switch (msg.status) {\n case 'error':\n error.set(\n msg.error\n ? deserializeError(msg.error)\n : new Error('remote computation error'),\n );\n status.set('error');\n return;\n case 'loading':\n case 'reloading':\n error.set(undefined);\n status.set(hasSnapshot ? 'reloading' : 'loading');\n return;\n case 'resolved':\n error.set(undefined);\n if (hasSnapshot) status.set('resolved');\n return;\n default:\n return;\n }\n }\n }\n };\n\n const subscribe = (): void => {\n if (hasSnapshot) status.set('reloading');\n worker._send({\n type: 'store:subscribe',\n store: key,\n clientId: worker.clientId,\n });\n };\n\n const resync = (): void => {\n status.set('reloading');\n worker._send({\n type: 'store:subscribe',\n store: key,\n clientId: worker.clientId,\n });\n };\n\n const unsub = worker._subscribe(onStoreMessage);\n const offReady = worker._onReady(subscribe);\n if (untracked(worker.connected)) subscribe(); // already up (created post-handshake): _onReady won't fire\n const offDisconnect = worker._onDisconnect(() => {\n for (const [, p] of writePending) p.reject(new WorkerCrashedError());\n writePending.clear();\n unacked.clear();\n });\n\n const self: WorkerStoreRef<T> = {\n store: s,\n value: root.asReadonly(),\n status,\n error,\n isLoading,\n connected: worker.connected,\n hasValue,\n write: (recipe) => {\n const base = untracked(root);\n if (base === undefined)\n return Promise.reject(\n new Error(\n '[@mmstack/worker] cannot write before the replica has hydrated',\n ),\n );\n if (!untracked(worker.connected))\n return Promise.reject(\n new WorkerCrashedError('worker is not connected'),\n );\n if (!isOwned())\n return Promise.reject(\n new Error(`[@mmstack/worker] store is read-only (published): ${key}`),\n );\n recipe(s);\n const before = lastEmitted;\n sync.flush(); // emit the routed write synchronously (driver-backed opSync)\n if (lastEmitted === before) return Promise.resolve(); // no-op recipe: nothing shipped\n const version = lastEmitted;\n return new Promise<void>((resolve, reject) => {\n writePending.set(version, { resolve, reject });\n });\n },\n destroy: () => {\n shipUnsub();\n sync.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 unacked.clear();\n worker._send({\n type: 'store:unsubscribe',\n store: key,\n clientId: worker.clientId,\n });\n },\n };\n\n inject(DestroyRef).onDestroy(() => self.destroy());\n\n if (opt?.register) {\n const scope = injectTransitionScope();\n scope.add(self, { suspends: opt.register === 'suspend' });\n let removed = false;\n const remove = () => {\n if (removed) return;\n removed = true;\n scope.remove(self);\n };\n inject(DestroyRef).onDestroy(remove);\n const destroy = self.destroy;\n self.destroy = () => {\n remove();\n destroy();\n };\n }\n\n return self;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["build"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmstack/worker",
|
|
3
|
-
"version": "21.
|
|
3
|
+
"version": "21.2.0",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"angular",
|
|
6
6
|
"signals",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@angular/core": ">=21 <22",
|
|
21
21
|
"@angular/common": ">=21 <22",
|
|
22
|
-
"@mmstack/primitives": ">=21.
|
|
22
|
+
"@mmstack/primitives": ">=21.8 <22"
|
|
23
23
|
},
|
|
24
24
|
"sideEffects": false,
|
|
25
25
|
"module": "fesm2022/mmstack-worker.mjs",
|
|
@@ -41,6 +41,13 @@ type WorkerHost<M extends WorkerSchema = WorkerSchema> = HasSchema<M> & {
|
|
|
41
41
|
readonly hostId: string;
|
|
42
42
|
/** Serve an additional transport (multi-client / tests). Returns a disconnect handle. */
|
|
43
43
|
connect(port: WorkerPortLike): () => void;
|
|
44
|
+
/**
|
|
45
|
+
* Apply an AUTHORITATIVE owner correction to an owned store: writes made inside `fn` are stamped
|
|
46
|
+
* at a bumped epoch, so they deterministically win the merge against any concurrent replica write
|
|
47
|
+
* (owner authority rides the epoch fold). Readers can gate effects on owner-settled values by the
|
|
48
|
+
* winning op's epoch. No-op for a read-only (published) or unknown store.
|
|
49
|
+
*/
|
|
50
|
+
override(store: string, fn: () => void): void;
|
|
44
51
|
/**
|
|
45
52
|
* Synchronously emit any pending owned-store changes to subscribers NOW, instead of waiting for
|
|
46
53
|
* the microtask driver. Makes the mirror deterministic (tests call it before asserting) and is
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OpSyncCheckpoint, OpEnvelope } from '@mmstack/primitives';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A structured-clone-safe rendering of an `Error` for cross-thread propagation. A worker throw
|
|
@@ -64,30 +64,13 @@ type WorkerEnvelope = {
|
|
|
64
64
|
store: string;
|
|
65
65
|
clientId: string;
|
|
66
66
|
} | {
|
|
67
|
-
type: 'store:
|
|
67
|
+
type: 'store:checkpoint';
|
|
68
68
|
store: string;
|
|
69
|
-
|
|
70
|
-
value: unknown;
|
|
71
|
-
} | {
|
|
72
|
-
type: 'store:ops';
|
|
73
|
-
store: string;
|
|
74
|
-
batch: OpBatch;
|
|
69
|
+
checkpoint: OpSyncCheckpoint;
|
|
75
70
|
} | {
|
|
76
|
-
type: 'store:
|
|
71
|
+
type: 'store:sync';
|
|
77
72
|
store: string;
|
|
78
|
-
|
|
79
|
-
clientId: string;
|
|
80
|
-
ops: readonly StoreOp[];
|
|
81
|
-
} | {
|
|
82
|
-
type: 'store:write:ack';
|
|
83
|
-
store: string;
|
|
84
|
-
writeId: number;
|
|
85
|
-
version: number;
|
|
86
|
-
} | {
|
|
87
|
-
type: 'store:write:error';
|
|
88
|
-
store: string;
|
|
89
|
-
writeId: number;
|
|
90
|
-
error: SerializedError;
|
|
73
|
+
env: OpEnvelope;
|
|
91
74
|
} | {
|
|
92
75
|
type: 'store:unsubscribe';
|
|
93
76
|
store: string;
|
|
@@ -117,10 +117,11 @@ 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: `recipe` applies optimistically to the store, its diff ships as
|
|
121
|
-
* and the owner
|
|
122
|
-
*
|
|
123
|
-
*
|
|
120
|
+
* Route a write to the OWNER: `recipe` applies optimistically to the store, its diff ships as a
|
|
121
|
+
* stamped op envelope, and the owner folds it in and echoes it back. Resolves once the owner has
|
|
122
|
+
* acknowledged it (its echo reconciles this replica). The owner can override with a higher-epoch
|
|
123
|
+
* correction that wins the merge. To hide the value until the owner confirms it, fork the store
|
|
124
|
+
* and reveal on resolve. Rejects if the worker is disconnected or the store is read-only.
|
|
124
125
|
*/
|
|
125
126
|
write(recipe: (draft: WritableSignalStore<T>) => void): Promise<void>;
|
|
126
127
|
};
|
|
@@ -142,13 +143,13 @@ type WorkerStoreRef<T, W extends boolean = true> = {
|
|
|
142
143
|
destroy(): void;
|
|
143
144
|
} & (W extends true ? WorkerStoreWrite<T> : object);
|
|
144
145
|
/**
|
|
145
|
-
* A live replica of a store subtree OWNED by the worker.
|
|
146
|
-
* hydrates from the owner's
|
|
147
|
-
* `store`
|
|
148
|
-
*
|
|
149
|
-
* op-log reader — `meshSync`, `persist` — can attach to the same store, so a persisted,
|
|
150
|
-
* worker-owned graph is just extra readers. Satisfies `ResourceLike`/`UseSource`, so it
|
|
151
|
-
* in transition scopes and nests in `latest()`/`use()`.
|
|
146
|
+
* A live replica of a store subtree OWNED by the worker. It runs its own `opSync` over the worker
|
|
147
|
+
* transport: it hydrates from the owner's checkpoint, then folds each owner (or peer) envelope into
|
|
148
|
+
* a main-thread `store` convergently. For an owned subtree the store is WRITABLE: a write applies
|
|
149
|
+
* optimistically and its stamped envelope routes to the owner (which folds it and echoes it back),
|
|
150
|
+
* and any op-log reader — `meshSync`, `persist` — can attach to the same store, so a persisted,
|
|
151
|
+
* meshed, worker-owned graph is just extra readers. Satisfies `ResourceLike`/`UseSource`, so it
|
|
152
|
+
* participates in transition scopes and nests in `latest()`/`use()`.
|
|
152
153
|
*/
|
|
153
154
|
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>>;
|
|
154
155
|
declare function workerStore<T extends object>(worker: WorkerRef, key: string, opt?: WorkerStoreOptions<T>): WorkerStoreRef<T, true>;
|