Release history

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 to make and 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 use initialize: true, and branch declarations use { initialize: true } with select.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. Replace targets.root.A.B with "A.B", update: targets.root with update: "root", and { target: targets.root, input } with { initialize: input }. Rename any top-level state named root. Declarations stored in variables before they reach make or .handle need as const.

Patch changes

  • Check the public Machine operations 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 become StartupError, 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-callback also checks initialize input 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, and runtimeCommands now produce native Arbitrary values from effect/unstable/arbitrary/Arbitrary. Custom input, event, and command generators must use that module instead of FastCheck. Use Arbitrary.schema(schema) for schema-derived generators, Arbitrary.array for sequences, and Arbitrary.sampleEffect or Arbitrary.checkEffect to sample and check them. In @effect/vitest, replace fastCheck: { numRuns } with arbitrary: { 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 with StoppedError, and completion without a match fails with Cause.NoSuchElementError. Compose Effect.timeout to bound the wait; cancellation releases observation without stopping the machine.

    Effect, Stream, and timer invocations now run inside Machine.invoke spans 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: true restarts the root lifecycle when the handler is at root. Root updates continue to use { update: targets.root, data } and retain active children.

    Use target instead of initial on 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 with update: { data: owner } inside the same call. History fallbacks use target({ 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. Remove initial from Machine.state, remove initial and initialConfiguration from Machine.make, and move shared startup data into the root handler's root value or input callback. Each compound declares initial: { target, data? }; parallel handlers use an initial map 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 from callbacks with data, which accepts literals or callbacks. Supply already-decoded values with { decoded: true, data: valueOrCallback }. For root or region data that itself contains decoded: true and data fields, use a callback returning the ordinary schema input. Bound branch and history builders retain .from and .decoded. Replace command-producing initialize callbacks with entry/exit handlers or invocations; definitions become executable only after .handle captures 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 use update for retained state data. initial, history, none: true, guard, and reenter express their respective operations explicitly.

    Register effects, streams, timers, logic, and children inside Machine.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 branches in make for conditional outcomes, queued commands, or nested construction. A transition uses { branches: "group", resolve }; its select constructors 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.state root, passed to Machine.make({ root }). Root fields support data-only machines and shared data that survives child transitions. Replace the former top-level state map with a root descriptor, put child handlers under handle({ states }), and use initial for root values or initialConfiguration for a complete startup override. Add direct target construction, non-reentering self.update, and guards with ancestor fallback. Construct local event protocols from field records or import existing schemas with the explicit FromSchemas constructors.

    Render typed state paths with React MachineState and own isolated instances with createMachineContext(AtomMachine.factory(machine)). Providers capture startup input without subscribing to state. Replace Atom bridge .state reads with .result or path selectors; .snapshot retains runtime status. Core MachineRef.state remains 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 removed Machine.Emit, Machine.Emits, Machine.EmitOf, and Machine.PseudoStateAnnotations aliases with EmittedEvent, EmittedEvents, EmittedEventOf, and SchemaLessStateAnnotations.

Patch changes

  • Align Machine.can types with its existing support for public and internal events. Querying an internal event does not make it sendable through MachineRef.send. Preserve interruption during snapshot encoding and decoding, validate reused event and emission values, and capture machine definitions independently of caller mutations.

    Make MachineTest.verify lazy 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 containingState and ancestors are 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.can for 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.can for 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.factory and bound factory for reusable, fully inferred machine bridge constructors. Every call creates a fresh lazy bridge, while ReturnType<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-react with useMachineAtom for 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.family and AtomMachine.familyChild for 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 build for 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-callback to detect direct Promise, fetch, timer, and scheduling operations, and extend no-redundant-resolve fixes 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.0 without 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-machine with recommended rules for redundant default resolvers, asynchronous planning callbacks, and one-use intermediate Machine.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.State now retains each state's projected value and output schemas. Consumers of serialized documents must accept schemaVersion: 3 and the new valueSchema and outputSchema fields.

    Replace planner-backed browser simulation and MachineSimulator with the side-effect-free MachineWalkthrough module. A walkthrough derives its initial and active configurations entirely from MachineDocument, 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.start and MachineSimulator.send to MachineWalkthrough.start, MachineWalkthrough.choices, and MachineWalkthrough.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.inputEventSchemas so 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 MachineSimulator and 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-machine command 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-polling on 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-machine and @typeonce/effect-machine-devtools at 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 nested builder(value, ...) calls with .decoded(value, ...); keep .from(input, ...) for schema make input. Plain state-update resolvers now expose the decoded owner as current and its construction builder as owner, replacing the previous ancestors plus target pattern.

    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 updates array naming replaced owners.

v0.21.0

Minor changes

  • Add to.local.update(...) and to.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 through target.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 call children.spawn(Family(id), { input }) inside an invoked Effect. Successfully started children survive owner state changes and remain addressable through machine references and AtomMachine until they stop or their parent stops.

    Logic.Scope.spawn accepts 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.encodeSnapshot return a canonical JSON representation or fail with MachineSchemaEncodeError. Encoded state values, completion outputs, and history values are now typed as Schema.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. Declared Schema.Void and Schema.Undefined completion outputs now use their canonical null encoding; an omitted output is reserved for final states that do not declare an output schema.

    ClusterMachine.make now 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 as SnapshotEncodeFailure without advancing the checkpoint.

    MachineTest.observedGraph continues to support process-local state. Its node encoded field 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, and Machine.SnapshotAt accept either the object returned by Machine.states or a machine definition, while preserving exact path validation and excluding control-only paths from Value.

    Machine.Machine.Input<M> now extracts the decoded startup value and is never when the machine uses Schema.Void. Code that needs the startup schema should migrate from Machine.Machine.Input<M> to Machine.Machine.InputSchema<M>; code that previously used Machine.Machine.Input<M>["Type"] can use Machine.Machine.Input<M> directly.

  • Replace Machine.invoke and its object-configuration helper types with state-local fluent invocation chains. Select an Effect, Stream, timer, process logic, or child from the handler's from parameter, 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-optional parent, compatible owners are checked when the child is invoked, and independent root APIs reject the machine.

    Replace parentEvents: ParentEvents with parent: Machine.optionalParent(ParentEvents) when the same machine must remain valid as either a root or a child. Optional declarations retain the previous parent | undefined behavior. Machines without a parent declaration no longer expose parent in schema-first behavior contexts.

  • Make definition-time topology instructions immutable values. Use to.none, declared .initial and history properties, and to.local.with without an empty call; state and choice destinations such as to.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.targetless and the { target: Machine.targetless, resolve } shorthand. Use (to) => to.none or (to) => to.none.resolve(...); block-bodied targetless resolvers may omit an explicit return 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 literal declinable: true to .resolve(...) when the resolver must receive decline(). Bare targets are accepted only when their schemas support default construction.

    Remove the machine-definition .invoke(...) method. Use Machine.invoke(...) in every state; it now retains the owning state, event, parent-event, output, error, element, snapshot, and service inference directly inside handle(...).

  • Add opt-in declinable transitions for conditional statechart dispatch.

    Set declinable: true on Machine.transition to expose a typed decline() 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 AtomMachine selectors so machines and invoked children with declared emitted events retain typed state selection and matching after make or bind.

v0.16.0

Minor changes

  • Replace conditional cases and otherwise transitions with named branches. Each branch declares one static target, while the required synchronous resolve function uses ordinary TypeScript control flow to return a typed select builder.

    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 stream sources to Machine.invoke. Stream values are handled through the typed onElement transition one committed parent macrostep at a time, while completion and typed failures use onDone and onFailure. 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 handle a one-shot implementation boundary. Machine.make(...) now returns a Machine.Definition; calling handle(...) returns a Machine without another handle method.

    To create multiple implementations, call handle independently on the original definition. Migrate chained calls by combining their state configurations into one handler tree.

  • Add Machine.state for topology that is genuinely reused at multiple mounts, plus definition-bound States.path(...) and Machine.Snapshot<typeof States> helpers for checked finite path families and snapshot queries.

    Rename Machine.defineStates to Machine.states. Migrate by replacing Machine.defineStates({...}) with Machine.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 typed self and parent protocols.

    Event protocol examples now pass tagged unions directly to Machine.events, Machine.internalEvents, and Machine.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.coverage report transition definitions and their exact branches separately. Read definition coverage through coverage.transitions.definitions and conditional branch coverage through coverage.transitions.branches.

    Replace the targetBounds verification law group with definitions. The new laws validate the declared startup root, transition registration, retained branchIndex, and the selected branch's exact target kind and scope.

  • Add exact transitionCoverage to MachineTest.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.initialDefinition exposes the root startup selection without executing its resolver.

    Use branchIndex to associate a retained transition with the corresponding entry in Machine.transitionDefinitions(machine).branches. Direct transitions use index 0; conditional cases retain their declaration index and otherwise follows the final case.

  • Require Machine.transition for every machine transition and capture each possible target as static machine topology. Direct transitions declare target and resolve; conditional transitions declare titled cases whose when functions return Option, plus an explicit otherwise branch. The selected target builder and conditional match value are inferred in each resolver.

    Initial state construction now uses the same target and resolve shape, restricted to the machine's declared initial state. Replace process logic previously created with Machine.transition by Machine.logic, and replace function handlers, target upper-bound lists, and States.initial construction with the explicit transition and initial target selectors.

  • Allow conditional Machine.transition definitions to infer any number of heterogeneous cases. Define cases with its locally supplied branch constructor 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 cases array with branch({ ... }) inside the cases: (branch) => [...] factory. Direct transitions and otherwise keep their existing shape.

  • Make MachineTest.verify accept 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 as definitions.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.invoke effect source 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), or initial.from(input) to enter the initial configuration declared by Machine.defineStates; the same operation is available through compatible local and branch target scopes. Schema-valued implicit children are constructed by initialize: ({ builder }) => ..., including fluent completion of every valued parallel region. Missing initializers are reported at handle(...), and .from validation remains a typed MachineSchemaDecodeError during planning.

    State handler initial and its StateInitial* utility types have been replaced by initialize and StateInitialize*. Migrate compound initializers from initial: () => new Child(...) to initialize: ({ 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).inspection and AtomMachine.inspection(machineAtom).

    The hot Effect Stream observes 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>; // after

    The inferred self and parent fields and all runtime behavior are unchanged.

v0.11.0

Minor changes

  • Add Machine.prepare for composing snapshot and emission streams before a machine initializes, while keeping Machine.start as 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 invocation self and parent references use the exact public input and parentEvents protocols.

v0.10.0

Minor changes

  • Make Machine.events and Machine.internalEvents definition-time protocol descriptors that are passed directly to Machine.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 .make methods.

    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.event constructor. 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 or target.none(); declared targets remain an upper bound on concrete destinations and never exclude target.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 with emit, and observe the hot, non-replaying MachineRef.emissions stream. Children declare the public inputs they expect from their owner through parentEvents, then communicate explicitly with the typed, optional parent actor 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 every parentEvents case is accepted by the parent. This release renames structural handler ancestry to containingState and ancestors, 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) and Machine.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 MachineSchemaDecodeError through planning or the running machine instead of throwing at the construction call site.

  • Replace Machine.invokeEffect, Machine.after, Machine.invokeMachine, and Machine.effect with one inline invoke lifecycle object API and a zero-runtime Machine.invoke inference helper.

    Choose an effect, after, logic, or child source and handle typed outcomes directly with onDone, onFailure, and onSnapshot. 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 schema when they own no data. Schema-less atomic, compound, parallel, and final states keep full control-flow semantics while exposing value-free .from(...) builders, undefined handler 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.defineStates query helpers to inspect an extracted snapshot subtree. Paths remain absolute and type-safe, but get, getSnapshot, and matches can 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.selectSnapshot and AtomMachine.selectSnapshotChild combinators 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 produce Option.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 sendAndAwait API.

  • 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 runRuntimeCommands and formatRuntimeTranscript names 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/testing entrypoint 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 | done trigger 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 typed raise, emit, sendTo, and stop operations; asynchronous work remains available through invoked Effects, actors, and child machines.

    Remove Machine.action, Machine.runtime, and Machine.runActions. Planning now returns closed actor commands, while managed runtimes execute those commands around state publication and typed emission delivery.

  • Add first-class logical snapshot resumption with Machine.resume, plus lazy AtomMachine.resume and 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 sendParent channel 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.run service-free for machines with invoked effects, matching the pure Machine.planInitial and Machine.plan APIs 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.logic process contract, lifecycle arbitration, child cleanup, and public MachineRef API.

  • Run compiled statecharts and their invoked machine children with a single process fiber, reducing lifecycle overhead and idle memory while preserving the general Machine.logic runtime 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 childChanges observers 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.invokeMachine across parent instances.

  • Defer construction of a compiled machine's StoppedError until its stopped join result 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.102 to 4.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 changes stream is first consumed, while preserving snapshot replay, terminal completion, and the existing MachineRef API.

  • 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.event for 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 .from state 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 as MachineSchemaDecodeError values.

  • 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 any channels.

v0.2.0

Minor changes

  • Separate public commands from machine-local events with typed events and internalEvents protocols, 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 with Machine.defineStates. Handler context.action is also removed in favor of the single canonical Machine.action staging 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.family primitive. Atom selectors now infer exact state paths and values from their bridge snapshot and no longer require a separate DefinedStates argument.

    Match child descriptors by id and machine identity, and split the old overloaded child constructor: use Machine.child(id, machine) for complete statecharts and Machine.childAddress<Event>(id) for lower-level process addresses. Machine.invoke now separates its lifecycle id from an explicit typed address. Remove the direct AtomMachine.make(runtime, machine) overload in favor of AtomMachine.bind(runtime).make(machine), and make the default child Atom startup-error channel unknown.

Type at least two characters to search.