Skip to content

Render child machines with MachineState and provide per-owner layers through createMachineContext - #243

Merged
SandroMaglione merged 1 commit into
mainfrom
claude/gifted-bardeen-c3ehlr
Oct 22, 2026
Merged

SandroMaglione merged 1 commit into
mainfrom
claude/gifted-bardeen-c3ehlr

Conversation

@SandroMaglione

Copy link
Copy Markdown
Member

Summary

  • MachineState accepts a child bridge (machine.child(Descriptor)) as well as a machine bridge. Paths and the children snapshot are typed by the child machine (ChildMachineAtom.State<A>), and inactive also renders while the state that owns the child is not active. MachineStateProps is now MachineStateProps<A, Path>, with MachineStateProps.Bridge and MachineStateProps.State<A>.
  • Export useMachineResult(atom): the non-suspending, hydration-safe reader behind MachineState (server value during SSR and hydration, keeps the last Success while the atom is Initial again) for custom readers of select, matches, can, and other result atoms.
  • createMachineContext factories can declare a second, Layer-typed parameter for per-owner capabilities. The Provider then requires a layer prop of exactly that type (and rejects it otherwise); like input, it is read once when the owner mounts. Factories that need a layer but no input declare the first parameter as void. New MachineContext.Args and MachineContext.ProviderProps<Args> types.
  • Add AtomMachine.isMachineAtom to tell machine bridges from child bridges; MachineState uses it to choose selectSnapshot or selectSnapshotChild.
  • Tests: child hydration (server-planned child state, DOM node identity and focus survive the parent starting, no console.error), inactive owner state renders inactive, useMachineResult never returns Initial after a Success during startup, per-owner layers (independent implementations per Provider, layer read once per React key). Typetests for child paths and snapshots, useMachineResult, layer required/rejected, and a layer missing the machine's service being a compile error.
  • Docs: React guide "Per-owner capabilities" gains the createMachineContext form; render and child sections document child bridges and useMachineResult; React README updated.

Changeset

  • Added or updated for a library or package-metadata change
  • Not required because this PR does not change a publishable package

minor for @typeonce/effect-machine and @typeonce/effect-machine-react (fixed group).

Validation

  • pnpm check (format check via dprint with the pinned plugin versions locally; packages/devtools/test/StaticSite.test.ts times out locally on unchanged main as well)
  • Automated type-performance measurement passed or was not required
  • Automated runtime- and memory-performance measurement passed or was not required

pnpm test:types, pnpm perf:types, and pnpm perf:runtime ran locally. The widened MachineState adds a negligible number of instantiations to the existing React typetest scenarios compared with main.


Generated by Claude Code

…through createMachineContext

MachineState accepts a child bridge and renders `inactive` while the owning
state is not active. useMachineResult exposes the hydration-safe result reader
for custom hooks. createMachineContext factories can declare a Layer parameter
that the Provider supplies through a typed `layer` prop. AtomMachine.isMachineAtom
tells machine bridges from child bridges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9xMXPkpeqmvAbWNGY7R55
@github-actions

Copy link
Copy Markdown
Contributor

Type performance

Measured with TypeScript 6.0.3 and skipLibCheck=true.

Scenario Base PR Difference
Effect only 55 55 0 (0.0%)
Import effect-machine 55 55 0 (0.0%)
Machine.state (3 children) 2,664 2,664 0 (0.0%)
Machine.make (3 states, 2 events) 8,932 8,932 0 (0.0%)
machine.handle (3 states, 2 transitions) 39,449 39,449 0 (0.0%)
declared transition (10 named branches) 60,402 60,402 0 (0.0%)
atomic construction, guards, and reentry 85,710 85,710 0 (0.0%)
registered invocation (input-taking Effect) 39,819 39,819 0 (0.0%)
machine.handle (depth 24) 194,531 194,531 0 (0.0%)
machine.handle (wide depth 16) 218,238 218,238 0 (0.0%)
machine.handle (parallel/history/choice) 91,642 91,642 0 (0.0%)
machine definition (3 independent implementations) 99,173 99,173 0 (0.0%)
machine exact input/output/error/services 82,941 82,941 0 (0.0%)
execution adapter readiness 71,428 71,428 0 (0.0%)

Marginal instantiations are measured against the matching setup without that API call:

Scenario Base PR Difference
Import effect-machine 0 0 0
Machine.state (3 children) 2,609 2,609 0 (0.0%)
Machine.make (3 states, 2 events) 6,260 6,260 0 (0.0%)
machine.handle (3 states, 2 transitions) 30,517 30,517 0 (0.0%)
declared transition (10 named branches) 51,849 51,849 0 (0.0%)
atomic construction, guards, and reentry 70,370 70,370 0 (0.0%)
registered invocation (input-taking Effect) 32,682 32,682 0 (0.0%)
machine.handle (depth 24) 177,222 177,222 0 (0.0%)
machine.handle (wide depth 16) 201,707 201,707 0 (0.0%)
machine.handle (parallel/history/choice) 79,144 79,144 0 (0.0%)
machine definition (3 independent implementations) 88,492 88,492 0 (0.0%)
machine exact input/output/error/services 70,732 70,732 0 (0.0%)
execution adapter readiness 31,922 31,922 0 (0.0%)
Check times (informational)
Scenario Base PR
Effect only 0.02s 0.02s
Import effect-machine 0.02s 0.02s
Machine.state (3 children) 0.06s 0.07s
Machine.make (3 states, 2 events) 0.10s 0.10s
machine.handle (3 states, 2 transitions) 0.22s 0.22s
declared transition (10 named branches) 0.30s 0.30s
atomic construction, guards, and reentry 0.37s 0.37s
registered invocation (input-taking Effect) 0.22s 0.23s
machine.handle (depth 24) 0.45s 0.45s
machine.handle (wide depth 16) 0.49s 0.49s
machine.handle (parallel/history/choice) 0.37s 0.37s
machine definition (3 independent implementations) 0.38s 0.38s
machine exact input/output/error/services 0.37s 0.34s
execution adapter readiness 0.32s 0.32s

Type instantiations are the comparison metric. Check time varies with runner load and is informational only.

@github-actions

Copy link
Copy Markdown
Contributor

Runtime performance

Median of 5 independent benchmark processes on AMD EPYC 7763 64-Core Processor with Node v24.21.0.

Pull request baseline

Scenario Effect Machine
Plan counter transitions 116,623 transitions/s
Drain burst with terminal fence 403,005 increments/s
Drain burst with a change observer 376,380 increments/s
Lookup and send to one child 329,677 increments/s
Start and stop a machine 117,841 machines/s
Start and stop a parent with one child 23,282 families/s
Complete an Effect invocation with tracing enabled 7,086 invocations/s
Complete an Effect invocation with tracing disabled 8,659 invocations/s
Plan transitions through a compound state 104,047 transitions/s
Plan transitions through parallel regions 82,084 transitions/s
Drain burst through a compound state 344,930 events/s
Drain burst through two parallel regions 317,580 events/s
Drain a compound-state burst with a change observer 320,324 events/s

Process runtime reference points

Scenario Effect Machine
Start and stop a raw generic process 19,867 processes/s
Start and stop a raw compiled process 69,266 processes/s
Memory profile Effect Machine
Idle machine 1.9 KiB
Raw generic managed process 11.7 KiB
Raw compiled process 3.1 KiB
Two independent idle machines 3.5 KiB
Idle parent with one child 6.0 KiB
Parent with observed child registry 9.5 KiB
Parent with observed invoked child snapshots 6.9 KiB

Effect Machine change from base

Metric Base Base variability PR PR variability Difference
Plan counter transitions 116,801 transitions/s 2.5% MAD 116,623 transitions/s 2.1% MAD -0.2%
Drain burst with terminal fence 406,167 increments/s 1.6% MAD 403,005 increments/s 1.2% MAD -0.8%
Drain burst with a change observer 382,133 increments/s 0.6% MAD 376,380 increments/s 0.4% MAD -1.5%
Lookup and send to one child 331,484 increments/s 1.2% MAD 329,677 increments/s 0.4% MAD -0.5%
Start and stop a machine 118,259 machines/s 1.6% MAD 117,841 machines/s 2.9% MAD -0.4%
Start and stop a parent with one child 23,976 families/s 1.1% MAD 23,282 families/s 1.1% MAD -2.9%
Complete an Effect invocation with tracing enabled 7,078 invocations/s 0.5% MAD 7,086 invocations/s 0.6% MAD +0.1%
Complete an Effect invocation with tracing disabled 8,543 invocations/s 1.3% MAD 8,659 invocations/s 0.7% MAD +1.4%
Plan transitions through a compound state 102,866 transitions/s 1.8% MAD 104,047 transitions/s 2.0% MAD +1.1%
Plan transitions through parallel regions 81,229 transitions/s 0.5% MAD 82,084 transitions/s 0.5% MAD +1.1%
Drain burst through a compound state 347,128 events/s 0.9% MAD 344,930 events/s 0.9% MAD -0.6%
Drain burst through two parallel regions 318,144 events/s 0.9% MAD 317,580 events/s 0.2% MAD -0.2%
Drain a compound-state burst with a change observer 325,145 events/s 0.3% MAD 320,324 events/s 0.3% MAD -1.5%
Idle machine heap per unit 1.9 KiB 1.4% MAD 1.9 KiB 0.3% MAD +0.0%
Raw generic managed process heap per unit 11.6 KiB 0.2% MAD 11.7 KiB 0.1% MAD +0.7%
Raw compiled process heap per unit 3.1 KiB 0.0% MAD 3.1 KiB 0.1% MAD -0.1%
Two independent idle machines heap per unit 3.5 KiB 0.0% MAD 3.5 KiB 0.0% MAD +0.0%
Idle parent with one child heap per unit 6.0 KiB 0.0% MAD 6.0 KiB 0.0% MAD 0.0%
Parent with observed child registry heap per unit 9.5 KiB 0.0% MAD 9.5 KiB 0.0% MAD -0.0%
Parent with observed invoked child snapshots heap per unit 6.9 KiB 0.1% MAD 6.9 KiB 0.0% MAD +0.1%

Process runtime reference change from base

Metric Base Base variability PR PR variability Difference
Start and stop a raw generic process 20,366 processes/s 0.9% MAD 19,867 processes/s 3.4% MAD -2.4%
Start and stop a raw compiled process 67,995 processes/s 1.0% MAD 69,266 processes/s 0.5% MAD +1.9%

Regression guard

No large, noise-adjusted throughput or heap regressions detected.

Versions and interpretation
  • Effect Machine: 0.42.0

Higher throughput is better; lower heap is better. Variability is the median absolute deviation across independent processes, relative to their median. Small differences on shared GitHub-hosted hardware remain informational; the required guard rejects only large changes beyond the measured noise allowance.

@SandroMaglione
SandroMaglione merged commit c9934ae into main Oct 22, 2026
9 checks passed
@SandroMaglione
SandroMaglione deleted the claude/gifted-bardeen-c3ehlr branch October 22, 2026 22:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants