Changelog
New features, improvements, and fixes from each release.
v0.39.0
Minor changes
Replace
Machine.targets(Root)references with declared state paths. Transitions, initial edges, history, owner updates, and named branches now take dotted path strings, spelled the same way as snapshot paths. They are checked against the root passed tomakeand suggested by the editor. The root node is"root", so a top-level state can no longer use that name.Restart the machine from fresh input with
initialize, which replaces root targets. Machines without input useinitialize: true, and branch declarations use{ initialize: true }withselect.branch({ input }).Machine.make({ root: Root, input: Input, events: Events }).handle({ initial: { target: "Idle" }, on: { Increment: { update: "root", data: ({ root }) => ({ ...root, count: root.count + 1 }), }, Reset: { initialize: ({ event }) => ({ id: event.id }), reenter: true }, }, states: { Idle: { on: { Resume: { history: "Checkout.recent" } } } }, });To migrate, remove
Machine.targets. Replacetargets.root.A.Bwith"A.B",update: targets.rootwithupdate: "root", and{ target: targets.root, input }with{ initialize: input }. Rename any top-level state namedroot. Declarations stored in variables before they reachmakeor.handleneedas const.
Patch changes
Check the public
Machineoperations against their implementations at compile time.Machine.start,resume,plan,planInitial,can,enabled,isFinal,encodeSnapshot,decodeSnapshot,make, and the event protocol builders no longer rely on unchecked casts, so their documented signatures now stay in sync with runtime behavior. Planning failures are classified consistently across runtime strategies: non-stabilization and schema failures stay typed, startup throws becomeStartupError, and other handler throws remain defects.Updated dependencies [e20e054]
Updated dependencies [86968b6]
Show transition declarations with declared state paths such as
{ target: "Checkout.Review" }and{ initialize: … }in the devtools.no-async-planning-callbackalso checksinitializeinput mappers.
v0.38.1
Patch changes
Upgrade Effect and companion packages to
4.0.0-rc.117. Install matching Effect packages when upgrading.Updated dependencies [a3f54ab]
v0.38.0
Minor changes
Upgrade Effect and companion packages to
4.0.0-rc.116. Install matching Effect packages when upgrading.MachineTest.scenarios,finiteModels, andruntimeCommandsnow produce nativeArbitraryvalues fromeffect/unstable/arbitrary/Arbitrary. Custom input, event, and command generators must use that module instead of FastCheck. UseArbitrary.schema(schema)for schema-derived generators,Arbitrary.arrayfor sequences, andArbitrary.sampleEffectorArbitrary.checkEffectto sample and check them. In@effect/vitest, replacefastCheck: { numRuns }witharbitrary: { runs }. Generate new replay tokens; earlier FastCheck seeds and paths do not reproduce the same cases.Command sequences shrink by removing irrelevant commands while retaining the remaining values. Fix finite-model verification of exit and entry paths when an ancestor's initial choice resolves inside an active compound state.
Patch changes
Updated dependencies [36dde0f]
v0.37.0
Minor changes
Add
Machine.waitFor(ref, predicate)for external Effects and tests awaiting a current or subsequent published snapshot. Type predicates narrow the result. Unmatched failures preserve their Cause, stopping fails withStoppedError, and completion without a match fails withCause.NoSuchElementError. ComposeEffect.timeoutto bound the wait; cancellation releases observation without stopping the machine.Effect, Stream, and timer invocations now run inside
Machine.invokespans with machine, state, source, and invocation identity. Use ordinary Effect tracing configuration to export them. This preserves resource ownership and does not propagate each sender's trace context through the mailbox.Fix indexed self-transitions and reentry for states without a value schema, so their invocations restart consistently with the generic runtime.
Patch changes
Updated dependencies [794a12a]
Fix devtools charts failing to render retry transitions into compound states. Route repairs preserve the direction and clearance at both ends of each transition, including straight routes that need a detour to reach a state header.
Keep the devtools platform dependencies on the supported Effect prerelease so fresh installations can start the CLI without missing-module errors.
v0.36.0
Minor changes
Pass machine input directly to root initial constructors, including parallel regions, without retaining startup-only values in root data. A root transition uses
{ target: targets.root, input }to reconstruct root and its initial children with fresh input;reenter: truerestarts the root lifecycle when the handler is at root. Root updates continue to use{ update: targets.root, data }and retain active children.Use
targetinstead ofinitialon event transitions. Named branch selectors now accept one construction object:select.checkout({ data: cart, states: { Review: { data: review } } }). Replace.from(value)with({ data: value }),.decoded(value)with({ decoded: true, data: value }), and chained owner updates withupdate: { data: owner }inside the same call. History fallbacks usetarget({ states: ... })with a complete tree containing their owner.Input remains limited to root construction and the root's initial callbacks. Nested initializers use state data and ancestors. Explicit subtree construction preserves source-local parallel retention, schema validation, and declared branch inspection.
Patch changes
Updated dependencies [21fbf58]
v0.35.0
Minor changes
Declare initial edges and data together in
.handle. RemoveinitialfromMachine.state, removeinitialandinitialConfigurationfromMachine.make, and move shared startup data into the root handler'srootvalue or input callback. Each compound declaresinitial: { target, data? }; parallel handlers use aninitialmap of region data. Initial targets must be direct children and remain inspectable without executing constructors.const root = Machine.state({ states: { Locked: {}, Unlocked: {} } }); const targets = Machine.targets(root); const machine = Machine.make({ root, events: Machine.events({ Coin: {} }), }).handle({ initial: { target: targets.root.Locked }, states: { Locked: { on: { Coin: { target: targets.root.Unlocked } } } }, });Replace declarative
fromcallbacks withdata, which accepts literals or callbacks. Supply already-decoded values with{ decoded: true, data: valueOrCallback }. For root or region data that itself containsdecoded: trueanddatafields, use a callback returning the ordinary schema input. Bound branch and history builders retain.fromand.decoded. Replace command-producinginitializecallbacks withentry/exithandlers or invocations; definitions become executable only after.handlecaptures their initial declarations.
Patch changes
Updated dependencies [e72b7f5]
v0.34.0
Minor changes
Declare transitions as objects using references from
Machine.targets(Root). Replace event-local selector chains with{ target: targets.root.Ready, from: ({ event }) => ({ value: event.value }) }, and useupdatefor retained state data.initial,history,none: true,guard, andreenterexpress their respective operations explicitly.Register
effects,streams,timers,logic, andchildreninsideMachine.make, then invoke them with{ src, input?, onDone?, onFailure?, onElement?, onSnapshot? }. Required inputs and reachable outcome handlers are checked from the source types; unused sources do not add service requirements. Pass lazy Effect and Stream values directly, or use a function with one required input and provide an input mapper.Declare named
branchesinmakefor conditional outcomes, queued commands, or nested construction. A transition uses{ branches: "group", resolve }; itsselectconstructors come from the declared destinations. Ordinary transitions stay inline. Full root configuration construction remains available at initialization and history fallback; runtime transitions use explicit destinations and retained owner updates. Devtools, testing, React integrations, and lint rules follow the new declarations.
Patch changes
Clarify declarative guards in the machine documentation and add a checked example of guarded state construction.
Improve internal typing and organization for machine definitions, execution strategies, snapshot validation, and Cluster contracts. Existing public APIs and machine behavior remain unchanged.
Updated dependencies [a10e9be]
Updated dependencies [c1b0693]
v0.33.0
Minor changes
Construct atomic destination and retained-owner updates with
.updating(owner).from(({ current, event }) => ({ target, update })), or use.decoded(...)for decoded values. Both values are complete replacements;.resolve(...)remains available for explicit configuration builders and commands. Value updates and atomic transitions now support.guard(...), declining before construction and commands while preserving ancestor fallback.Use chainable
.reenter()before.from(...),.decoded(...), or.resolve(...)to force source exit and entry. Replace.resolve(callback, { reenter: true })with.reenter().resolve(callback)and omit reentry entirely when it is false. Named branching transitions use.branches(...).reenter().resolve(...). The redundant-resolver lint rule preserves these modifiers when simplifying default construction.
Patch changes
Updated dependencies [3255f24]
Construct atomic destination and retained-owner updates with
.updating(owner).from(({ current, event }) => ({ target, update })), or use.decoded(...)for decoded values. Both values are complete replacements;.resolve(...)remains available for explicit configuration builders and commands. Value updates and atomic transitions now support.guard(...), declining before construction and commands while preserving ancestor fallback.Use chainable
.reenter()before.from(...),.decoded(...), or.resolve(...)to force source exit and entry. Replace.resolve(callback, { reenter: true })with.reenter().resolve(callback)and omit reentry entirely when it is false. Named branching transitions use.branches(...).reenter().resolve(...). The redundant-resolver lint rule preserves these modifiers when simplifying default construction.
v0.32.0
Minor changes
Model each machine with one
Machine.stateroot, passed toMachine.make({ root }). Rootfieldssupport data-only machines and shared data that survives child transitions. Replace the former top-level state map with a root descriptor, put child handlers underhandle({ states }), and useinitialfor root values orinitialConfigurationfor a complete startup override. Add direct target construction, non-reenteringself.update, and guards with ancestor fallback. Construct local event protocols from field records or import existing schemas with the explicitFromSchemasconstructors.Render typed state paths with React
MachineStateand own isolated instances withcreateMachineContext(AtomMachine.factory(machine)). Providers capture startup input without subscribing to state. Replace Atom bridge.statereads with.resultor path selectors;.snapshotretains runtime status. CoreMachineRef.stateremains available. Testing runs and probes accept public deferred event inputs and record decoded receipts.Encoded snapshots use version 2 and include the root at path
""; explicitly migrate older persisted snapshots before decoding. Replace removedMachine.Emit,Machine.Emits,Machine.EmitOf, andMachine.PseudoStateAnnotationsaliases withEmittedEvent,EmittedEvents,EmittedEventOf, andSchemaLessStateAnnotations.
Patch changes
Align
Machine.cantypes with its existing support for public and internal events. Querying an internal event does not make it sendable throughMachineRef.send. Preserve interruption during snapshot encoding and decoding, validate reused event and emission values, and capture machine definitions independently of caller mutations.Make
MachineTest.verifylazy and compare decoded values without lossy JSON serialization. Serialize concurrent devtools refreshes, stop watcher work with its server scope, and prevent lint rules from matching shadowed machine bindings.Updated dependencies [1160040]
Updated dependencies [163be91]
Keep charts available for machines that transition between compound states and nested children.
Hierarchy routes now follow the vertical chart direction with stable source, terminal, label, and initial-marker clearance. This includes multi-branch fan-out and returns to a parent's initial configuration.
Parent-origin cues now identify transitions from an ancestor into deeply nested descendants. Transitions owned by parallel states, including child-invocation outcomes, use short local corridors instead of long ELK detours across the container.
v0.31.2
Patch changes
Fix initial choice resolvers so schema-backed
containingStateandancestorsare decoded before the choice runs.This makes
.from(...)state construction behave the same for transient initial choices as it does for ordinary active-state entry.Updated dependencies [65e4722]
v0.31.1
Patch changes
Make statecharts easier to scan with compact graphical badges for automatic transitions, invocation outcomes, stream updates, snapshots, choices, and branch groups.
Failure transitions now use a distinct red treatment, while state cards, activity colors, and transition labels use a colorblind-friendly palette with stronger foreground contrast.
v0.31.0
Minor changes
Add
AtomMachine.canfor reactive event-acceptance queries with lifecycle-aware failures and stable derived atom identity.Declare a projection once from a concrete event or an atom containing a changing event, then apply it to compatible machine bridges:
const submitAllowed = AtomMachine.can(AuthEvents.Submitted()); const canSubmitAtom = submitAllowed(authMachineAtom);
Patch changes
Updated dependencies [9ec7ef4]
v0.30.0
Minor changes
Add
Machine.canfor testing whether a concrete public event would select a transition from a snapshot. It preserves schema failures, honors declinable handlers and hierarchy, and does not execute transition lifecycle or collected work.Add
AtomMachine.factoryand boundfactoryfor reusable, fully inferred machine bridge constructors. Every call creates a fresh lazy bridge, whileReturnType<typeof constructor>preserves the exact machine and bound runtime error types.
Patch changes
Updated dependencies [d1f921c]
v0.29.0
Minor changes
Add
@typeonce/effect-machine-reactwithuseMachineAtomfor owning and mounting one stable machine atom without subscribing its React owner to machine state.Typed state-path projections now return the same atom for repeated calls with the same machine and path. Descendants can select state-owned data directly during render:
const machine = useMachineAtom(() => MachineAtoms.make(AuthMachine, input)); const editing = useAtomSuspense( AtomMachine.selectSnapshot(machine, "Editing") ).value;Startup input is captured when React creates the owner. Send an event to update the running workflow, or change the owner's React key to replace the machine.
Patch changes
Updated dependencies [d739ebd]
v0.28.0
Minor changes
Add
AtomMachine.familyandAtomMachine.familyChildfor keyed machine atoms that retain their machine bridge and use weak family values when the runtime supports them.The machine startup input is the root family key. Define each public readonly or writable atom once, then look it up directly from React without
useMemo:const processAtoms = AtomMachine.family(processMachine, { atoms: { details: AtomMachine.select("Processing"), send: (machine) => machine.send, }, });Root and child selectors now also support data-last calls such as
AtomMachine.select("Processing")(machine).
Patch changes
Make statecharts denser by flowing states from top to bottom, replacing full initial-entry lanes with compact top-entry markers, and sizing state cards from their visible names and invocations.
State values now appear as compact JSON-shaped type previews in the state inspector instead of occupying the topology. Transition labels sit directly on clear route segments when space permits, hierarchy-crossing routes avoid compound-state headers, and routes attach to their actual source and target before turning. Horizontally scrolling machine tabs also keep their position when selecting or live-reloading a machine.
Charts remain available when every deterministic layout has only cosmetic label-to-route crossings. Structural failures such as detached edges, node crossings, and overlapping routes still prevent rendering.
Updated dependencies [9aab73e]
v0.27.1
Patch changes
Lay out every transition through one obstacle-aware chart pipeline, including self-transitions, targetless updates, and transitions between compound states and their children.
Validate node, label, containment, self-loop clearance, and route-clearance invariants before rendering. The visualizer retries deterministic, progressively more spacious layouts and reports a diagnostic error instead of displaying geometry that violates those constraints.
v0.27.0
Minor changes
Add
effect-machine buildfor publishing the project visualizer as a static website.The command inspects the selected machines once, validates their documents, and writes relative HTML, CSS, JavaScript,
machines.json, and build metadata to--out-dir. The generated site keeps the interactive statechart and topology walkthrough without a live devtools server or project code at viewing time.Add recommended rules that reject duplicate invocation identities, browser API access during planning, and nondeterministic time or randomness during planning.
Strengthen
no-async-planning-callbackto detect direct Promise, fetch, timer, and scheduling operations, and extendno-redundant-resolvefixes to resolver-only reentry and empty targetless resolvers. Diagnostics now explain how to move work into state-owned invocations, pass external facts through input or events, or model sequential work with separate states.
v0.26.2
Patch changes
Keep the statechart available for machines that combine nested state updates with cross-hierarchy transitions.
Self-transitions now use deterministic local routes, and layout retries with relaxed port constraints before reporting a failure.
v0.26.1
Patch changes
Fix the published plugin entrypoints so Oxlint loads built JavaScript instead of TypeScript source under
node_modules.Upgrade from
0.26.0without changing the Oxlint configuration. Both the package root and the recommended configuration now resolve to built files.
v0.26.0
Minor changes
Add
@typeonce/oxlint-plugin-effect-machinewith recommended rules for redundant default resolvers, asynchronous planning callbacks, and one-use intermediateMachine.make(...)definitions.All three Effect Machine packages now release at the same version.
Patch changes
Upgrade the exact Effect peer dependency and companion Effect packages to
4.0.0-rc.112.Improve the static statechart so initial and reachable states follow a stable left-to-right order, reverse and self-transitions use dedicated routes, and transition labels stay clear of arrowheads and compound boundaries.
Distinguish automatic transitions with dashed lines and correlate invoke outcomes with muted colors for effect, timer, stream, process, and child-machine activities.
Stack parallel regions into vertical lanes, separate states with no statically known path from the initial state, and make long or intersecting routes easier to follow with rounded corners, edge casing, and direction cues.
Add a machine analysis inspector that reports declared public events without handlers and state subtrees without a statically known path from the initial configuration.
Updated dependencies [4130963]
Updated dependencies [806d2fd]
v0.25.0
Minor changes
Replace the text-tree topology pane with a statically laid-out statechart. State cards expose value fields and invocations, while routed transition edges and compound regions make the machine topology readable without a draggable canvas. Directional colors distinguish incoming from outgoing relationships, and conditional branches with the same source and target share one topology edge while retaining their full details in the inspector. Machine tabs sit above the full-viewport chart, selection remains visible independently from the on-demand floating inspector, and corner zoom and fit controls provide a whole-machine overview.
MachineDocument.Statenow retains each state's projected value and output schemas. Consumers of serialized documents must acceptschemaVersion: 3and the newvalueSchemaandoutputSchemafields.Replace planner-backed browser simulation and
MachineSimulatorwith the side-effect-freeMachineWalkthroughmodule. A walkthrough derives its initial and active configurations entirely fromMachineDocument, exposes each documented transition branch as an explicit choice, preserves parallel regions, records shallow and deep history, and retains an immutable timeline with cursor-based time travel. Runtime-resolved targets and first-use history remain visible but unavailable instead of executing callbacks or inventing results.The browser now presents public machine and event schemas as read-only contracts and uses transition edges for simulation. Targetless transitions render as clickable self-loops, runtime-resolved targets terminate at disabled dashed placeholders, and state nodes remain read-only. Direct choices advance immediately while ambiguous or unavailable branches appear in a compact anchored picker. The bottom dock is reserved for the time-travel timeline. The browser no longer asks for payload values or evaluates initializers, resolvers, guards, updates, automatic callbacks, or invoke outcomes. Migrate programmatic document exploration from
MachineSimulator.startandMachineSimulator.sendtoMachineWalkthrough.start,MachineWalkthrough.choices, andMachineWalkthrough.take.
v0.24.0
Minor changes
Add planner-backed simulation sessions to the web visualizer. Machine and event inputs are rendered as fields from their Effect schemas, including type and constraint metadata, nested objects, arrays, unions, enums, literals, booleans, strings, and numbers. Browser constraints provide immediate feedback, while authoritative Effect Schema failures are mapped back to their fields. Each isolated step uses the real Effect Machine planner and shows selected branches, concrete topology changes, raised and emitted events, planned commands, completion, and output as a structured trace.
Expose
Machine.inputEventSchemasso inspection tools can describe or construct valid public events without reaching into the opaque event protocol. Planning evaluates synchronous statechart callbacks but does not commit commands or start runtime activities. Schema and planning failures remain visible beside the machine topology.
Patch changes
Updated dependencies [eda432c]
v0.23.0
Minor changes
Add a local interactive text visualizer prototype that renders the public machine inspection data as a collapsible tree.
Use the text tree to navigate topology, expand nested states, select subtrees, and inspect structured machine details without converting the model into a chart.
Add
MachineSimulatorand browser controls for side-effect-free, best-effort topology simulation. Direct required transitions advance the active tree; runtime-dependent transitions remain visibly indeterminate instead of executing user code or guessing.Add a local
effect-machinecommand that discovers exported.handle(...)machines, keeps their last valid inspection document across incomplete reloads, and serves the live interactive text visualizer.Native file-system events are used by default. Pass
--watch-pollingon platforms where native events are unavailable.
Patch changes
Move the published package into an Effect-style workspace without changing its public exports.
Release
@typeonce/effect-machineand@typeonce/effect-machine-devtoolsat the same version. Install matching versions so the devtools inspection protocol and machine model remain compatible.Updated dependencies [f90b37d]
v0.22.0
Minor changes
Make state construction modes explicit and allow one topology target to replace a retained valued owner atomically.
Valued builders are no longer callable. Replace
target(value)and nestedbuilder(value, ...)calls with.decoded(value, ...); keep.from(input, ...)for schema make input. Plain state-update resolvers now expose the decoded owner ascurrentand its construction builder asowner, replacing the previousancestorsplustargetpattern.Declare a combined transition with
.updating(ownerSelector). The resolver must finish destination construction with.update(...), so the owner replacement cannot be omitted:to.local .SavingPlan() .updating(to.branch.Ready) .resolve(({ current, owner, target }) => target .from({ request }) .update(owner.decoded(new Ready({ ...current, notice: null }))) );Transition inspection and retained microsteps now include an
updatesarray naming replaced owners.
v0.21.0
Minor changes
Add
to.local.update(...)andto.branch.<path>.update(...)for replacing an active compound or parallel state's value without reconstructing its active descendants.Updates accept decoded values through
target(value)or schema make input throughtarget.from(input). They preserve descendant configuration and state-owned work by default, support named branches and declinable resolvers, and expose the updated owner through transition inspection.
v0.20.0
Minor changes
Add process-owned child machine spawning for runtime-sized child sets.
Use
Machine.childFamily(machine)to bind a child machine once, then callchildren.spawn(Family(id), { input })inside an invoked Effect. Successfully started children survive owner state changes and remain addressable through machine references andAtomMachineuntil they stop or their parent stops.Logic.Scope.spawnaccepts the same child descriptors for lower-level process logic. Dynamic spawn calls retain child input, startup failure and service inference, and check the child's declared parent protocol.
v0.19.1
Patch changes
Upgrade the exact Effect peer dependency and companion Effect packages to
4.0.0-rc.111.Add focused guides for statechart modeling and Effect Atom ownership in React applications, and publish the statechart guide on the documentation website.
v0.19.0
Minor changes
Make
Machine.encodeSnapshotreturn a canonical JSON representation or fail withMachineSchemaEncodeError. Encoded state values, completion outputs, and history values are now typed asSchema.Json; rich schema values use their canonical JSON codecs, while cycles and other non-JSON values fail at the machine boundary instead of causing a later serialization crash. DeclaredSchema.VoidandSchema.Undefinedcompletion outputs now use their canonicalnullencoding; an omitted output is reserved for final states that do not declare an output schema.ClusterMachine.makenow requires JSON-encoded state, completion-output, and public input-event schemas. Keep process-local capabilities in services, adapters, or internal events, and give transported values an explicit JSON codec. Cluster snapshot encoding failures are reported asSnapshotEncodeFailurewithout advancing the checkpoint.MachineTest.observedGraphcontinues to support process-local state. Its nodeencodedfield is now optional: portable snapshots retain their canonical JSON form, while non-portable snapshots use local structural identity and omit it. Snapshot encoding failures no longer appear in the operation's error channel.
Patch changes
Upgrade the exact Effect peer dependency and companion Effect packages to
4.0.0-rc.110.Document the nested configuration and callback APIs used by
Machine.states, event protocols,Machine.make,Definition.handle, execution, transitions, and state invocation.The documentation site now presents those core authoring APIs on a standalone guide-reference page while keeping module pages focused on public exports. Parameters remain grouped under their point of use with searchable signatures, lifecycle contexts, target semantics, defaults, source links, focused examples, and a complete nested page outline. Redundant configuration headings are omitted, and each parameter is presented as a named API block with its signature, description, and attached source link. Long signatures stay contained within their documentation blocks across responsive layouts.
v0.18.0
Minor changes
Add consumer-facing state and startup-input extractors.
Machine.Snapshot,Machine.Value, andMachine.SnapshotAtaccept either the object returned byMachine.statesor a machine definition, while preserving exact path validation and excluding control-only paths fromValue.Machine.Machine.Input<M>now extracts the decoded startup value and isneverwhen the machine usesSchema.Void. Code that needs the startup schema should migrate fromMachine.Machine.Input<M>toMachine.Machine.InputSchema<M>; code that previously usedMachine.Machine.Input<M>["Type"]can useMachine.Machine.Input<M>directly.Replace
Machine.invokeand its object-configuration helper types with state-local fluent invocation chains. Select an Effect, Stream, timer, process logic, or child from the handler'sfromparameter, then handle every reachable lifecycle channel before returning the chain:machine.handle({ Loading: { invoke: (from) => from .effect("load", () => loadUser()) .onDone((to) => to.full.Ready()) .onFailure((to) => to.full.Failed()), }, });Return an array of completed chains for multiple activities. Sources and child descriptors remain reusable, while keeping the invocation declaration local preserves exact owner-state, event, parent, output, failure, element, snapshot, and service inference.
v0.17.0
Minor changes
Make owning-machine requirements explicit and statically safe. Declare
parent: Machine.parent(ParentEvents)for a child-only machine; its behavior receives a non-optionalparent, compatible owners are checked when the child is invoked, and independent root APIs reject the machine.Replace
parentEvents: ParentEventswithparent: Machine.optionalParent(ParentEvents)when the same machine must remain valid as either a root or a child. Optional declarations retain the previousparent | undefinedbehavior. Machines without a parent declaration no longer exposeparentin schema-first behavior contexts.Make definition-time topology instructions immutable values. Use
to.none, declared.initialand history properties, andto.local.withwithout an empty call; state and choice destinations such asto.full.Running()remain callable.Author machine startup through the same target-first grammar:
initial: (to) => to.Flow.initial.resolve(...). The selector is captured once and its resolver remains lazy until initial planning.Remove
Machine.targetlessand the{ target: Machine.targetless, resolve }shorthand. Use(to) => to.noneor(to) => to.none.resolve(...); block-bodied targetless resolvers may omit an explicitreturn undefined.Replace
Machine.transition(...)with fluent transition selectors supplied directly to inline handlers. Select a target and optionally attach its resolver, reentry, or named branches without an intermediate wrapper:const handlers = { Start: (to) => to.full .Running() .resolve(({ event, target }) => target.from({ count: event.count })), Route: (to) => to .branches({ running: { target: to.full.Running() }, done: { target: to.full.Done() }, unchanged: { target: to.none }, }) .resolve(({ event, select }) => event.cached ? select.done.from() : select.running.from() ), };Use
.reenter()for resolver-free reentry, or pass literaldeclinable: trueto.resolve(...)when the resolver must receivedecline(). Bare targets are accepted only when their schemas support default construction.Remove the machine-definition
.invoke(...)method. UseMachine.invoke(...)in every state; it now retains the owning state, event, parent-event, output, error, element, snapshot, and service inference directly insidehandle(...).Add opt-in declinable transitions for conditional statechart dispatch.
Set
declinable: trueonMachine.transitionto expose a typeddecline()resolver capability. Declining selects no transition, discards operations enqueued by that resolver, and lets hierarchical event or eventless dispatch continue with the next eligible ancestor.target.none()remains handled and continues to consume the trigger.Declining a completion or invocation outcome ignores that lifecycle occurrence because those triggers do not dispatch to ancestor handlers.
Static transition definitions now expose
acceptance: "required" | "declinable"alongside their exact target branches. Choices and initial routing remain total and reject declinable transitions.
Patch changes
Fix
AtomMachineselectors so machines and invoked children with declared emitted events retain typed state selection and matching aftermakeorbind.
v0.16.0
Minor changes
Replace conditional
casesandotherwisetransitions with namedbranches. Each branch declares one static target, while the required synchronousresolvefunction uses ordinary TypeScript control flow to return a typedselectbuilder.Machine.transition({ branches: (to) => ({ moving: { target: to.local.Running() }, unchanged: { target: to.none() }, }), resolve: ({ event, select }) => event.axis === 0 ? select.unchanged() : select.moving.from({ startedAt: event.at }), });Branch keys are stable inspection, visualization, trace-verification, and coverage identities. Optional branch titles remain presentation metadata.
Add
streamsources toMachine.invoke. Stream values are handled through the typedonElementtransition one committed parent macrostep at a time, while completion and typed failures useonDoneandonFailure. Leaving the owning state interrupts the Stream and runs its finalizers.Add the direct
{ target: Machine.targetless, resolve }transition shorthand for handlers that keep the current configuration and only enqueue commands.
v0.15.0
Minor changes
Make
handlea one-shot implementation boundary.Machine.make(...)now returns aMachine.Definition; callinghandle(...)returns aMachinewithout anotherhandlemethod.To create multiple implementations, call
handleindependently on the original definition. Migrate chained calls by combining their state configurations into one handler tree.Add
Machine.statefor topology that is genuinely reused at multiple mounts, plus definition-boundStates.path(...)andMachine.Snapshot<typeof States>helpers for checked finite path families and snapshot queries.Rename
Machine.defineStatestoMachine.states. Migrate by replacingMachine.defineStates({...})withMachine.states({...}); one-off topology should remain inline in that complete state definition. The returned state tree is now an immutable structural capture, so repeated mounts do not retain shared caller-owned configuration objects.
Patch changes
Fix
Machine.invoke(...)inside.handle(...)so invocation sources and lifecycle handlers receive the owning machine's typedselfandparentprotocols.Event protocol examples now pass tagged unions directly to
Machine.events,Machine.internalEvents, andMachine.emittedEvents, avoiding throwaway schema bindings.
v0.14.1
Patch changes
Fix
to.local.with()so schema-backed compound scopes can select and rebuild their local value from both direct handlers and nested invoke outcomes.Expand the inspection examples with text and Mermaid state diagrams built from
Machine.stateNodes,Machine.initialDefinition,Machine.transitionDefinitions,Machine.activityDefinitions, and live configuration. The examples render concrete conditional branches, reentry, choices, history and final states, activities, and safely escaped user-defined labels.
v0.14.0
Minor changes
Make
MachineTest.coveragereport transition definitions and their exact branches separately. Read definition coverage throughcoverage.transitions.definitionsand conditional branch coverage throughcoverage.transitions.branches.Replace the
targetBoundsverification law group withdefinitions. The new laws validate the declared startup root, transition registration, retainedbranchIndex, and the selected branch's exact target kind and scope.Add exact
transitionCoveragetoMachineTest.Exploration. Coverage includes startup and every concretely planned event, including state-limit candidates, while unplanned depth- and transition-limit frontiers remain misses.Retain exact static and runtime transition evidence for testing and visualization. Transition branch inspection now includes the selected target kind and scope, retained planner transitions identify the zero-based branch that executed, and
Machine.initialDefinitionexposes the root startup selection without executing its resolver.Use
branchIndexto associate a retained transition with the corresponding entry inMachine.transitionDefinitions(machine).branches. Direct transitions use index0; conditional cases retain their declaration index andotherwisefollows the final case.Require
Machine.transitionfor every machine transition and capture each possible target as static machine topology. Direct transitions declaretargetandresolve; conditional transitions declare titledcaseswhosewhenfunctions returnOption, plus an explicitotherwisebranch. The selected target builder and conditional match value are inferred in each resolver.Initial state construction now uses the same
targetandresolveshape, restricted to the machine's declared initial state. Replace process logic previously created withMachine.transitionbyMachine.logic, and replace function handlers, target upper-bound lists, andStates.initialconstruction with the explicit transition and initial target selectors.Allow conditional
Machine.transitiondefinitions to infer any number of heterogeneous cases. Definecaseswith its locally suppliedbranchconstructor so each predicate match and selected target remain exact in the corresponding resolver:Machine.transition({ cases: (branch) => [ branch({ title: "cached", when: ({ event }) => event.cached, target: (to) => to.full.Ready(), resolve: ({ match, target }) => target.from({ data: match }), }), ], otherwise: { target: (to) => to.full.Loading(), resolve: ({ target }) => target.from(), }, });Replace each object previously written directly in the
casesarray withbranch({ ... })inside thecases: (branch) => [...]factory. Direct transitions andotherwisekeep their existing shape.Make
MachineTest.verifyaccept startup roots reached through exact retained initial-choice routes and reject retained targets whose choice, initial, or history resolution is inconsistent with their selected static branch. Resolution failures are reported asdefinitions.resolution.
v0.13.0
Minor changes
Upgrade the exact Effect peer dependency and companion Effect packages to
4.0.0-rc.109.Require every
Machine.invokeeffectsource to be a factory evaluated when its owning state is entered. This gives lifecycle callbacks immediate output and failure inference while making Effect construction timing explicit.Wrap previously direct Effects in a zero-argument function:
Machine.invoke({ id: "load", effect: () => load, onDone: ({ output, target }) => target.none(), });Add typed declared-initial entry to compound and parallel transition targets.
Use
target.full.opened.initial(),initial(value), orinitial.from(input)to enter the initial configuration declared byMachine.defineStates; the same operation is available through compatiblelocalandbranchtarget scopes. Schema-valued implicit children are constructed byinitialize: ({ builder }) => ..., including fluent completion of every valued parallel region. Missing initializers are reported athandle(...), and.fromvalidation remains a typedMachineSchemaDecodeErrorduring planning.State handler
initialand itsStateInitial*utility types have been replaced byinitializeandStateInitialize*. Migrate compound initializers frominitial: () => new Child(...)toinitialize: ({ builder }) => builder(new Child(...)), and parallel initializers from returned value records to chained region builders.Add live, root-scoped machine inspection through
Machine.prepare(machine).inspectionandAtomMachine.inspection(machineAtom).The hot Effect
Streamobserves ordered creation, initialization, mailbox delivery and processing, state changes, emissions, Effect and timer activities, and termination for a prepared root and all locally owned descendants:const prepared = yield * Machine.prepare(machine); yield * prepared.inspection.pipe( Stream.runForEach((event) => Console.log(event.sequence, event.subject.id, event._tag) ), Effect.forkScoped({ startImmediately: true }) ); const ref = yield * prepared.start;Inspection is non-replayed, never fails, and completes with the root. Its session ids and ordering are local to one prepared ownership tree; distributed identity and delivery remain an Effect Cluster concern.
v0.12.0
Minor changes
Rename the minimal inter-machine reference types so they use machine terminology and remain distinct from Effect Cluster concepts.
Machine.ActorRef<Event>; // before Machine.MachineTarget<Event>; // after Machine.ActorContext<InputEvents, ParentEvents>; // before Machine.MachineReferences<InputEvents, ParentEvents>; // afterThe inferred
selfandparentfields and all runtime behavior are unchanged.
v0.11.0
Minor changes
Add
Machine.preparefor composing snapshot and emission streams before a machine initializes, while keepingMachine.startas the one-step convenience.const prepared = yield * Machine.prepare(machine); yield * prepared.emissions.pipe( Stream.runForEach(handleEmission), Effect.forkScoped({ startImmediately: true }) ); const ref = yield * prepared.start;AtomMachine emission streams use the same preparation boundary, and machine definitions now expose
definition.invoke(...)so invocationselfandparentreferences use the exact public input andparentEventsprotocols.
v0.10.0
Minor changes
Make
Machine.eventsandMachine.internalEventsdefinition-time protocol descriptors that are passed directly toMachine.make. The descriptors expose type-safe deferred constructors while retaining their schemas privately, so applications can export the event API without exporting schemas or reaching for throwing schema.makemethods.const Events = Machine.events(PublicEvent); const InternalEvents = Machine.internalEvents(InternalEvent); const machine = Machine.make({ states: States.states, events: Events, internalEvents: InternalEvents, initial: () => States.initial.Idle.from(), });Remove the eager schema-based
Machine.eventconstructor. Pass complete decoded event objects directly to APIs that intentionally retain values, such as manual model-testing scenarios or transport messages.Add
target.none()for explicit targetless transitions. Every installed transition handler now returns a concrete target ortarget.none(); declaredtargetsremain an upper bound on concrete destinations and never excludetarget.none().Remove
Machine.retag. To reuse compatible fields across sibling states, destructure away the source discriminator and construct the destination through its target builder:const { _tag: _, ...fields } = state; return target.local.Saving.from({ ...fields, attempt: 1 });Separate actor inputs from outward notifications. Declare emissions with
Machine.emittedEvents, publish them withemit, and observe the hot, non-replayingMachineRef.emissionsstream. Children declare the public inputs they expect from their owner throughparentEvents, then communicate explicitly with the typed, optionalparentactor reference:const Emissions = Machine.emittedEvents(Progress); const ParentEvents = Machine.events(Completed); const worker = Machine.make({ // ... emittedEvents: Emissions, parentEvents: ParentEvents, }).handle({ Working: { entry: ({ parent }, enqueue) => { enqueue.emit(Emissions.Progress({ value: 0.5 })); if (parent !== undefined) { enqueue.sendTo(parent, ParentEvents.Completed({ value: 42 })); } }, }, });Handler contexts also expose typed
self; invoked-child composition checks that everyparentEventscase is accepted by the parent. This release renames structural handler ancestry tocontainingStateandancestors, supports zero-payload event and emission constructors with(), and exposes root and child emission streams through AtomMachine.
v0.9.0
Minor changes
Add
Machine.events(machine)andMachine.internalEvents(machine)as the standard way to construct protocol events.The returned tag-keyed constructors preserve schema make inputs and defer decoding until machine delivery, so invalid values fail with
MachineSchemaDecodeErrorthrough planning or the running machine instead of throwing at the construction call site.Replace
Machine.invokeEffect,Machine.after,Machine.invokeMachine, andMachine.effectwith one inlineinvokelifecycle object API and a zero-runtimeMachine.invokeinference helper.Choose an
effect,after,logic, orchildsource and handle typed outcomes directly withonDone,onFailure, andonSnapshot. Lifecycle handlers can now transition the owning state without routing results through mapped machine events.State-dependent Effect sources infer their owner state, output, error, and service requirements together without a manual return annotation.
v0.8.0
Minor changes
Allow active states to omit
schemawhen they own no data. Schema-less atomic, compound, parallel, and final states keep full control-flow semantics while exposing value-free.from(...)builders,undefinedhandler state, and snapshot-only query APIs.const States = Machine.defineStates({ Form: { initial: "Editing", states: { Editing: {}, Saving }, }, }); States.initial.Form.from((form) => form.Editing.from());
v0.7.0
Minor changes
Allow
Machine.defineStatesquery helpers to inspect an extracted snapshot subtree. Paths remain absolute and type-safe, butget,getSnapshot, andmatchescan now continue from a snapshot selected earlier instead of requiring the complete root snapshot.const readySnapshot = States.getSnapshot(snapshot, "Ready"); if (Option.isSome(readySnapshot)) { States.get(readySnapshot.value, "Ready.editor"); States.matches(readySnapshot.value, "Ready.editor.Editing"); } const editorSnapshotAtom = AtomMachine.selectSnapshot( machineAtom, "Ready.editor" );Add equality-aware
AtomMachine.selectSnapshotandAtomMachine.selectSnapshotChildcombinators for reactive consumers that need the complete logical snapshot subtree instead of only its state value. The selected atoms retain nested topology, suppress structurally equal updates, and produceOption.none()while the path or invoked child is inactive.
v0.6.1
Patch changes
Reduce pull request performance-check latency while preserving focused type, runtime, and memory regression coverage.
Include the original TypeScript sources in the published package.
v0.6.0
Minor changes
Upgrade Effect and the companion Effect packages from the beta release line to
4.0.0-rc.108.
Patch changes
Upgrade the XState v6 performance comparison to
6.0.0-alpha.36.
v0.5.1
Patch changes
Complete the interactive examples, align example state construction with the recommended API, and restructure the README around the current usage patterns.
v0.5.0
Minor changes
Add reusable runtime invariants, law-oriented causal command verification, and an explicit planner/runtime agreement check. Causal probe microsteps now retain stable public snapshots even when the optimized runtime reuses internal state.
Add state, step, and trace invariants for checking application semantics over planner traces, including machine-inferred builders, conditional observation requirements, structured reports, and property-test assertions. Add bounded breadth-first exploration with state-dependent event representatives, shortest witnesses, explicit truncation frontiers, and fail-closed reachability assertions. Add testing-only runtime probes with acknowledged event delivery so tests can causally inspect ignored, targetless, changing, and failed live macrosteps without adding a production
sendAndAwaitAPI.Add explicitly named causal and enqueue-oriented runtime command runners. Causal command tests now retain an exact probe step for every processed send, support probe-bound asynchronous waits, attribute processing failures to the submitted command, and format replayable causal transcripts. Deprecate the ambiguous
runRuntimeCommandsandformatRuntimeTranscriptnames in favor of their explicit enqueue-oriented replacements.
Patch changes
Add Effect-compatible TypeDoc API reference generation and a lightweight static reference website with module navigation, declaration pages, source links, responsive layouts, and local search. Document the public API with library-native version metadata and focused examples for the primary machine, persistence, testing, reactivity, and Cluster workflows.
v0.4.0
Minor changes
Add an Effect-native
@typeonce/effect-machine/testingentrypoint with schema-derived scenarios, replayable planner traces, independent statechart law and finite-model verification for compound, parallel, and history states, trace coverage, observed Effect graphs, and live runtime command models.Expose retained planner transition evidence and correct reentry boundaries, simultaneous parallel target application, and recorded nested-history restoration through inactive ancestors.
Model finite transitions through one discriminated
event | always | donetrigger representation, with independent stabilization, completion ordering, cycle detection, generated mixed-trigger models, managed-runtime differential checks, and activity-lifecycle command verification. Hand-authored finite models now migrate event transitions from{ source, event, ... }to{ source, trigger: { type: "event", event }, ... }.Replace Effectful state transition, lifecycle, choice, history, and initial callbacks with a synchronous
(state, event) => [nextState, commands]core. Callbacks may enqueue only typedraise,emit,sendTo, andstopoperations; asynchronous work remains available through invoked Effects, actors, and child machines.Remove
Machine.action,Machine.runtime, andMachine.runActions. Planning now returns closed actorcommands, while managed runtimes execute those commands around state publication and typed emission delivery.Add first-class logical snapshot resumption with
Machine.resume, plus lazyAtomMachine.resumeand bound-runtime integration. Resumed machines validate decoded snapshots, preserve logical history and completion metadata, and create fresh managed invokes, children, scopes, and timers without replaying historical statechart work.Add public getters for inspecting compiled state nodes, registered transition handlers, declared transition targets, and the active state configuration of a snapshot. Event, eventless, and completion handlers may declare an upper bound of target paths that is checked against inferred and runtime results.
Represent compiled state-node inspection as a six-way discriminated union so atomic, compound, parallel, final, history, and choice metadata narrow without impossible field combinations.
Expose a captured full machine snapshot to event, eventless, and completion transition contexts; add serializable state-owned activity inspection for invokes, timers, and child machines; and surface resolved Effect Schema annotations plus descriptive pseudo-state annotations through state-node inspection.
Add first-class, type-safe choice pseudo-states with Effectful resolvers, inspectable target bounds, stable-snapshot exclusion, and MachineTest trace, coverage, verification, generated-model, and reference-model support.
Patch changes
Batch lossless ordered snapshot publications across each synchronous compiled machine drain segment and share the compact process context through prototype methods.
Reduce the retained memory of invoked machines by running the original child logic with a compact guarded
sendParentchannel instead of allocating a wrapper process for every invocation.Fix first-use nested history defaults by requiring a complete source-independent configuration containing the history owner and using it to rebuild inactive compound and parallel ancestors.
Consolidate generic and compiled state-scoped invocation lifecycle handling behind one owner-local child registry, preserving duplicate detection, stale callback isolation, and path-scoped teardown without changing the public API.
Reuse the validated statechart configuration while draining queued event batches, avoiding repeated snapshot normalization without retaining the cache while a machine is idle. Canonicalize history snapshot paths in machine document order so batched and public planning produce identical snapshots.
Capture event dispatch definitions when handlers are registered and make compiled execution fall back safely when a state configuration contains unknown semantics.
Decompose machine topology, schema protocols, configurations, snapshot serialization, command execution, semantic planning, and compiled execution plans into explicit internal modules without changing runtime behavior or public types.
Reduce compiled-machine lifecycle and retained-memory overhead by using owner-local terminal arbitration while preserving public completion, cleanup, and first-terminal-wins semantics.
Organize public, internal, testing, and unstable modules into Effect-shaped directories without changing package entrypoints. Add a TypeScript-resolved architecture check that enforces dependency direction, test boundaries, acyclic runtime imports, and internal naming conventions.
Reduce retained machine memory by sharing immutable zero-input execution descriptors across process instances while preserving per-instance invoke state.
Suspend compiled statechart workers while their mailboxes are idle and start an on-demand drain when an event arrives. This reduces retained heap for idle machines and invoked families without changing event ordering, terminal arbitration, or the public machine API.
Skip child-registry allocation for statecharts that cannot invoke child processes, while preserving empty child lookup, observation, send, and stop behavior.
Remove unused internal declarations, imports, and type assertions, and enforce unused-local and unchecked-index diagnostics during type checking.
Keep
MachineTest.runservice-free for machines with invoked effects, matching the pureMachine.planInitialandMachine.planAPIs it uses internally.Separate the public Machine, MachineTest, AtomMachine, and ClusterMachine contracts from their internal implementations. Enforce designated implementation seams and explicit public function signatures through the architecture check without changing the package API.
Run eligible compound and parallel statecharts on a compact indexed configuration with precompiled numeric topology and event dispatch. Public snapshots are materialized only at process boundaries, reducing per-event configuration work while preserving schema validation, raised-event stabilization, transition conflict rules, resume behavior, and the public machine API.
Compact running machine workers into a single generator loop and allocate emitted-event runtime closures only when a machine emits, reducing idle heap and improving event throughput without changing scheduler yield semantics.
Compact invoked-child session bookkeeping into an atomic runtime table, reducing parent and child lifecycle overhead without changing invoke ordering or race protection.
Run compiled statecharts on a compact, class-backed process kernel that shares operation implementations and materializes terminal and observation primitives only when used. This reduces retained memory and improves runtime throughput while preserving the general
Machine.logicprocess contract, lifecycle arbitration, child cleanup, and publicMachineRefAPI.Run compiled statecharts and their invoked machine children with a single process fiber, reducing lifecycle overhead and idle memory while preserving the general
Machine.logicruntime contract.Compile eligible machine initial-state normalization, reuse the validated startup configuration, and unify invoked-child ownership with the runtime child registry.
Compile reusable statechart execution metadata and run eligible flat machines through a synchronous specialized planner inside the compact Effect process kernel. This removes per-event Effect wrappers and repeated topology construction while preserving schema validation, raised-event stabilization, lifecycle ordering, observation, interruption, invoked children, and the public planning and machine APIs.
Remove the library-owned eight-level handler-tree inference ceiling. Nested handler validation and accumulated state, error, service, choice, history, and output evidence now continue until TypeScript's normal compiler limits.
Deliver invoked child snapshots directly from the child runtime, removing the replay PubSub and watcher fiber previously retained by every snapshot-mapped invocation.
Reduce invoked-child memory and lifecycle overhead by delivering terminal outcomes directly through process supervision and allocating a watcher fiber only for invokes that map active child snapshots.
Reduce hierarchical transition overhead by materializing the compiled transition context as a plain object.
Let the Effect runtime scheduler control cooperative yielding while draining machine event bursts, preserving runtime scheduler configuration and avoiding a forced scheduler turn after every event.
Reduce the retained memory of
childChangesobservers with a compact ordered handoff that avoids replaying complete child-registry snapshots.Reduce child lifecycle overhead by reserving child starts atomically and specializing zero- and one-item invoke cleanup without weakening parallel finalization.
Avoid rebuilding a complete configuration for same-state atomic snapshot transitions in the compiled flat executor.
Reuse settled startup outcomes, fast-path idle childless startup and stop, and move invoked session coordination onto a compact parent-owned kernel.
Add local and pull request runtime benchmark reporting for pure planning, end-to-end event drainage, machine lifecycle throughput, and idle-machine memory growth against the compiled package, XState 5, and the published XState 6 alpha.
Start eligible compiled invoked machines directly from their synchronous initial kernel while preserving per-instance input evaluation, inherited services, scoped ownership, observation, and terminal behavior. Reuse the immutable process descriptor captured by
Machine.invokeMachineacross parent instances.Defer construction of a compiled machine's
StoppedErroruntil its stoppedjoinresult is observed.Initialize eligible compiled machines through the shared synchronous startup kernel while preserving Effect services and invoke lifecycle behavior.
Reduce runtime planner allocation overhead by reusing compiled schema decoders and state topology paths, deferring effect service allocation until it is needed, and avoiding unused snapshots.
Update the required Effect runtime and development integration from
4.0.0-beta.102to4.0.0-beta.105.Update the supported Effect 4 beta to 4.0.0-beta.107, preserve schema-arbitrary diagnostics across Effect's new generator factory API, and refresh the XState v6 runtime benchmark baseline to 6.0.0-alpha.31.
Allocate child process scopes and observable child registries only when a machine uses child-management capabilities.
Allocate change-observation resources only when a machine's
changesstream is first consumed, while preserving snapshot replay, terminal completion, and the existingMachineRefAPI.Use a compact FIFO mailbox for on-demand compiled statecharts while retaining Effect Queue for persistent custom process logic. This reduces idle heap for machines and invoked families while preserving FIFO delivery, terminal send rejection, and wake-up behavior.
Reduce the memory retained by running machines by consolidating process termination into a single supervisor signal.
Reduce managed runtime memory and lifecycle overhead by removing redundant process coordination state, reusing the live runtime service, and allocating invoke management only for machines with invoke definitions.
Reduce child-machine ownership memory by consolidating supervision and registry state, tracking anonymous children without per-child scope finalizers, and allocating child observation resources only when consumed.
Reduce child-capable process memory and lifecycle overhead with a compact synchronous registry, while lazily allocating ordered child-change publication only when observed.
Add
Machine.eventfor constructing protocol-owned events that validate once and avoid redundant decoding on repeated delivery.Run eligible flat machines on the same indexed execution representation as compound and parallel machines, with a specialized single-root dispatch loop and owner-local state slots. This removes the separate flat configuration executor while preserving schema validation, raised events, immutable public snapshots, resume behavior, and terminal completion.
Store compiled machine snapshots in an owner-only mutable reference while retaining atomic terminal reservation and lazy observation. This reduces transition overhead and idle heap without changing the public API, event ordering, or terminal behavior.
Replace implicit compiled-process capability markers with one typed execution descriptor, make lifecycle states explicit, and centralize snapshot publication at Effect boundaries.
Add direct generic/indexed planner and generic/compiled runtime strategy guardrails, including startup, targetless, reentry, invoke, snapshot-stability, and generated-model coverage. Expand the runtime benchmark suite with hierarchical and parallel planning, observed hierarchical execution, and generic process lifecycle measurements.
Harden machine execution and definitions while preserving the existing Effect-native API. Self-stop now uses supervisor-owned terminal arbitration without initialization or worker deadlocks; execution adapters consistently reject incomplete output, history, and choice implementations; finite-union event tags narrow correctly; and machine guards verify the runtime brand value.
State definitions now reject unknown node properties and unsafe state keys at compile time and runtime with path-local diagnostics. Add deterministic lifecycle, adversarial snapshot-codec, type-performance, activity-lifecycle, and planner-versus-runtime verification coverage.
Compile event dispatch, ancestry lookup, value-only leaf updates, and final-state checks for compound and parallel machines that do not use automatic or lifecycle transitions. Unsupported statechart capabilities continue through the general planner with unchanged semantics.
Use the compact synchronous teardown path for idle compiled invoked children while preserving ownership cleanup and stopped completion behavior.
Make compiled execution plans expose an honest runtime-only contract, document their owned indexed state, and strengthen differential coverage for mutation-sensitive transitions.
Harden planning, snapshot round trips, and logical runtime resumption for valid typed machines. Initial choices no longer retain abandoned roots, history fallbacks resolve nested choices before snapshot normalization, and the independent finite-model oracle follows nested choice initializers.
v0.3.0
Minor changes
Add fully typed shallow and deep history states. History targets restore schema-validated state values, support typed defaults before the first capture, require only the initializers needed by shallow restoration, preserve parallel configurations, and round-trip through snapshot encoding and decoding.
Add safe
.fromstate construction to initial and transition target builders. Constructor inputs are resolved through the selected state schema during planning, preserving defaults and class identity while reporting validation failures asMachineSchemaDecodeErrorvalues.Allow state builder
.from()calls to omit the constructor input when the selected schema accepts{}. Required fields and compound or parallel child selection remain type-safe, and omitted inputs still run through schema construction during planning.
Patch changes
Allow local and branch targets to enter inactive nested parallel states. These targets now require a complete selection for every parallel region while preserving partial updates for parallel states that are already active.
Improve compile-time diagnostics for invalid state definitions, event protocols, and handler configurations. Errors now retain the relevant configuration shape and state path while preserving existing inference and type safety.
Preserve every machine protocol channel when creating bound AtomMachine bridges from deeply composed handled machines. Inline invoked children also retain their exact error, service, event, and output types instead of inheriting erased contextual
anychannels.
v0.2.0
Minor changes
Separate public commands from machine-local events with typed
eventsandinternalEventsprotocols, unique-tag validation, and public-schema validation for Cluster delivery.Move finality entirely into state definitions. This is a breaking API change: remove
type: "final"from handlers and declare final states and output schemas withMachine.defineStates. Handlercontext.actionis also removed in favor of the single canonicalMachine.actionstaging API. Planning and execution now require an output implementation for every declared output schema and expose discriminated, schema-derived terminal results.Add typed helpers for one-shot Effects, timers, staged actions with a returned transition value, state retagging, immediate parents, and identity-safe invoked child addressing. Add bound Atom runtime factories, fail-aware results, equality-aware selectors, and reactive child bridges, while tightening startup and protocol error types. Protocol schemas are owned by each machine and rely on Effect's schema parser memoization instead of package-level cache registries. Reactive child bridge identity uses Effect's standard
Atom.familyprimitive. Atom selectors now infer exact state paths and values from their bridge snapshot and no longer require a separateDefinedStatesargument.Match child descriptors by id and machine identity, and split the old overloaded child constructor: use
Machine.child(id, machine)for complete statecharts andMachine.childAddress<Event>(id)for lower-level process addresses.Machine.invokenow separates its lifecycleidfrom an explicit typedaddress. Remove the directAtomMachine.make(runtime, machine)overload in favor ofAtomMachine.bind(runtime).make(machine), and make the default child Atom startup-error channelunknown.