You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: code/core/src/shared/open-service/README.md
+11-9Lines changed: 11 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -69,7 +69,7 @@ Internal tests and implementation code may import from the individual modules di
69
69
-[service-error-serialization.ts](./service-error-serialization.ts): transport-safe (de)serialization of thrown errors and their `cause` chains, used by remote command replies
70
70
-[channel-slot.ts](../../channels/channel-slot.ts): `getChannel` / `setChannel` — the shared channel install surface
71
71
-[service-transport.ts](./service-transport.ts): shared channel transport — wraps commands to broadcast, wires the sync-start initialization + patch listeners (hub or leaf), and runs the remote-command-execution protocol
72
-
-[service-sync.ts](./service-sync.ts): last-write-wins ordering, the `deepReconcile` structural merge, and the per-service snapshot reconciler
72
+
-[service-sync.ts](./service-sync.ts): last-write-wins ordering, `applyStatePatch` structural state application, and the per-service snapshot reconciler
73
73
-[use-service-query.ts](./use-service-query.ts): `useServiceQuery` React hook backed by `useSyncExternalStore`
74
74
-[use-service-command.ts](./use-service-command.ts): `useServiceCommand` React hook returning a stable command reference
75
75
-[fixtures.ts](./fixtures.ts): scenario fixtures used by the test suite
@@ -338,10 +338,10 @@ an arbitrary style choice:
338
338
-**Reactivity.** State is wrapped in a `deepSignal` proxy for fine-grained per-field tracking, and
339
339
`deepSignal` throws (`"this object can't be observed"`) on scalars, `null`, and `undefined` — there
340
340
are no fields to track on a scalar.
341
-
-**Sync.** Cross-peer reconciliation (`deepReconcile` in [service-sync.ts](./service-sync.ts)) merges
341
+
-**Sync.** Cross-peer reconciliation (`applyStatePatch` in [service-sync.ts](./service-sync.ts)) merges
342
342
state by walking object keys; it has no concept of replacing a whole scalar.
343
343
344
-
Arrays are a special case: `deepSignal`*can* observe them, but `deepReconcile` replaces arrays
344
+
Arrays are a special case: `deepSignal`*can* observe them, but `applyStatePatch` replaces arrays
345
345
wholesale rather than merging by key, so a **top-level** array state would silently fail to sync
346
346
between peers. They are therefore rejected too. Wrap collections in a field instead:
347
347
@@ -422,7 +422,7 @@ If multiple tasks resolve to the same path, their states are deep-merged.
422
422
423
423
`writeOpenServiceStaticFiles(outputDir)` then writes those logical paths underneath `<outputDir>/services`, converting slash-separated logical keys into native filesystem paths for the current operating system.
424
424
425
-
These snapshots are currently only a build artifact for the server-side static build flow. This slice does not implement a separate runtime mode that consumes prebuilt snapshot stores instead of running `load` normally.
425
+
In a static Storybook build (`CONFIG_TYPE === 'PRODUCTION'`), manager and preview runtimes pass a browser static loader into `registerService`. For every query that declares `staticPath`, the runtime swaps the authored `load` hook for a fetch of `./services/<serviceId>/<staticPath>` relative to the served HTML root (the same convention as `./index.json`), then applies the JSON snapshot via `applyStatePatch(..., { preserveMissingKeys: true })` so snapshots for one query input do not delete state populated by other inputs. Development mode keeps running `load` normally against the dev server.
426
426
427
427
Static path rules:
428
428
@@ -489,9 +489,9 @@ Every channel event carries the emitter's `clientId` (generated per `registerSer
489
489
490
490
Incoming state (from sync-start-reply or patches) is applied via `serviceRuntime.commandSelf.setState(...)` directly — not through the wrapped commands — so no broadcast is triggered for received state.
491
491
492
-
### `deepReconcile`
492
+
### `applyStatePatch`
493
493
494
-
Rather than replacing the entire state object on each patch (which would invalidate all signal subscriptions), `deepReconcile` (in [service-sync.ts](./service-sync.ts)) recursively merges plain-object values in place: arrays and primitives are replaced directly, keys absent from the snapshot are deletedso deletions propagate, and `__proto__`/`constructor`/`prototype` are skipped to block prototype pollution. This keeps fine-grained subscriptions on unaffected nested fields from firing spuriously.
494
+
Rather than replacing the entire state object on each patch (which would invalidate all signal subscriptions), `applyStatePatch` (in [service-sync.ts](./service-sync.ts)) recursively merges plain-object values in place: arrays and primitives are replaced directly, `__proto__`/`constructor`/`prototype` are skipped to block prototype pollution, and `preserveMissingKeys` controls whether missing keys are deleted. Cross-peer sync passes `false`so deletions propagate from full snapshots; static JSON loading passes `true` because each static file is a partial snapshot. This keeps fine-grained subscriptions on unaffected nested fields from firing spuriously.
495
495
496
496
### State sync sequence
497
497
@@ -635,9 +635,11 @@ The manager is connected to both the dev server and the preview, so it can invok
635
635
in either; but a preview cannot directly invoke a server-only command, and vice versa — route such
636
636
calls through the manager, or implement the command on a directly-connected peer.
637
637
638
-
There is **no timeout**. Invoking a command that no reachable peer implements leaves the promise
639
-
pending forever, until the service is unregistered — `disconnect` rejects every outstanding call with
640
-
`OpenServiceRemoteCommandDisconnectedError`.
638
+
If no peer emits `services:command-ack` within a short window, the requester
639
+
rejects with `OpenServiceRemoteCommandUnhandledError` — the common case in a static build when a
640
+
query's `load` calls a server-only command. Once a peer acknowledges, the requester waits for
641
+
`services:command-result` or `services:command-error` as before. Unregistering the service still
642
+
rejects outstanding calls with `OpenServiceRemoteCommandDisconnectedError`.
0 commit comments