Skip to content

Latest commit

 

History

History
439 lines (314 loc) · 25.5 KB

File metadata and controls

439 lines (314 loc) · 25.5 KB

effect-machine

0.16.0

Minor Changes

  • d17665e Thanks @cevr! - Replace ambient Scope detection with explicit ActorScope service

    • Machine.spawn and system.spawn no longer attach cleanup finalizers to ambient Scope.Scope. This fixes a bug where unrelated scopes would unexpectedly tear down actors.
    • New ActorScope service tag — when present in context, actors attach stop finalizers to it.
    • New Machine.scoped(effect) helper bridges Scope.ScopeActorScope for opt-in auto-cleanup.
    • Backport call improvements to v3: warning log on stopped actor, complete ProcessEventResult fields with satisfies for type safety.
    • Fix bun test tsconfig resolution and add test:all / v3 tests to gate.
  • ec8ccd3 Thanks @cevr! - Upgrade to effect 4.0.0-beta.47, require tsgo for type checking

    Breaking: Minimum peer dependency is now effect@>=4.0.0-beta.47. The ServiceMap module was removed upstream — all ServiceMap.Service usages are now Context.Service.

    • Rename ServiceMap.ServiceContext.Service throughout
    • Rename Effect.services()Effect.context()
    • Switch type checker from tsc to tsgo (native Go compiler via @typescript/native-preview)
    • Switch Effect LSP from tsconfig plugin patch to effect-language-service diagnostics CLI
    • Simplify tsconfig.json for TypeScript 6 defaults

0.15.2

Patch Changes

  • 0c81fd3 Thanks @cevr! - fix(v3): backport reply metadata stripping from event constructor payloads

    Event.reply(...) constructor payload typing no longer leaks reply schema metadata into user payload arguments.

0.15.1

Patch Changes

  • 612e686 Thanks @cevr! - Fix Event.reply(...) constructor payload typing so reply schema metadata does not leak into user payload arguments.

    Add regressions for:

    • payload-bearing reply event constructors accepting plain payload objects
    • ask() with payload-bearing reply events

0.15.0

Minor Changes

  • d518ed6 Thanks @cevr! - Cold spawn + Recovery/Durability lifecycle API (v3 backport).

    Breaking changes:

    • Machine.spawn now returns an unstarted actor. Call yield* actor.start to fork the event loop, background effects, and spawn effects. Events sent before start() are queued.
    • system.spawn auto-starts — no change needed for registry-based spawns.
    • PersistConfig<S> is removed. Use Lifecycle<S, E> instead.

    New APIs:

    • ActorRef.start — idempotent Effect that starts the actor
    • Recovery<S> — resolves initial state per generation during actor.start
    • RecoveryContext<S>{ actorId, generation, machineInitial }
    • Durability<S, E> — saves state after committed transitions
    • DurabilityCommit<S, E>{ actorId, generation, previousState, nextState, event }
    • Lifecycle<S, E>{ recovery?, durability? }

    Migration from PersistConfig:

    // Before (PersistConfig)
    Machine.spawn(machine, {
      persist: {
        load: () => storage.get(key),
        save: (state) => storage.set(key, state),
        shouldSave: (state, prev) => state._tag !== prev._tag,
        onRestore: (state, { initial }) => validate(state),
      },
    });
    
    // After (Lifecycle)
    const actor =
      yield *
      Machine.spawn(machine, {
        lifecycle: {
          recovery: {
            // Replaces load() + onRestore() — single callback
            resolve: ({ actorId, generation, machineInitial }) =>
              storage.get(key).pipe(
                Effect.map(Option.fromNullable),
                // Do any validation/migration here
              ),
          },
          durability: {
            // Receives full commit context, not just the new state
            save: ({ actorId, generation, previousState, nextState, event }) =>
              storage.set(key, nextState),
            shouldSave: (state, prev) => state._tag !== prev._tag,
          },
        },
      });
    yield * actor.start; // NEW: explicit start required

    Key differences from PersistConfig:

    • Recovery.resolve merges load() + onRestore() into one callback
    • Recovery.resolve receives RecoveryContext with actorId, generation (0 = cold start, 1+ = supervision restart), and machineInitial
    • Durability.save receives DurabilityCommit with full transition context (previous state, next state, event, generation)
    • Recovery runs during actor.start, not during allocation
    • hydrate option overrides recovery entirely (resolve is never called)

0.14.0

Minor Changes

  • ff7fd8f Thanks @cevr! - Unified slot system redesign + local persistence + slot schemas.

    Breaking changes (pre-1.0):

    • Slot.Guards / Slot.Effects replaced by Slot.define + Slot.fn
    • guards: / effects: on Machine.make replaced by single slots: field
    • Slot handlers take only params — no ctx parameter. Use yield* machine.Context for machine state.
    • HandlerContext.guards / HandlerContext.effects replaced by HandlerContext.slots
    • StateHandlerContext.effects replaced by StateHandlerContext.slots
    • Removed: SlotContext, GuardsDef, EffectsDef, GuardSlot, EffectSlot, GuardHandlers, EffectHandlers, HasGuardKeys, HasEffectKeys
    • SlotProvisionError.slotType is now "slot" only (was "guard" | "effect" | "slot")
    • Machine.spawn slots option is now ProvideSlots<SD> (type-checked, was Record<string, any>)

    New APIs:

    • Slot.fn(fields, returnSchema?) — define a slot with typed params and arbitrary return type
    • Slot.define({ ... }) — create a slots schema from slot definitions
    • SlotFnDef.inputSchema / outputSchema — materialized schemas for runtime validation and serialization
    • SlotsSchema.requestSchema / resultSchema / invocationSchema — wire-format schemas for RPC and persistence
    • slotValidation option on Machine.make — runtime input/output validation (default: true)
    • SlotCodecError — tagged error for validation failures (raised as defect)
    • PersistConfig<S> — local persistence for Machine.spawn:
      • load() → hydrate from storage
      • save(state) → save after transitions
      • shouldSave?(state, prev) → filter saves
      • onRestore?(state, { initial }) → recovery decision hook
    • ActorSystem.spawn now accepts slots and persist options
    • Multi-state .spawn() and .task() overloads (array of states)
    • .task() shorthand — omit onSuccess when task returns Event directly

    Internal:

    • resolveActorSystem() and runSupervisionLoop() extracted from createActor
    • Queue.clear replaces manual poll loop for shutdown drain
    • Plain-object return bug fixed in slot resolve (uses Effect.isEffect)
    • materializeMachine threads _slotValidation through copies

Patch Changes

  • 8e8a9ce Thanks @cevr! - - feat: union-level derive on State and Event schemas — dispatches by _tag, preserves specific variant subtype
    • fix: derive partial keys not in target variant are now silently dropped
    • fix: .task() onSuccess is now optional — omit when task returns Event directly

0.13.0

Minor Changes

  • 74e3fea Thanks @cevr! - Add actor supervision with automatic restart on defect.

    New APIs:

    • Supervision.restart({ maxRestarts, within, backoff }) — Schedule-based restart policy
    • Supervision.none — no supervision (default, crashes are terminal)
    • actor.awaitExit — resolves with ActorExit<S> when actor terminally stops
    • actor.watch(other) — now returns ActorExit<unknown> (breaking, pre-1.0)

    New types:

    • ActorExit<S>Final { state } | Stopped | Defect { cause, phase }
    • DefectPhase"transition" | "spawn" | "background" | "initial-spawn"
    • Supervision.Policy — Schedule-based restart policy interface

    Usage:

    import { Machine, Supervision } from "effect-machine";
    
    const actor =
      yield *
      Machine.spawn(machine, {
        supervision: Supervision.restart({ maxRestarts: 3, within: "1 minute" }),
      });
    
    const exit = yield * actor.awaitExit; // ActorExit<S>

    Breaking changes (pre-1.0):

    • watch() returns Effect<ActorExit<unknown>> instead of Effect<void>
    • SystemEvent.ActorStopped gains exit: ActorExit<unknown> field
    • New SystemEvent.ActorRestarted variant

    Internal:

    • Runtime kernel split: cell-owned resources, actorScope, exitDeferred
    • Background/spawn/transition defect detection with DefectPhase tagging
    • Generation owner fiber for actorScope lifecycle
    • Effect.runForkWith for proper service propagation (v4)
    • globalXInEffect diagnostics enabled in tsconfig

0.12.0

Minor Changes

  • f26b21f Thanks @cevr! - feat(cluster): entity persistence with snapshot and journal strategies

    Add opt-in state persistence for entity-machines across deactivation/reactivation:

    • Snapshot strategy: periodic background saves + deactivation finalizer. Simple, fast.
    • Journal strategy: inline event append on each Send/Ask RPC, replay on reactivation. Full audit trail.
    • PersistenceAdapter service tag with saveSnapshot, loadSnapshot, appendEvents (CAS), loadEvents
    • InMemoryPersistenceAdapter for testing/development
    • PersistenceKey = { entityType, entityId } prevents cross-type collisions
    • Journal append failures defect the entity (cluster retry restarts from last snapshot)
    • Snapshot scheduler only in snapshot-only mode (prevents state/version tear in journal mode)
    • v3 backport included

    Also includes the cluster overhaul (runtime kernel, EntityActorRef, WatchState, self.reply, self.spawn).

  • 33d8a87 Thanks @cevr! - feat: add typed reply schemas for ask()

    • Event.reply(fields, schema) — declare reply-bearing events with schema validation
    • Machine.reply(state, value) — branded helper replacing duck-typed { state, reply }
    • actor.ask(event) — infers return type from event's reply schema; non-reply events are type errors
    • Runtime validation: reply values decoded through schema; decode failure = defect
    • Entity-machine: Ask RPC propagates replies through cluster boundary
    • Backported to v3

0.11.0

Minor Changes

  • 6bdee0c Thanks @cevr! - Delete monolithic persistence subsystem, add composable primitives.

    Added:

    • Machine.replay(built, events, { from? }) — fold events through transition handlers to compute state. Respects postpone rules and final-state cutoff. Runs effectful handlers with stubbed self/system.
    • actor.transitions — PubSub-backed stream of { fromState, toState, event } on every successful transition. Observational, not a durability guarantee.

    Removed:

    • PersistenceAdapter, PersistenceAdapterTag, PersistenceError, VersionConflictError
    • PersistentMachine, PersistentActorRef, PersistenceConfig
    • createPersistentActor, restorePersistentActor, isPersistentMachine
    • InMemoryPersistenceAdapter, makeInMemoryPersistenceAdapter
    • Machine.persist(), BuiltMachine.persist()
    • ActorSystem.restore, ActorSystem.restoreMany, ActorSystem.restoreAll, ActorSystem.listPersisted

    Migration: Compose persistence from primitives:

    • Snapshot: actor.changes → save to your store
    • Event journal: actor.transitions → append events
    • Restore from snapshot: Machine.spawn(machine, { hydrate: loadedState })
    • Restore from events: Machine.replay(machine, events)Machine.spawn(machine, { hydrate: state })

Patch Changes

  • 3ff2dfb Thanks @cevr! - Fix multi-stage postpone drain in live actor event loop. Previously, postponed events were drained in a single pass — if a drained event caused a state change that made other postponed events runnable, they waited until the next mailbox event. Now loops until stable, matching simulate() and replay() behavior.

0.10.0

Minor Changes

  • 921e063 Thanks @cevr! - OTP-inspired API redesign:

    • rename dispatch→call, add cast alias for send
    • extract sync helpers to actor.sync.* namespace
    • add ask() for typed domain replies from handlers
    • add .timeout() for gen_statem-style state timeouts
    • add .postpone() for gen_statem-style event postpone
    • fix reply settlement (ActorStoppedError on stop/interrupt)

    Breaking: removed top-level sync methods (sendSync, stopSync, etc.), removed dispatchPromise.

  • eee2ff4 Thanks @cevr! - Backport all v4 features to Effect v3 variant + restructure into v3/ directory

    Restructure:

    • src-v3/v3/src/, tsconfig.v3.jsonv3/tsconfig.json, tsdown.v3.config.tsv3/tsdown.config.ts
    • Added v3/test/ with full test suite (248 tests)
    • Package exports unchanged: effect-machine/v3, effect-machine/v3/cluster

    Features backported from v4:

    • call() — serialized request-reply (OTP gen_server:call)
    • cast() — fire-and-forget alias for send (OTP gen_server:cast)
    • ask() — typed domain reply from handler's { state, reply } return
    • ActorRef.sync namespace — replaces flat sendSync/stopSync/snapshotSync/matchesSync/canSync
    • .timeout() builder — gen_statem-style state timeouts
    • .postpone() builder — gen_statem-style event postpone with drain-until-stable
    • hasReply structural flag on ProcessEventResult
    • ActorStoppedError / NoReplyError error types
    • makeInspectorEffect / combineInspectors / tracingInspector inspection helpers
    • actorId threaded through all handler contexts

    Bug fix:

    • State.derive() now guards against _tag override in partial argument

0.9.0

Minor Changes

  • 6e3497b Thanks @cevr! - Add dispatch and dispatchPromise to ActorRef — synchronous event processing with transition receipts.

    dispatch(event) — Effect-based. Sends event through the queue (preserving serialization) and returns ProcessEventResult<State> with { transitioned, previousState, newState, lifecycleRan, isFinal }. OTP gen_server:call equivalent.

    dispatchPromise(event) — Promise-based. Same semantics as dispatch for use at non-Effect boundaries (React event handlers, framework hooks, tests).

    Also exports ProcessEventResult from the public API.

0.8.0

Minor Changes

  • 675f461 Thanks @cevr! - Add effectful inspectors, scoped from(...) transitions, and named task inspection events.

Patch Changes

  • ac8f691 Thanks @cevr! - Update Effect dependency to 4.0.0-beta.35

0.7.2

Patch Changes

  • f6c5bbe Thanks @cevr! - Update Effect dependency to 4.0.0-beta.26

0.7.1

Patch Changes

  • fa54c61 Thanks @cevr! - Update Effect dependency to 4.0.0-beta.21
    • Replace Effect.makeSemaphore with Semaphore.make (removed in beta.6)

0.7.0

Minor Changes

  • 34c85b8 Thanks @cevr! - Migrate to Effect v4 (4.0.0-beta.5)
    • Default export now targets Effect v4 — v3 users should import from effect-machine/v3
    • Migrate src/ and test suite to v4 APIs (Effect, Schema, SubscriptionRef, ServiceMap)
    • MachineStateSchema/MachineEventSchema now use Schema.Codec for encode/decode compat
    • 213 tests passing across 19 files

0.6.0

Minor Changes

  • 9d5bd6f Thanks @cevr! - Add actor.children to expose child actors spawned via self.spawn

    • actor.children returns ReadonlyMap<string, ActorRef> of children spawned via self.spawn
    • State-scoped children auto-removed from map on state exit
    • Works for both regular and persistent actors
  • 9d5bd6f Thanks @cevr! - Add observable ActorSystem with event stream and sync observation

    • system.subscribe(fn) — sync callback for ActorSpawned / ActorStopped events, returns unsubscribe
    • system.actors — sync snapshot of all registered actors (ReadonlyMap)
    • system.events — async Stream<SystemEvent> via PubSub (each subscriber gets own queue)
    • Works with both explicit (ActorSystemDefault) and implicit (Machine.spawn) systems
    • No events emitted during system teardown
    • Double-stop prevention: system.stop + scope finalizer won't emit duplicate ActorStopped

0.5.0

Minor Changes

  • 28835c6 Thanks @cevr! - Add observable ActorSystem and wire up actor.children for persistent actors

    ActorSystem observation:

    • system.subscribe(fn) — sync callback for ActorSpawned / ActorStopped events, returns unsubscribe
    • system.actors — sync snapshot of all registered actors (ReadonlyMap)
    • system.events — async Stream<SystemEvent> via PubSub (each subscriber gets own queue)
    • Works with both explicit (ActorSystemDefault) and implicit (Machine.spawn) systems
    • No events emitted during system teardown
    • Double-stop prevention: system.stop + scope finalizer won't emit duplicate ActorStopped

    actor.children:

    • Wire up childrenMap in persistent actors so actor.children reflects self.spawn children
    • Children auto-removed from map on scope close (state-scoped cleanup)

0.4.0

Minor Changes

  • bd3953e Thanks @cevr! - Add child actor support: self.spawn() from handlers, actor.system on every ActorRef, implicit system creation for Machine.spawn, and automatic lifecycle coupling to state scope

0.3.2

Patch Changes

  • a16c44e Thanks @cevr! - Add tsdown build step. Library now ships pre-built ESM with .d.ts declarations instead of raw TypeScript source.

0.3.1

Patch Changes

  • Add stopSync to ActorRef — fire-and-forget stop for sync contexts (framework cleanup hooks, event handlers).

0.3.0

Minor Changes

  • 0154ac9 Thanks @cevr! - feat: DX overhaul — multi-state .on(), State.derive(), .onAny(), waitFor deadlock fix, sendSync, waitFor(State.X), .build() / BuiltMachine

    • Multi-state .on()/.reenter(): Accept arrays of states — .on([State.A, State.B], Event.X, handler)
    • State.derive(): Construct new state from source — State.B.derive(stateA, { extra: val }) picks overlapping fields + applies overrides
    • .onAny() wildcard transitions: Handle event from any state — .onAny(Event.Cancel, () => State.Cancelled). Specific .on() takes priority.
    • waitFor deadlock fix: Rewrote to use sync listeners + Deferred instead of SubscriptionRef.changes stream, preventing semaphore deadlock on synchronous transitions
    • sendSync: Fire-and-forget sync send for framework integration (React/Solid hooks)
    • waitFor(State.X): Accept state constructor/value instead of predicate — actor.waitFor(State.Active) and actor.sendAndWait(event, State.Done)
    • .build() / BuiltMachine: Terminal builder method — .provide() renamed to .build(), returns BuiltMachine. .validate() removed. Machine.spawn and ActorSystem.spawn accept BuiltMachine. No-slot machines: .build() with no args.
  • 365da12 Thanks @cevr! - feat: remove Scope.Scope from Machine.spawn and system.spawn signatures

    • Scope-optional spawn: Machine.spawn and ActorSystem.spawn no longer require Scope.Scope in R. Both detect scope via Effect.serviceOption — if present, attach cleanup finalizer; if absent, skip.
    • Daemon forks: Event loop, background effects, and persistence fibers use Effect.forkDaemon — detached from parent scope, cleaned up by actor.stop.
    • System-level cleanup: ActorSystem layer teardown stops all registered actors automatically. ActorSystemDefault is now Layer.scoped.
    • Breaking: Callers that relied on Scope.Scope appearing in the R type of spawn may need type adjustments. Effect.scoped wrappers around spawn are no longer required but still work (scope detection finds them).

0.2.4

Patch Changes

  • d29f2ff Thanks @cevr! - feat: makeInspector accepts Schema constructors as type params

    makeInspector<typeof MyState, typeof MyEvent>(cb) now auto-extracts .Type from schema constructors via ResolveType. No need for typeof MyState.Type anymore.

0.2.3

Patch Changes

  • 85a2854 Thanks @cevr! - fix: use forkScoped for event loop fiber to prevent premature interruption

    Changed Effect.fork to Effect.forkScoped for the actor event loop fiber. Previously, the event loop was attached to the calling fiber's scope, meaning it would be interrupted when a transient caller completed. Now the event loop's lifetime is tied to the provided Scope.Scope service, allowing callers to control the actor's lifecycle explicitly.

0.2.2

Patch Changes

  • 2bcb0bb Thanks @cevr! - Add AnyInspectionEvent type alias and default generic params on makeInspector/consoleInspector/collectingInspector so untyped inspectors work without explicit casts.

  • c50c3ce Thanks @cevr! - Fix waitFor race condition where a fast state transition between get and changes subscription could cause waitFor to hang forever. Now uses stateRef.changes directly which emits the current value atomically as its first element.

0.2.1

Patch Changes

  • 639ee87 Thanks @cevr! - Fix PersistenceAdapterTag key for deterministic key diagnostics and align language-service config.

0.2.0

Minor Changes

  • f4a8c8f Thanks @cevr! - - send after stop is a no-op for actors and persistent actors

    • async persistence worker for journaling/metadata to keep event loop fast
    • snapshot schedule completion no longer stops event loop
    • serialize spawn/restore to avoid duplicate side-effects under concurrency
  • a77f9fa Thanks @cevr! - Add Machine.task, ActorRef wait/await helpers, and error inspection events.

  • 3172971 Thanks @cevr! - Add Effect.fn tracing across actor, persistence, cluster, and testing flows. Fix persistent actor lifecycle (spawn/background effects, snapshot scheduler), restore-from-events behavior, duplicate spawn handling, and slot preflight errors. Update persistence docs; PersistentActorRef now carries environment R and replayTo runs in R.