Skip to content

Replace target references with declared state paths - #226

Merged
SandroMaglione merged 1 commit into
mainfrom
replace-target-references-with-paths
Sep 28, 2026
Merged

SandroMaglione merged 1 commit into
mainfrom
replace-target-references-with-paths

Conversation

@SandroMaglione

Copy link
Copy Markdown
Member

Summary

  • Transitions, initial edges, history, owner updates, and named branches take dotted path strings ("Checkout.Review") instead of Machine.targets(Root) references. Paths are spelled like snapshot paths, checked against the root passed to make, and completed by the editor. Machine.targets, Targets, and TargetReference are removed.
  • The root node is spelled "root" (e.g. update: "root"), so a top-level state named root is rejected at compile time and when make captures the definition. The root is not a destination.
  • initialize replaces { target: root, input } for reconstructing root from fresh machine input: a value or callback for machines with input, true otherwise. Branch declarations use { initialize: true } with select.branch({ input }). This also fixes reconstructing an input-less machine, which previously crashed while decoding a missing input schema.
  • Runtime validation resolves paths against the declared topology and reports undeclared paths, target: "root", and invalid initialize combinations.
  • Devtools renders declarations with paths. The lint rule no-async-planning-callback also checks initialize mappers. The runtime-benchmark compatibility layer supports both the reference and path APIs through a capability probe.
  • Tests, typetests, perf fixtures, README, guides, and JSDoc examples are migrated. Fixtures that used a top-level state named root now use main. New Paths runtime and type tests plus a forced generic-versus-optimized differential case cover input-less root initialization.

Trade-off: a declaration stored in a variable before it reaches make or .handle needs as const so its paths stay literal. This is documented in the README and in the changeset.

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, with direct migration steps. Patch for devtools and the oxlint plugin. Removed targets from the pending typed-operation-boundary changeset, since this release removes it.

Validation

  • pnpm check
  • Automated type-performance measurement passed or was not required
  • Automated runtime- and memory-performance measurement passed or was not required

Local pnpm perf:types and pnpm perf:runtime passed within the configured budgets. The CI base-versus-PR comparisons are authoritative.

🤖 Generated with Claude Code

Transitions, initial edges, history, owner updates, and named branches
take dotted path strings checked against the root passed to make. The
root is spelled "root", and `initialize` reconstructs it from fresh
machine input, replacing root targets and Machine.targets.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@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,632 8,932 +300 (+3.5%)
machine.handle (3 states, 2 transitions) 49,125 39,348 -9,777 (-19.9%)
declared transition (10 named branches) 62,183 60,165 -2,018 (-3.2%)
atomic construction, guards, and reentry 87,731 85,402 -2,329 (-2.7%)
registered invocation (input-taking Effect) 48,929 39,467 -9,462 (-19.3%)
machine.handle (depth 24) 196,525 194,526 -1,999 (-1.0%)
machine.handle (wide depth 16) 220,268 218,233 -2,035 (-0.9%)
machine.handle (parallel/history/choice) 94,249 91,633 -2,616 (-2.8%)
machine definition (3 independent implementations) 99,583 98,225 -1,358 (-1.4%)
machine exact input/output/error/services 84,918 82,791 -2,127 (-2.5%)
execution adapter readiness 72,521 71,416 -1,105 (-1.5%)

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) 5,960 6,260 +300 (+5.0%)
machine.handle (3 states, 2 transitions) 40,493 30,416 -10,077 (-24.9%)
declared transition (10 named branches) 53,930 51,612 -2,318 (-4.3%)
atomic construction, guards, and reentry 72,720 70,062 -2,658 (-3.7%)
registered invocation (input-taking Effect) 42,088 32,330 -9,758 (-23.2%)
machine.handle (depth 24) 179,530 177,217 -2,313 (-1.3%)
machine.handle (wide depth 16) 204,051 201,702 -2,349 (-1.2%)
machine.handle (parallel/history/choice) 82,065 79,135 -2,930 (-3.6%)
machine definition (3 independent implementations) 89,216 87,544 -1,672 (-1.9%)
machine exact input/output/error/services 73,038 70,582 -2,456 (-3.4%)
execution adapter readiness 23,180 31,918 +8,738 (+37.7%)
Check times (informational)
Scenario Base PR
Effect only 0.03s 0.02s
Import effect-machine 0.03s 0.03s
Machine.state (3 children) 0.07s 0.07s
Machine.make (3 states, 2 events) 0.11s 0.11s
machine.handle (3 states, 2 transitions) 0.27s 0.29s
declared transition (10 named branches) 0.34s 0.32s
atomic construction, guards, and reentry 0.42s 0.42s
registered invocation (input-taking Effect) 0.27s 0.24s
machine.handle (depth 24) 0.50s 0.49s
machine.handle (wide depth 16) 0.57s 0.55s
machine.handle (parallel/history/choice) 0.40s 0.42s
machine definition (3 independent implementations) 0.43s 0.42s
machine exact input/output/error/services 0.41s 0.38s
execution adapter readiness 0.36s 0.34s

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 9V45 96-Core Processor with Node v24.21.0.

Pull request baseline

Scenario Effect Machine
Plan counter transitions 197,696 transitions/s
Drain burst with terminal fence 740,736 increments/s
Drain burst with a change observer 683,652 increments/s
Lookup and send to one child 632,875 increments/s
Start and stop a machine 229,043 machines/s
Start and stop a parent with one child 68,064 families/s
Complete an Effect invocation with tracing enabled 28,759 invocations/s
Complete an Effect invocation with tracing disabled 31,163 invocations/s
Plan transitions through a compound state 177,837 transitions/s
Plan transitions through parallel regions 145,635 transitions/s
Drain burst through a compound state 589,773 events/s
Drain burst through two parallel regions 540,364 events/s
Drain a compound-state burst with a change observer 555,208 events/s

Process runtime reference points

Scenario Effect Machine
Start and stop a raw generic process 56,443 processes/s
Start and stop a raw compiled process 133,476 processes/s
Memory profile Effect Machine
Idle machine 1.8 KiB
Raw generic managed process 11.6 KiB
Raw compiled process 3.0 KiB
Two independent idle machines 3.4 KiB
Idle parent with one child 5.9 KiB
Parent with observed child registry 9.3 KiB
Parent with observed invoked child snapshots 6.4 KiB

Effect Machine change from base

Metric Base Base variability PR PR variability Difference
Plan counter transitions 197,882 transitions/s 1.2% MAD 197,696 transitions/s 0.2% MAD -0.1%
Drain burst with terminal fence 716,790 increments/s 1.1% MAD 740,736 increments/s 0.4% MAD +3.3%
Drain burst with a change observer 674,871 increments/s 2.5% MAD 683,652 increments/s 2.6% MAD +1.3%
Lookup and send to one child 628,769 increments/s 1.2% MAD 632,875 increments/s 2.6% MAD +0.7%
Start and stop a machine 225,887 machines/s 1.9% MAD 229,043 machines/s 0.9% MAD +1.4%
Start and stop a parent with one child 68,343 families/s 2.0% MAD 68,064 families/s 0.7% MAD -0.4%
Complete an Effect invocation with tracing enabled 29,221 invocations/s 2.7% MAD 28,759 invocations/s 1.4% MAD -1.6%
Complete an Effect invocation with tracing disabled 30,913 invocations/s 2.3% MAD 31,163 invocations/s 0.7% MAD +0.8%
Plan transitions through a compound state 181,758 transitions/s 2.0% MAD 177,837 transitions/s 1.6% MAD -2.2%
Plan transitions through parallel regions 146,825 transitions/s 0.4% MAD 145,635 transitions/s 1.3% MAD -0.8%
Drain burst through a compound state 596,068 events/s 2.1% MAD 589,773 events/s 1.2% MAD -1.1%
Drain burst through two parallel regions 558,813 events/s 0.0% MAD 540,364 events/s 1.3% MAD -3.3%
Drain a compound-state burst with a change observer 571,516 events/s 1.8% MAD 555,208 events/s 2.3% MAD -2.9%
Idle machine heap per unit 1.8 KiB 0.7% MAD 1.8 KiB 0.1% MAD -0.5%
Raw generic managed process heap per unit 11.6 KiB 0.0% MAD 11.6 KiB 0.0% MAD +0.0%
Raw compiled process heap per unit 3.0 KiB 0.2% MAD 3.0 KiB 0.0% MAD +0.1%
Two independent idle machines heap per unit 3.4 KiB 0.0% MAD 3.4 KiB 0.2% MAD -0.2%
Idle parent with one child heap per unit 5.8 KiB 0.0% MAD 5.9 KiB 0.0% MAD +0.1%
Parent with observed child registry heap per unit 9.3 KiB 0.0% MAD 9.3 KiB 0.0% MAD +0.1%
Parent with observed invoked child snapshots heap per unit 6.4 KiB 0.1% MAD 6.4 KiB 0.1% MAD +0.1%

Process runtime reference change from base

Metric Base Base variability PR PR variability Difference
Start and stop a raw generic process 56,828 processes/s 2.1% MAD 56,443 processes/s 0.5% MAD -0.7%
Start and stop a raw compiled process 134,571 processes/s 1.9% MAD 133,476 processes/s 1.4% MAD -0.8%

Regression guard

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

Versions and interpretation
  • Effect Machine: 0.38.1

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 e20e054 into main Sep 28, 2026
8 checks passed
@SandroMaglione
SandroMaglione deleted the replace-target-references-with-paths branch September 28, 2026 02:47
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.

1 participant