diff --git a/.changeset/bright-event-protocols.md b/.changeset/bright-event-protocols.md new file mode 100644 index 0000000..9cb72d5 --- /dev/null +++ b/.changeset/bright-event-protocols.md @@ -0,0 +1,19 @@ +--- +"@typeonce/effect-machine": minor +--- + +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. + +```ts +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. diff --git a/README.md b/README.md index 255fbbd..8bfa458 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,12 @@ const Event = Schema.TaggedUnion({ }) const States = Machine.defineStates(State.cases) +const CounterEvent = Machine.events(Event) const CounterDefinition = Machine.make({ id: "Counter", states: States.states, - events: [Event], + events: CounterEvent, initial: () => States.initial.Idle.from() }) @@ -61,8 +62,6 @@ const Counter = CounterDefinition.handle({ } }) -const CounterEvent = Machine.events(Counter) - const program = Effect.gen(function*() { const ref = yield* Machine.start(Counter) yield* ref.send(CounterEvent.Start()) @@ -80,9 +79,11 @@ Use this order to preserve inference and keep boundaries explicit: 1. Define domain, state, public-event, internal-event, and emitted-event schemas. 2. Declare topology with `Machine.defineStates`. -3. Create the protocol and initializer with `Machine.make`. -4. Implement every active state with `.handle(...)`. -5. Add runtime, Atom, testing, or cluster adapters at the application boundary. +3. Create public and internal event descriptors with `Machine.events` and + `Machine.internalEvents`. +4. Create the machine protocol and initializer with `Machine.make`. +5. Implement every active state with `.handle(...)`. +6. Add runtime, Atom, testing, or cluster adapters at the application boundary. ### Construct state through builders @@ -134,22 +135,24 @@ const Internal = Schema.TaggedUnion({ SaveFailed: { message: Schema.String } }) +export const CommandEvent = Machine.events(Command) +export type PublicCommandEvent = Machine.EventOf +const InternalEvent = Machine.internalEvents(Internal) + const definition = Machine.make({ states: States.states, - events: [Command], - internalEvents: [Internal], + events: CommandEvent, + internalEvents: InternalEvent, initial: () => States.initial.Idle.from() }) - -const CommandEvent = Machine.events(definition) -const InternalEvent = Machine.internalEvents(definition) ``` Handlers see both protocols. Typed `send` and `Machine.plan` accept only public events. Event tags must be unique and public/internal tags must be disjoint. -Use `Machine.events(machine)` and `Machine.internalEvents(machine)` as the -standard constructors for their respective protocols: +Export the descriptor returned by `Machine.events` instead of exporting its +schemas. This keeps the deferred constructors as the standard way to create +events without exposing schema `.make` methods: ```ts ref.send(CommandEvent.Save()) @@ -160,6 +163,9 @@ The returned constructors preserve each schema's make input, including required fields and constructor defaults. They defer schema construction until delivery, so invalid values fail planning or the running machine with `MachineSchemaDecodeError` instead of throwing at the call site. +Schemas with an open discriminator such as `_tag: Schema.String` remain valid +protocols but cannot expose a finite constructor set; pass a complete event +object to `send` or `Machine.plan` for those events. ### Choose the target by scope @@ -296,8 +302,8 @@ import { MachineTest } from "@typeonce/effect-machine/testing" const trace = yield* MachineTest.run(Counter, { events: [ - Machine.event(Counter, Event.cases.Start), - Machine.event(Counter, Event.cases.Increment) + { _tag: "Start" }, + { _tag: "Increment" } ] }) @@ -305,9 +311,9 @@ yield* MachineTest.verify(Counter, trace) ``` `MachineTest` scenarios retain decoded event values for model inspection, so -this is the main case for the eager `Machine.event` API. Pure planner tests do -not execute invokes or time. Use a started machine and a probe when those -semantics matter. +pass complete decoded objects when defining scenarios manually. Pure planner +tests do not execute invokes or time. Use a started machine and a probe when +those semantics matter. ## Entrypoints diff --git a/api-reference.config.json b/api-reference.config.json index f904e0a..9ccac3e 100644 --- a/api-reference.config.json +++ b/api-reference.config.json @@ -29,7 +29,8 @@ "decodeSnapshot", "defineStates", "encodeSnapshot", - "event", + "events", + "internalEvents", "invoke", "make", "plan", diff --git a/docs/agent-guide.md b/docs/agent-guide.md index 55a53ca..87c83bd 100644 --- a/docs/agent-guide.md +++ b/docs/agent-guide.md @@ -49,13 +49,15 @@ const InternalEvent = Schema.TaggedUnion({ }) const States = Machine.defineStates(State.cases) +const Events = Machine.events(Event) +const InternalEvents = Machine.internalEvents(InternalEvent) ``` -After `Machine.make`, derive public constructors with `Machine.events(machine)` -and internal constructors with `Machine.internalEvents(machine)`. Construct new -state values through the target or initial builder's `.from(...)` method. Both -event constructors and state `.from(...)` defer schema construction until -planning, so validation failures remain typed machine errors. Use +Pass these descriptors to `Machine.make` and export `Events` instead of the raw +event schema. Construct new state values through the target or initial +builder's `.from(...)` method. Both event constructors and state `.from(...)` +defer schema construction until planning, so validation failures remain typed +machine errors. Use `Schema.TaggedClass` when a case needs class methods or nominal class identity; the deferred constructors preserve that identity after decoding. @@ -223,7 +225,7 @@ const States = Machine.defineStates({ const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: () => States.initial.Done.from() }).handle({ Done: { @@ -505,15 +507,15 @@ an event for the parent. Both operations validate their schemas. union handled inside the statechart: ```ts +const Events = Machine.events(Event) +const InternalEvents = Machine.internalEvents(InternalEvent) + const definition = Machine.make({ states: States.states, - events: [Event], - internalEvents: [InternalEvent], + events: Events, + internalEvents: InternalEvents, initial: () => States.initial.Idle.from() }) - -const Events = Machine.events(definition) -const InternalEvents = Machine.internalEvents(definition) ``` Use the protocol-bound constructors at every machine delivery boundary: @@ -532,9 +534,13 @@ fields are intentionally unavailable until the owning machine processes it. Invalid constructor input fails `Machine.plan` or the running machine with `MachineSchemaDecodeError`; creating the instruction itself never performs -schema validation. `Machine.event(machine, schema, fields?)` remains available -as an eager low-level constructor for callers that explicitly want an already -decoded value and accept synchronous failure. +schema validation. APIs that explicitly retain decoded events, such as manual +model-testing scenarios or transport messages, can receive complete event +objects directly. + +An open discriminator such as `_tag: Schema.String` cannot produce named +constructors because its tag set is not finite. The schema still participates +in the protocol; pass a complete event object at the delivery boundary. Use the exported utility types when another API must preserve the boundary: @@ -951,11 +957,11 @@ initial: () => States.initial.Idle.from() ### Invoked child emits events not accepted by the parent -Add the child's emitted schemas to the parent machine's `internalEvents` array: +Create an internal descriptor from the child's emitted schemas: ```ts -events: [Submit], -internalEvents: [...ChildMachine.emits] +events: Machine.events(Submit), +internalEvents: Machine.internalEvents(...ChildMachine.emits) ``` ### An internal event is rejected by `send` diff --git a/examples/platformer/src/machine.test.ts b/examples/platformer/src/machine.test.ts index cb40cc0..88d3179 100644 --- a/examples/platformer/src/machine.test.ts +++ b/examples/platformer/src/machine.test.ts @@ -5,10 +5,10 @@ import { describe, expect, it } from "vitest" import { makeTextRenderer } from "../../../test/machine/visualization/text.ts" import { airJumpMode, + type CharacterEvent, CharacterEvents, CharacterMachine, type CharacterSnapshot, - Event, facingDirection, locomotionMode, wallContact @@ -103,15 +103,17 @@ const laws = [ // Exploration scenarios retain decoded events for trace inspection. const EventValue = { - Resume: () => Machine.event(CharacterMachine, Event.cases.Resume), - Pause: (fields: { readonly at: number }) => Machine.event(CharacterMachine, Event.cases.Pause, fields), - Reset: () => Machine.event(CharacterMachine, Event.cases.Reset), - JumpPressed: (fields: { readonly at: number; readonly y: number; readonly wall: -1 | 0 | 1 }) => - Machine.event(CharacterMachine, Event.cases.JumpPressed, fields), - Landed: (fields: { readonly impact: number; readonly axis: -1 | 0 | 1; readonly at: number }) => - Machine.event(CharacterMachine, Event.cases.Landed, fields), - ApexReached: (fields: { readonly y: number }) => Machine.event(CharacterMachine, Event.cases.ApexReached, fields), - DownPressed: (fields: { readonly at: number }) => Machine.event(CharacterMachine, Event.cases.DownPressed, fields) + Resume: (): CharacterEvent => ({ _tag: "Resume" }), + Pause: (fields: { readonly at: number }): CharacterEvent => ({ _tag: "Pause", ...fields }), + Reset: (): CharacterEvent => ({ _tag: "Reset" }), + JumpPressed: ( + fields: { readonly at: number; readonly y: number; readonly wall: -1 | 0 | 1 } + ) => ({ _tag: "JumpPressed", ...fields } as const), + Landed: ( + fields: { readonly impact: number; readonly axis: -1 | 0 | 1; readonly at: number } + ) => ({ _tag: "Landed", ...fields } as const), + ApexReached: (fields: { readonly y: number }) => ({ _tag: "ApexReached", ...fields } as const), + DownPressed: (fields: { readonly at: number }) => ({ _tag: "DownPressed", ...fields } as const) } const explorationEvents = ({ snapshot }: MachineTest.ExplorationStateContext) => { diff --git a/examples/platformer/src/machine.ts b/examples/platformer/src/machine.ts index 69aa962..f1926d3 100644 --- a/examples/platformer/src/machine.ts +++ b/examples/platformer/src/machine.ts @@ -18,7 +18,7 @@ const State = Schema.TaggedUnion({ }) // Inputs and physics facts are one runtime-decoded, statically typed protocol. -export const Event = Schema.TaggedUnion({ +const Event = Schema.TaggedUnion({ Move: { axis: Axis, at: Schema.Number }, JumpPressed: { at: Schema.Number, y: Schema.Number, wall: Axis }, WallContact: { wall: Axis }, @@ -39,6 +39,9 @@ const InternalEvent = Schema.TaggedUnion({ WallJump: { at: Schema.Number, push: Axis } }) +export const CharacterEvents = Machine.events(Event) +const InternalEvents = Machine.internalEvents(InternalEvent) + const awayFrom = (wall: Axis): Axis => (wall === -1 ? 1 : wall === 1 ? -1 : 0) export const CharacterStates = Machine.defineStates({ @@ -124,14 +127,11 @@ const initialCharacter = () => const definition = Machine.make({ id: "PlatformerCharacter", states: CharacterStates.states, - events: [Event], - internalEvents: [InternalEvent], + events: CharacterEvents, + internalEvents: InternalEvents, initial: initialCharacter }) -export const CharacterEvents = Machine.events(definition) -const InternalEvents = Machine.internalEvents(definition) - export const CharacterMachine = definition.handle({ Character: { on: { diff --git a/examples/playground/src/examples/examples.test.ts b/examples/playground/src/examples/examples.test.ts index 72971a3..09b2746 100644 --- a/examples/playground/src/examples/examples.test.ts +++ b/examples/playground/src/examples/examples.test.ts @@ -2,9 +2,9 @@ import { assert, describe, it } from "@effect/vitest" import { Machine } from "@typeonce/effect-machine" import { MachineTest } from "@typeonce/effect-machine/testing" import { Effect } from "effect" -import { MicrowaveEvent, MicrowaveMachine } from "./microwave/machine.ts" +import { MicrowaveMachine } from "./microwave/machine.ts" import { TrafficLightMachine } from "./traffic-light/machine.ts" -import { TurnstileEvent, TurnstileMachine } from "./turnstile/machine.ts" +import { TurnstileMachine } from "./turnstile/machine.ts" import { SharedMachine, SharedTransportEvents } from "./worker-tabs/machine.ts" describe("playground machines", () => { @@ -12,10 +12,10 @@ describe("playground machines", () => { Effect.gen(function*() { const trace = yield* MachineTest.run(TurnstileMachine, { events: [ - Machine.event(TurnstileMachine, TurnstileEvent.cases.GatePushed), - Machine.event(TurnstileMachine, TurnstileEvent.cases.CoinInserted), - Machine.event(TurnstileMachine, TurnstileEvent.cases.CoinInserted), - Machine.event(TurnstileMachine, TurnstileEvent.cases.GatePushed) + { _tag: "GatePushed" }, + { _tag: "CoinInserted" }, + { _tag: "CoinInserted" }, + { _tag: "GatePushed" } ] }) @@ -46,11 +46,11 @@ describe("playground machines", () => { Effect.gen(function*() { const trace = yield* MachineTest.run(MicrowaveMachine, { events: [ - Machine.event(MicrowaveMachine, MicrowaveEvent.cases.PowerPressed), - Machine.event(MicrowaveMachine, MicrowaveEvent.cases.DoorOpened), - Machine.event(MicrowaveMachine, MicrowaveEvent.cases.PowerPressed), - Machine.event(MicrowaveMachine, MicrowaveEvent.cases.DoorClosed), - Machine.event(MicrowaveMachine, MicrowaveEvent.cases.PowerPressed) + { _tag: "PowerPressed" }, + { _tag: "DoorOpened" }, + { _tag: "PowerPressed" }, + { _tag: "DoorClosed" }, + { _tag: "PowerPressed" } ] }) diff --git a/examples/playground/src/examples/media-player/definition.ts b/examples/playground/src/examples/media-player/definition.ts index e31abbe..7664083 100644 --- a/examples/playground/src/examples/media-player/definition.ts +++ b/examples/playground/src/examples/media-player/definition.ts @@ -1,5 +1,7 @@ import { Machine } from "@typeonce/effect-machine" -import { initialAudioSettings, MediaPlayerEvent, MediaPlayerInternalEvent, MediaPlayerStates } from "./schemas.ts" +import { initialAudioSettings, MediaPlayerEvents, MediaPlayerInternalEvents, MediaPlayerStates } from "./schemas.ts" + +export { MediaPlayerEvents, MediaPlayerInternalEvents } from "./schemas.ts" const initialPlayer = () => MediaPlayerStates.initial.Player.from((player) => @@ -16,10 +18,7 @@ const initialPlayer = () => export const MediaPlayerDefinition = Machine.make({ id: "MediaPlayer", states: MediaPlayerStates.states, - events: [MediaPlayerEvent], - internalEvents: [MediaPlayerInternalEvent], + events: MediaPlayerEvents, + internalEvents: MediaPlayerInternalEvents, initial: initialPlayer }) - -export const MediaPlayerEvents = Machine.events(MediaPlayerDefinition) -export const MediaPlayerInternalEvents = Machine.internalEvents(MediaPlayerDefinition) diff --git a/examples/playground/src/examples/media-player/machine.test.ts b/examples/playground/src/examples/media-player/machine.test.ts index 70e28b0..faab1fd 100644 --- a/examples/playground/src/examples/media-player/machine.test.ts +++ b/examples/playground/src/examples/media-player/machine.test.ts @@ -3,24 +3,21 @@ import { Machine } from "@typeonce/effect-machine" import { MachineTest } from "@typeonce/effect-machine/testing" import { Effect, Graph } from "effect" import { MediaPlayerMachine } from "./machine.ts" -import { MediaPlayerEvent } from "./schemas.ts" - -const everyPublicEvent = [ - Machine.event(MediaPlayerMachine, MediaPlayerEvent.cases.SourceSelected, { - url: "https://example.com/audio.mp3" - }), - Machine.event(MediaPlayerMachine, MediaPlayerEvent.cases.PlayRequested), - Machine.event(MediaPlayerMachine, MediaPlayerEvent.cases.PauseRequested), - Machine.event(MediaPlayerMachine, MediaPlayerEvent.cases.RestartRequested), - Machine.event(MediaPlayerMachine, MediaPlayerEvent.cases.MediaWaiting), - Machine.event(MediaPlayerMachine, MediaPlayerEvent.cases.MediaCanPlay), - Machine.event(MediaPlayerMachine, MediaPlayerEvent.cases.PlaybackEnded, { currentTime: 42 }), - Machine.event(MediaPlayerMachine, MediaPlayerEvent.cases.TimeUpdated, { currentTime: 21 }), - Machine.event(MediaPlayerMachine, MediaPlayerEvent.cases.MediaFailed, { message: "unsupported codec" }), - Machine.event(MediaPlayerMachine, MediaPlayerEvent.cases.VolumeChanged, { volume: 0.4 }), - Machine.event(MediaPlayerMachine, MediaPlayerEvent.cases.PlaybackRateChanged, { playbackRate: 1.5 }), - Machine.event(MediaPlayerMachine, MediaPlayerEvent.cases.MuteRequested), - Machine.event(MediaPlayerMachine, MediaPlayerEvent.cases.UnmuteRequested) + +const everyPublicEvent: ReadonlyArray> = [ + { _tag: "SourceSelected", url: "https://example.com/audio.mp3" }, + { _tag: "PlayRequested" }, + { _tag: "PauseRequested" }, + { _tag: "RestartRequested" }, + { _tag: "MediaWaiting" }, + { _tag: "MediaCanPlay" }, + { _tag: "PlaybackEnded", currentTime: 42 }, + { _tag: "TimeUpdated", currentTime: 21 }, + { _tag: "MediaFailed", message: "unsupported codec" }, + { _tag: "VolumeChanged", volume: 0.4 }, + { _tag: "PlaybackRateChanged", playbackRate: 1.5 }, + { _tag: "MuteRequested" }, + { _tag: "UnmuteRequested" } ] const generated = MachineTest.scenarios(MediaPlayerMachine, { @@ -152,10 +149,8 @@ describe("media-player statechart model", () => { configuration.includes("Player.settings.Muted") ) assert.deepStrictEqual(loadingAndMuted.trace.scenario.events, [ - Machine.event(MediaPlayerMachine, MediaPlayerEvent.cases.SourceSelected, { - url: "https://example.com/audio.mp3" - }), - Machine.event(MediaPlayerMachine, MediaPlayerEvent.cases.MuteRequested) + { _tag: "SourceSelected", url: "https://example.com/audio.mp3" }, + { _tag: "MuteRequested" } ]) yield* MachineTest.assertUnreachable( diff --git a/examples/playground/src/examples/media-player/schemas.ts b/examples/playground/src/examples/media-player/schemas.ts index f24409c..fb6fd6c 100644 --- a/examples/playground/src/examples/media-player/schemas.ts +++ b/examples/playground/src/examples/media-player/schemas.ts @@ -62,7 +62,7 @@ export const MediaPlayerState = Schema.TaggedUnion({ Muted: soundSettingsFields }) -export const MediaPlayerEvent = Schema.TaggedUnion({ +const MediaPlayerEvent = Schema.TaggedUnion({ SourceSelected: { url: Schema.String }, PlayRequested: {}, PauseRequested: {}, @@ -78,7 +78,7 @@ export const MediaPlayerEvent = Schema.TaggedUnion({ UnmuteRequested: {} }) -export const MediaPlayerInternalEvent = Schema.TaggedUnion({ +const MediaPlayerInternalEvent = Schema.TaggedUnion({ LoadSucceeded: {}, RestartSucceeded: {}, LoudnessMeasured: { @@ -89,6 +89,9 @@ export const MediaPlayerInternalEvent = Schema.TaggedUnion({ OperationFailed: { message: Schema.String } }) +export const MediaPlayerEvents = Machine.events(MediaPlayerEvent) +export const MediaPlayerInternalEvents = Machine.internalEvents(MediaPlayerInternalEvent) + export const MediaPlayerStates = Machine.defineStates({ Player: { type: "parallel", diff --git a/examples/playground/src/examples/microwave/machine.ts b/examples/playground/src/examples/microwave/machine.ts index ac48280..c93d01c 100644 --- a/examples/playground/src/examples/microwave/machine.ts +++ b/examples/playground/src/examples/microwave/machine.ts @@ -5,12 +5,14 @@ export const MicrowaveState = Schema.TaggedUnion({ Cooking: { elapsedSeconds: Schema.Number } }) -export const MicrowaveEvent = Schema.TaggedUnion({ +const MicrowaveEvent = Schema.TaggedUnion({ PowerPressed: {}, DoorOpened: {}, DoorClosed: {} }) +export const MicrowaveEvents = Machine.events(MicrowaveEvent) + export const MicrowaveStates = Machine.defineStates({ Oven: { type: "parallel", @@ -36,7 +38,7 @@ export const MicrowaveStates = Machine.defineStates({ const definition = Machine.make({ id: "Microwave", states: MicrowaveStates.states, - events: [MicrowaveEvent], + events: MicrowaveEvents, initial: () => MicrowaveStates.initial.Oven.from((oven) => oven @@ -45,7 +47,6 @@ const definition = Machine.make({ ) }) -export const MicrowaveEvents = Machine.events(definition) export const MicrowaveMachine = definition.handle({ Oven: { states: { diff --git a/examples/playground/src/examples/traffic-light/machine.ts b/examples/playground/src/examples/traffic-light/machine.ts index 5cbdb7e..6ac4adf 100644 --- a/examples/playground/src/examples/traffic-light/machine.ts +++ b/examples/playground/src/examples/traffic-light/machine.ts @@ -1,10 +1,12 @@ import { Machine } from "@typeonce/effect-machine" import { Schema } from "effect" -export const TrafficLightEvent = Schema.TaggedUnion({ +const TrafficLightEvent = Schema.TaggedUnion({ Reset: {} }) +export const TrafficLightEvents = Machine.events(TrafficLightEvent) + export const trafficLightDurations = { Red: 4_000, RedYellow: 1_000, @@ -22,11 +24,10 @@ export const TrafficLightStates = Machine.defineStates({ const definition = Machine.make({ id: "TrafficLight", states: TrafficLightStates.states, - events: [TrafficLightEvent], + events: TrafficLightEvents, initial: () => TrafficLightStates.initial.Red.from() }) -export const TrafficLightEvents = Machine.events(definition) export const TrafficLightMachine = definition.handle({ Red: { invoke: Machine.invoke({ diff --git a/examples/playground/src/examples/turnstile/machine.ts b/examples/playground/src/examples/turnstile/machine.ts index 3db169b..3cf23e9 100644 --- a/examples/playground/src/examples/turnstile/machine.ts +++ b/examples/playground/src/examples/turnstile/machine.ts @@ -1,11 +1,13 @@ import { Machine } from "@typeonce/effect-machine" import { Schema } from "effect" -export const TurnstileEvent = Schema.TaggedUnion({ +const TurnstileEvent = Schema.TaggedUnion({ CoinInserted: {}, GatePushed: {} }) +export const TurnstileEvents = Machine.events(TurnstileEvent) + export const TurnstileStates = Machine.defineStates({ Locked: {}, Unlocked: {} @@ -14,12 +16,10 @@ export const TurnstileStates = Machine.defineStates({ const definition = Machine.make({ id: "Turnstile", states: TurnstileStates.states, - events: [TurnstileEvent], + events: TurnstileEvents, initial: () => TurnstileStates.initial.Locked.from() }) -export const TurnstileEvents = Machine.events(definition) - export const TurnstileMachine = definition.handle({ Locked: { on: { diff --git a/examples/playground/src/examples/worker-tabs/machine.ts b/examples/playground/src/examples/worker-tabs/machine.ts index 3d66520..81f4ef2 100644 --- a/examples/playground/src/examples/worker-tabs/machine.ts +++ b/examples/playground/src/examples/worker-tabs/machine.ts @@ -1,37 +1,34 @@ import { Machine } from "@typeonce/effect-machine" import { Schema } from "effect" +import { type SharedEvent, SharedMachineEvents } from "./protocol.ts" + +export type { SharedEvent } from "./protocol.ts" export const SharedMachineState = Schema.TaggedUnion({ Idle: { count: Schema.Number }, Active: { count: Schema.Number } }) -export const SharedMachineEvent = Schema.TaggedUnion({ - Started: {}, - Incremented: {}, - Reset: {}, - Stopped: {}, - Synchronized: { active: Schema.Boolean, count: Schema.Number } -}) - export const SharedMachineStates = Machine.defineStates(SharedMachineState.cases) const definition = Machine.make({ id: "WorkerHostedMachine", states: SharedMachineStates.states, - events: [SharedMachineEvent], + events: SharedMachineEvents, initial: () => SharedMachineStates.initial.Idle.from({ count: 0 }) }) // Worker and BroadcastChannel messages need decoded, cloneable data rather // than the opaque instructions returned by Machine.events. export const SharedTransportEvents = { - Started: () => Machine.event(definition, SharedMachineEvent.cases.Started), - Incremented: () => Machine.event(definition, SharedMachineEvent.cases.Incremented), - Reset: () => Machine.event(definition, SharedMachineEvent.cases.Reset), - Stopped: () => Machine.event(definition, SharedMachineEvent.cases.Stopped), - Synchronized: (fields: { readonly active: boolean; readonly count: number }) => - Machine.event(definition, SharedMachineEvent.cases.Synchronized, fields) + Started: (): SharedEvent => ({ _tag: "Started" }), + Incremented: (): SharedEvent => ({ _tag: "Incremented" }), + Reset: (): SharedEvent => ({ _tag: "Reset" }), + Stopped: (): SharedEvent => ({ _tag: "Stopped" }), + Synchronized: (fields: { readonly active: boolean; readonly count: number }): SharedEvent => ({ + _tag: "Synchronized", + ...fields + }) } export const SharedMachine = definition.handle({ @@ -58,5 +55,4 @@ export const SharedMachine = definition.handle({ } }) -export type SharedEvent = typeof SharedMachineEvent.Type export type SharedSnapshot = Machine.Machine.Snapshot diff --git a/examples/playground/src/examples/worker-tabs/protocol.ts b/examples/playground/src/examples/worker-tabs/protocol.ts index 842ed10..c753887 100644 --- a/examples/playground/src/examples/worker-tabs/protocol.ts +++ b/examples/playground/src/examples/worker-tabs/protocol.ts @@ -1,5 +1,17 @@ +import { Machine } from "@typeonce/effect-machine" import { Schema } from "effect" -import { SharedMachineEvent, type SharedSnapshot } from "./machine.ts" +import type { SharedSnapshot } from "./machine.ts" + +const SharedMachineEvent = Schema.TaggedUnion({ + Started: {}, + Incremented: {}, + Reset: {}, + Stopped: {}, + Synchronized: { active: Schema.Boolean, count: Schema.Number } +}) + +export const SharedMachineEvents = Machine.events(SharedMachineEvent) +export type SharedEvent = Machine.EventOf export const WorkerRequestSchema = Schema.TaggedUnion({ Ping: { requestId: Schema.String }, diff --git a/examples/pokemon/src/machine.ts b/examples/pokemon/src/machine.ts index 6488bc4..cb4e2bf 100644 --- a/examples/pokemon/src/machine.ts +++ b/examples/pokemon/src/machine.ts @@ -17,7 +17,7 @@ export const ReplaceChild = Machine.child("replace", ReplaceMachine) const machine = Machine.make({ states: States.states, - events: [ReplaceInTeam], + events: Machine.events(ReplaceInTeam), initial: () => States.initial.Loading.from() }).handle({ Loading: { diff --git a/examples/pokemon/src/machines/replace.ts b/examples/pokemon/src/machines/replace.ts index fed23ad..61066e5 100644 --- a/examples/pokemon/src/machines/replace.ts +++ b/examples/pokemon/src/machines/replace.ts @@ -29,10 +29,10 @@ const replaceWithRandom = Effect.sleep("500 millis").pipe( export const ReplaceStates = Machine.defineStates({ Idle: {}, Replacing }) -export { ReplacePokemon } +export const ReplaceEvents = Machine.events(ReplacePokemon, Replaced) export const ReplaceMachine = Machine.make({ states: ReplaceStates.states, - events: [ReplacePokemon, Replaced], + events: ReplaceEvents, emits: [ReplaceInTeam], initial: () => ReplaceStates.initial.Idle.from() }).handle({ diff --git a/examples/pokemon/src/machines/selection.ts b/examples/pokemon/src/machines/selection.ts index 488f7d7..000e798 100644 --- a/examples/pokemon/src/machines/selection.ts +++ b/examples/pokemon/src/machines/selection.ts @@ -68,10 +68,10 @@ export const SelectionStates = Machine.defineStates({ } }) -export { SelectPokemon, UpdateSearchText } +export const SelectionEvents = Machine.events(SelectPokemon, UpdateSearchText, SearchResult, ReplacePokemon) export const SelectionMachine = Machine.make({ states: SelectionStates.states, - events: [SelectPokemon, UpdateSearchText, SearchResult, ReplacePokemon], + events: SelectionEvents, emits: [ReplaceInTeam], initial: () => SelectionStates.initial.form.from((form) => diff --git a/examples/pokemon/src/router.tsx b/examples/pokemon/src/router.tsx index 161b56b..cb3bde2 100644 --- a/examples/pokemon/src/router.tsx +++ b/examples/pokemon/src/router.tsx @@ -3,8 +3,8 @@ import { createRootRoute, createRoute, createRouter, Link, Outlet } from "@tanst import { Match, Option } from "effect" import { AsyncResult } from "effect/unstable/reactivity" import { machineAtom, replaceMachineAtom, selectionMachineAtom, States } from "./machine.js" -import { ReplacePokemon, ReplaceStates } from "./machines/replace.ts" -import { SelectionStates, SelectPokemon, UpdateSearchText } from "./machines/selection.ts" +import { ReplaceEvents, ReplaceStates } from "./machines/replace.ts" +import { SelectionEvents, SelectionStates } from "./machines/selection.ts" import type { Pokemon } from "./pokemon.ts" const rootRoute = createRootRoute({ @@ -72,7 +72,7 @@ function Selection() { send(new UpdateSearchText({ value: event.target.value }))} + onChange={(event) => send(SelectionEvents.UpdateSearchText({ value: event.target.value }))} /> {SelectionStates.matches(state.value, "form.search.Searching") &&

Searching…

} @@ -84,7 +84,7 @@ function Selection() {

{pokemon.name}

{pokemon.name} -
@@ -174,7 +174,7 @@ function PokemonGrid({ team }: { team: readonly (typeof Pokemon.Type)[] }) { type="button" onClick={() => sendSelection( - new SelectPokemon({ + SelectionEvents.SelectPokemon({ id: pokemon.id }) )} @@ -184,7 +184,7 @@ function PokemonGrid({ team }: { team: readonly (typeof Pokemon.Type)[] }) { diff --git a/perf/runtime/counter.mjs b/perf/runtime/counter.mjs index 7133e0f..9585619 100644 --- a/perf/runtime/counter.mjs +++ b/perf/runtime/counter.mjs @@ -49,7 +49,7 @@ const CounterStates = Machine.defineStates({ export const counterMachine = Machine.make({ id: "RuntimeBenchmarkCounter", states: CounterStates.states, - events: [CounterEvent.cases.Increment, CounterEvent.cases.Finish], + events: benchmarkApi.events(CounterEvent.cases.Increment, CounterEvent.cases.Finish), initial: () => CounterStates.initial.Count.from({ value: 0 }) }).handle({ Count: { @@ -70,7 +70,7 @@ const CounterChild = Machine.child("counter", counterMachine) const counterParentMachine = Machine.make({ id: "RuntimeBenchmarkCounterParent", states: ParentStates.states, - events: [], + events: benchmarkApi.events(), initial: () => ParentStates.initial.Active.from() }).handle({ Active: { @@ -81,7 +81,7 @@ const counterParentMachine = Machine.make({ const counterSnapshotParentMachine = Machine.make({ id: "RuntimeBenchmarkSnapshotCounterParent", states: ParentStates.states, - events: [], + events: benchmarkApi.events(), initial: () => ParentStates.initial.Active.from() }).handle({ Active: { @@ -124,7 +124,7 @@ const HierarchicalStates = Machine.defineStates({ const hierarchicalCounterMachine = Machine.make({ id: "RuntimeBenchmarkHierarchicalCounter", states: HierarchicalStates.states, - events: [HierarchicalEvent.cases.Increment, HierarchicalEvent.cases.Finish], + events: benchmarkApi.events(HierarchicalEvent.cases.Increment, HierarchicalEvent.cases.Finish), initial: () => HierarchicalStates.initial.Active.from( (active) => active.Count.from({ value: 0 }) @@ -173,11 +173,7 @@ const ParallelStates = Machine.defineStates({ const parallelCounterMachine = Machine.make({ id: "RuntimeBenchmarkParallelCounter", states: ParallelStates.states, - events: [ - HierarchicalEvent.cases.IncrementLeft, - HierarchicalEvent.cases.IncrementRight, - HierarchicalEvent.cases.Finish - ], + events: benchmarkApi.events(HierarchicalEvent.cases.IncrementLeft, HierarchicalEvent.cases.IncrementRight, HierarchicalEvent.cases.Finish), initial: () => ParallelStates.initial.Active.from( (active) => @@ -213,18 +209,13 @@ const parallelCounterMachine = Machine.make({ } }) -// The pull-request harness evaluates this fixture against both the base and -// candidate builds. Older base builds do not expose Machine.event yet. -const makeEvent = (machine, schema) => - typeof Machine.event === "function" ? Machine.event(machine, schema) : schema.make({}) - -export const incrementEvent = makeEvent(counterMachine, CounterEvent.cases.Increment) -const finishEvent = makeEvent(counterMachine, CounterEvent.cases.Finish) -const hierarchicalIncrementEvent = makeEvent(hierarchicalCounterMachine, HierarchicalEvent.cases.Increment) -const hierarchicalFinishEvent = makeEvent(hierarchicalCounterMachine, HierarchicalEvent.cases.Finish) -const parallelIncrementLeftEvent = makeEvent(parallelCounterMachine, HierarchicalEvent.cases.IncrementLeft) -const parallelIncrementRightEvent = makeEvent(parallelCounterMachine, HierarchicalEvent.cases.IncrementRight) -const parallelFinishEvent = makeEvent(parallelCounterMachine, HierarchicalEvent.cases.Finish) +export const incrementEvent = { _tag: "Increment" } +const finishEvent = { _tag: "Finish" } +const hierarchicalIncrementEvent = { _tag: "Increment" } +const hierarchicalFinishEvent = { _tag: "Finish" } +const parallelIncrementLeftEvent = { _tag: "IncrementLeft" } +const parallelIncrementRightEvent = { _tag: "IncrementRight" } +const parallelFinishEvent = { _tag: "Finish" } export const initialCounterSnapshot = Effect.runSync( Machine.planInitial(counterMachine).pipe(Effect.map((planned) => planned.state)) diff --git a/perf/runtime/effect-machine-compatibility.mjs b/perf/runtime/effect-machine-compatibility.mjs index 433a08a..e511ae0 100644 --- a/perf/runtime/effect-machine-compatibility.mjs +++ b/perf/runtime/effect-machine-compatibility.mjs @@ -6,6 +6,9 @@ * capability boundary instead of leaking version checks into benchmark cases. */ export const makeEffectMachineBenchmarkApi = (Machine) => ({ + events: typeof Machine.event === "function" + ? (...schemas) => schemas + : (...schemas) => Machine.events(...schemas), invokeChild: typeof Machine.invokeMachine === "function" ? ({ onSnapshot, onFailure, ...config }) => { if (onFailure !== undefined) { diff --git a/perf/types/adapter-readiness-control.ts b/perf/types/adapter-readiness-control.ts index 5c38c28..3602db8 100644 --- a/perf/types/adapter-readiness-control.ts +++ b/perf/types/adapter-readiness-control.ts @@ -28,7 +28,7 @@ export const snapshot = States.initial.Ready(Ready.make({})) export const machine = Machine.make({ id: "perf-readiness", states: States.states, - events: [], + events: Machine.events(), initial: () => snapshot }).handle({ Flow: { diff --git a/perf/types/composition-control.ts b/perf/types/composition-control.ts index 4e4593e..6734832 100644 --- a/perf/types/composition-control.ts +++ b/perf/types/composition-control.ts @@ -74,6 +74,6 @@ export const initialWorkspace = () => export const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: initialWorkspace }) diff --git a/perf/types/dynamic-invoke-control.ts b/perf/types/dynamic-invoke-control.ts index 9672c31..73f677f 100644 --- a/perf/types/dynamic-invoke-control.ts +++ b/perf/types/dynamic-invoke-control.ts @@ -13,6 +13,6 @@ export const loadUser = (userId: string) => Effect.fail(new LoadError()).pipe(Ef export const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: () => States.initial.Loading(Loading.make({ userId: "user-1" })) }) diff --git a/perf/types/exact-channels-control.ts b/perf/types/exact-channels-control.ts index 9584359..84906d6 100644 --- a/perf/types/exact-channels-control.ts +++ b/perf/types/exact-channels-control.ts @@ -19,8 +19,8 @@ export const States = Machine.defineStates({ export const machine = Machine.make({ states: States.states, - events: [Start], - internalEvents: [Loaded], + events: Machine.events(Start), + internalEvents: Machine.internalEvents(Loaded), emits: [Notice], input: Input, initial: (input) => States.initial.Idle(Idle.make({ value: input.seed })) diff --git a/perf/types/handle-depth-24-control.ts b/perf/types/handle-depth-24-control.ts index 20feba8..dc6ba6d 100644 --- a/perf/types/handle-depth-24-control.ts +++ b/perf/types/handle-depth-24-control.ts @@ -158,7 +158,7 @@ export const States = Machine.defineStates({ export const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: (): never => { throw new Error("type-performance fixture") } diff --git a/perf/types/handle-depth-wide-16-control.ts b/perf/types/handle-depth-wide-16-control.ts index 4f82985..63d03f1 100644 --- a/perf/types/handle-depth-wide-16-control.ts +++ b/perf/types/handle-depth-wide-16-control.ts @@ -141,7 +141,7 @@ export const States = Machine.defineStates({ export const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: (): never => { throw new Error("type-performance fixture") } diff --git a/perf/types/handle.ts b/perf/types/handle.ts index 374da16..4177fee 100644 --- a/perf/types/handle.ts +++ b/perf/types/handle.ts @@ -16,7 +16,7 @@ const States = Machine.defineStates(State.cases) const machine = Machine.make({ states: States.states, - events: [Event.cases.Start, Event.cases.Finish], + events: Machine.events(Event.cases.Start, Event.cases.Finish), initial: () => States.initial.Idle(State.cases.Idle.make({})) }).handle({ Idle: { diff --git a/perf/types/make.ts b/perf/types/make.ts index d07875c..9d2123f 100644 --- a/perf/types/make.ts +++ b/perf/types/make.ts @@ -16,7 +16,7 @@ const States = Machine.defineStates(State.cases) const machine = Machine.make({ states: States.states, - events: [Event.cases.Start, Event.cases.Finish], + events: Machine.events(Event.cases.Start, Event.cases.Finish), initial: () => States.initial.Idle(State.cases.Idle.make({})) }) diff --git a/perf/types/successive-handle-control.ts b/perf/types/successive-handle-control.ts index 18c7066..6ccd129 100644 --- a/perf/types/successive-handle-control.ts +++ b/perf/types/successive-handle-control.ts @@ -35,6 +35,6 @@ export const initialIdle = () => States.initial.Flow(Flow.make({}), (flow) => fl export const machine = Machine.make({ states: States.states, - events: [Start, Finish], + events: Machine.events(Start, Finish), initial: initialIdle }) diff --git a/scripts/fixtures/consumer/consumer.ts b/scripts/fixtures/consumer/consumer.ts index ec73140..7dd4614 100644 --- a/scripts/fixtures/consumer/consumer.ts +++ b/scripts/fixtures/consumer/consumer.ts @@ -19,12 +19,14 @@ const InternalEvent = Schema.TaggedUnion({ }) const States = Machine.defineStates(State.cases) +const PublicEvents = Machine.events(PublicEvent) +const InternalEvents = Machine.internalEvents(InternalEvent) const machine = Machine.make({ id: "Consumer", states: States.states, - events: [PublicEvent.cases.Start], - internalEvents: [InternalEvent.cases.Loaded], + events: PublicEvents, + internalEvents: InternalEvents, initial: () => States.initial.Idle(State.cases.Idle.make({})) }).handle({ Idle: { @@ -63,8 +65,10 @@ const generated = MachineTest.scenarios(machine, { minEvents: 1, maxEvents: 2 }) type InputEvent = Machine.Machine.InputEvent type HandledEvent = Machine.Machine.Event -const start: InputEvent = PublicEvent.cases.Start.make({}) -const loaded: HandledEvent = InternalEvent.cases.Loaded.make({ value: "ready" }) +const constructedStart = PublicEvents.Start() +const constructedLoaded = InternalEvents.Loaded({ value: "ready" }) +const start: InputEvent = { _tag: "Start" } +const loaded: HandledEvent = { _tag: "Loaded", value: "ready" } // @ts-expect-error Internal events cannot cross the public input boundary. const invalidInput: InputEvent = loaded @@ -78,6 +82,8 @@ void [ invoked, delayed, generated, + constructedStart, + constructedLoaded, start, loaded, invalidInput diff --git a/scripts/fixtures/consumer/deep-bound.ts b/scripts/fixtures/consumer/deep-bound.ts index a1c666e..1c62f19 100644 --- a/scripts/fixtures/consumer/deep-bound.ts +++ b/scripts/fixtures/consumer/deep-bound.ts @@ -45,7 +45,7 @@ const ChildStates = Machine.defineStates({ }) const childMachine = Machine.make({ states: ChildStates.states, - events: [], + events: Machine.events(), emits: [Internal.cases.ChildNotice], input: Schema.Struct({ value: Schema.String }), initial: ({ value }) => ChildStates.initial.Done(ChildState.cases.Done.make({ value })) @@ -84,8 +84,8 @@ const States = Machine.defineStates({ const machine = Machine.make({ states: States.states, - events: [Event.cases.Begin, Event.cases.Save], - internalEvents: [Internal.cases.Loaded, Internal.cases.ChildCompleted, ...childMachine.emits], + events: Machine.events(Event.cases.Begin, Event.cases.Save), + internalEvents: Machine.internalEvents(Internal.cases.Loaded, Internal.cases.ChildCompleted, ...childMachine.emits), emits: [Emitted.cases.Notice], input: Schema.Struct({ seed: Schema.String }), initial: ({ seed: _seed }) => States.initial.Idle(State.cases.Idle.make({})) @@ -221,7 +221,7 @@ const PackagedDeepStates = Machine.defineStates({ }) const packagedDeepMachine = Machine.make({ states: PackagedDeepStates.states, - events: [], + events: Machine.events(), initial: (): never => { throw new Error("type-only packaged consumer fixture") } diff --git a/scripts/runtime-performance-compatibility.test.mjs b/scripts/runtime-performance-compatibility.test.mjs index f705b2b..ab8e1a7 100644 --- a/scripts/runtime-performance-compatibility.test.mjs +++ b/scripts/runtime-performance-compatibility.test.mjs @@ -5,6 +5,7 @@ import { makeEffectMachineBenchmarkApi } from "../perf/runtime/effect-machine-co test("uses the current child invocation capability when available", () => { const calls = [] const Machine = { + events: (...schemas) => ({ api: "current-events", schemas }), invoke: (config) => { calls.push(config) return { api: "current", config } @@ -12,6 +13,11 @@ test("uses the current child invocation capability when available", () => { } const config = { child: "counter", onDone: () => undefined } + assert.deepEqual(makeEffectMachineBenchmarkApi(Machine).events("Increment", "Finish"), { + api: "current-events", + schemas: ["Increment", "Finish"] + }) + assert.deepEqual(makeEffectMachineBenchmarkApi(Machine).invokeChild(config), { api: "current", config @@ -24,12 +30,15 @@ test("adapts lifecycle names for the legacy child invocation capability", () => const onDone = () => undefined const onSnapshot = () => undefined const Machine = { + event: () => undefined, invokeMachine: (config) => { calls.push(config) return { api: "legacy", config } } } + assert.deepEqual(makeEffectMachineBenchmarkApi(Machine).events("Increment", "Finish"), ["Increment", "Finish"]) + assert.deepEqual( makeEffectMachineBenchmarkApi(Machine).invokeChild({ child: "counter", diff --git a/src/Machine.ts b/src/Machine.ts index 37e548d..e9e8025 100644 --- a/src/Machine.ts +++ b/src/Machine.ts @@ -50,6 +50,7 @@ export const TypeId: TypeId = "~effect/Machine" declare const MachineOutputStatesTypeId: unique symbol declare const MachineTypeId: unique symbol declare const EventConstructionTypeId: unique symbol +declare const EventProtocolTypeId: unique symbol const ChildMachineLogicTypeId: typeof internal.ChildMachineLogicTypeId = internal.ChildMachineLogicTypeId @@ -158,7 +159,7 @@ export interface Machine< * * @since 0.4.0 */ - readonly events: InputEvents + readonly events: Machine.EventProtocol<"public", InputEvents> /** * Events reserved for raised events, child emissions, and other machine-local @@ -166,7 +167,10 @@ export interface Machine< * * @since 0.4.0 */ - readonly internalEvents: ReadonlyArray + readonly internalEvents: Machine.EventProtocol< + "internal", + Machine.InternalEventSchemas + > /** * Events that the machine may emit to its parent or external adapter. @@ -676,8 +680,9 @@ type DuplicateEventTag< : Events extends readonly [ infer Head extends Machine.TaggedSchema, ...infer Tail extends ReadonlyArray - ] ? Machine.TagOf extends infer Tag extends PropertyKey ? Tag extends Seen ? Tag | DuplicateEventTag - : DuplicateEventTag + ] ? Machine.TagOf extends infer Tag extends PropertyKey ? + | Extract + | DuplicateEventTag : never : never @@ -2040,8 +2045,8 @@ export declare namespace Machine { /** @internal */ readonly [MachineTypeId]: TypeCarrier readonly states: StateSchemas - readonly events: ReadonlyArray - readonly internalEvents: ReadonlyArray + readonly events: EventProtocol.Any<"public"> + readonly internalEvents: EventProtocol.Any<"internal"> readonly emits: ReadonlyArray readonly input: Schema.Top | undefined readonly id: string | undefined @@ -2161,13 +2166,19 @@ export declare namespace Machine { */ export type InputEvents = M[typeof MachineTypeId]["inputEvents"] - /** Extracts the internal event schema tuple carried by a machine definition. */ - export type InternalEvents = Events extends readonly [ - ...InputEvents, + /** Extracts an internal event schema tuple from the complete and public protocols. */ + export type InternalEventSchemas< + Events extends ReadonlyArray, + InputEvents extends ReadonlyArray + > = Events extends readonly [ + ...InputEvents, ...infer Internal extends ReadonlyArray - ] ? Internal + ] ? readonly [...Internal] : readonly [] + /** Extracts the internal event schema tuple carried by a machine definition. */ + export type InternalEvents = InternalEventSchemas, InputEvents> + /** * Extracts the complete event protocol handled inside a machine. * @@ -2204,6 +2215,9 @@ export declare namespace Machine { /** Event inputs accepted for a schema tuple at machine delivery boundaries. */ export type EventInputOf> = EventInput> + /** Identifies whether an event protocol is accepted publicly or only inside a machine. */ + export type EventProtocolKind = "public" | "internal" + type EventConstructorInput = Omit type EventConstructor< @@ -2213,6 +2227,11 @@ export declare namespace Machine { (input?: EventConstructorInput) => EventConstruction> : (input: EventConstructorInput) => EventConstruction> + type FiniteEventTag = string extends Tag ? never + : number extends Tag ? never + : symbol extends Tag ? never + : Tag + type EventConstructorsForSchema = EventSchema extends { readonly cases: infer Cases extends Readonly> } ? { @@ -2222,7 +2241,7 @@ export declare namespace Machine { : EventSchema extends { readonly members: infer Members extends ReadonlyArray } ? Types.UnionToIntersection> : { - readonly [Tag in EventSchema["Type"]["_tag"]]: EventConstructor + readonly [Tag in FiniteEventTag]: EventConstructor } /** Protocol-bound constructors keyed by each configured event tag. */ @@ -2231,6 +2250,37 @@ export declare namespace Machine { Types.UnionToIntersection>[Tag] } + /** + * A schema-backed event protocol exposing only deferred event constructors. + * + * The schema tuple is retained opaquely for machine runtime validation and + * type inference. It is not exposed as a runtime property. + * + * @since 0.10.0 + */ + export type EventProtocol< + Kind extends EventProtocolKind, + Schemas extends ReadonlyArray + > = EventConstructors & { + readonly [EventProtocolTypeId]: { + readonly kind: Kind + readonly schemas: Schemas + } + } + + export namespace EventProtocol { + /** An erased event protocol descriptor retaining its kind and schema carrier. */ + export interface Any { + readonly [EventProtocolTypeId]: { + readonly kind: Kind + readonly schemas: ReadonlyArray + } + } + } + + /** @internal Extracts the schema tuple carried opaquely by an event protocol. */ + export type EventProtocolSchemas = Protocol[typeof EventProtocolTypeId]["schemas"] + /** * Extracts the event protocol emitted by a machine. * @@ -5872,7 +5922,7 @@ export const isFinal: < * * Machine.make({ * states: States.states, - * events: [], + * events: Machine.events(), * initial: () => States.initial.idle.from() * }) * ``` @@ -5893,9 +5943,11 @@ type MakeConfig< > = { readonly id?: string readonly states: States & DefineStateTreeInput> - readonly events: InputEvents & ValidateInputEventProtocol> + readonly events: + & Machine.EventProtocol<"public", InputEvents> + & ValidateInputEventProtocol> readonly internalEvents?: - & InternalEvents + & Machine.EventProtocol<"internal", InternalEvents> & ValidateInternalEventProtocol< NoInfer, NoInfer @@ -5968,10 +6020,10 @@ interface Make { * `defineStates` or is passed inline. Call `handle` on the returned definition * to implement state behavior with ordinary TypeScript control flow. * - * Schemas in `events` define the public input protocol. Schemas in - * `internalEvents` are added to the complete handler protocol for raised - * events, child emissions, and other machine-local deliveries. Their tags - * must be disjoint. + * `Machine.events` defines the public input protocol. `Machine.internalEvents` + * adds events used for raised events, child emissions, and other machine-local + * deliveries. Both descriptors expose deferred constructors while retaining + * their schemas opaquely for runtime validation. Their tags must be disjoint. * * **Example** (Typed counter machine) * @@ -5988,10 +6040,11 @@ interface Make { * }) {} * * const States = Machine.defineStates({ Count }) + * const Events = Machine.events(Increment) * * const counter = Machine.make({ * states: States.states, - * events: [Increment], + * events: Events, * initial: () => States.initial.Count(new Count({ value: 0 })) * }).handle({ * Count: { @@ -6009,60 +6062,19 @@ interface Make { */ export const make: Make = internal.make -type EventConstructorArgs = {} extends EventSchema["~type.make.in"] ? - [input?: EventSchema["~type.make.in"]] - : [input: EventSchema["~type.make.in"]] - /** - * Constructs an event from a schema owned by a machine's protocol. - * - * **Details** - * - * The schema constructor runs exactly once. The resulting decoded event is - * trusted by this machine and definitions derived from it with `handle`, so - * repeated delivery does not decode the same already-constructed value again. - * Events supplied through ordinary `send` and `plan` calls remain untrusted - * and continue through full runtime schema validation. - * Treat the returned event as immutable after construction. - * This eager low-level constructor throws `MachineSchemaDecodeError` when - * construction fails. Prefer {@link events} and {@link internalEvents} for - * ordinary delivery so construction remains inside the machine error channel. - * - * The schema must be one of the machine's configured public or internal event - * schemas, or a case schema belonging to a configured `Schema.TaggedUnion`. - * - * **Example** - * - * ```ts - * import { Schema } from "effect" - * import { Machine } from "@typeonce/effect-machine" - * - * class Idle extends Schema.TaggedClass("Idle")("Idle", {}) {} - * class Increment extends Schema.TaggedClass("Increment")("Increment", { - * by: Schema.Number - * }) {} + * Extracts the decoded event union carried by an event protocol descriptor. * - * const States = Machine.defineStates({ Idle }) - * const counter = Machine.make({ - * states: States.states, - * events: [Increment], - * initial: () => States.initial.Idle.from() - * }).handle({ Idle: { on: { Increment: () => States.initial.Idle.from() } } }) - * - * const increment = Machine.event(counter, Increment, { by: 1 }) - * ``` - * - * @category constructors - * @since 0.4.0 + * @category utility types + * @since 0.10.0 */ -export const event: ( - machine: M, - schema: EventSchema & ([EventSchema["Type"]] extends [Machine.Event] ? unknown : never), - ...args: EventConstructorArgs -) => EventSchema["Type"] = internal.event +export type EventOf = Machine.EventOf< + Machine.EventProtocolSchemas +> /** - * Returns deferred constructors for every public event in a machine protocol. + * Defines a public event protocol and returns deferred constructors for every + * statically finite configured event tag. * * Constructor inputs retain their schema-derived required fields, defaults, * and transformations. Construction is deferred until delivery so failures @@ -6072,31 +6084,42 @@ export const event: ( - machine: M -) => Machine.EventConstructors> = internal.events +export const events = >( + ...schemas: Schemas & ValidateInputEventProtocol> +): Machine.EventProtocol<"public", readonly [...Schemas]> => internal.events(...(schemas as Schemas)) /** - * Returns deferred constructors for every internal event in a machine - * protocol. + * Defines an internal event protocol and returns deferred constructors for + * every statically finite configured event tag. * * Use these constructors for raised events and other machine-local deliveries. * Construction failures are reported through the * owning machine's `MachineSchemaDecodeError` channel. * + * **Example** + * + * ```ts + * const InternalEvents = Machine.internalEvents(Loaded, Failed) + * const machine = Machine.make({ internalEvents: InternalEvents, ... }) + * + * // Inside a transition callback: + * return [nextState, [raise(InternalEvents.Loaded({ value }))]] + * ``` + * * @category constructors - * @since 0.9.0 + * @since 0.10.0 */ -export const internalEvents: ( - machine: M -) => Machine.EventConstructors> = internal.internalEvents +export const internalEvents = >( + ...schemas: Schemas & ValidateInternalEventProtocol> +): Machine.EventProtocol<"internal", readonly [...Schemas]> => internal.internalEvents(...(schemas as Schemas)) /** * Encodes a decoded machine snapshot into a normalized data representation. @@ -6131,7 +6154,7 @@ export const internalEvents: ( * const States = Machine.defineStates({ Idle }) * const machine = Machine.make({ * states: States.states, - * events: [], + * events: Machine.events(), * initial: () => States.initial.Idle.from() * }).handle({ Idle: {} }) * @@ -6211,7 +6234,7 @@ export const encodeSnapshot: < * const States = Machine.defineStates({ Idle }) * const machine = Machine.make({ * states: States.states, - * events: [], + * events: Machine.events(), * initial: () => States.initial.Idle.from() * }).handle({ Idle: {} }) * @@ -6641,7 +6664,7 @@ export const retag: States.initial.Idle.from() * }).handle({ Idle: {} }) * @@ -6876,7 +6899,7 @@ export const enabled: < * const States = Machine.defineStates({ Off, On }) * const machine = Machine.make({ * states: States.states, - * events: [Toggle], + * events: Machine.events(Toggle), * initial: () => States.initial.Off.from() * }).handle({ Off: { on: { Toggle: () => States.initial.On.from() } }, On: {} }) * @@ -7196,7 +7219,7 @@ export const watch: ( * const States = Machine.defineStates({ Idle }) * const machine = Machine.make({ * states: States.states, - * events: [], + * events: Machine.events(), * initial: () => States.initial.Idle.from() * }).handle({ Idle: {} }) * @@ -7303,7 +7326,7 @@ export const start: < * const States = Machine.defineStates({ Idle }) * const machine = Machine.make({ * states: States.states, - * events: [], + * events: Machine.events(), * initial: () => States.initial.Idle.from() * }).handle({ Idle: {} }) * diff --git a/src/internal/machine/cluster.ts b/src/internal/machine/cluster.ts index 584c002..00fa0bd 100644 --- a/src/internal/machine/cluster.ts +++ b/src/internal/machine/cluster.ts @@ -14,6 +14,7 @@ import { Rpc } from "effect/unstable/rpc" import type * as Machine from "../../Machine.js" import type { Checkpoint, ClusterMachine, LoadResult } from "../../unstable/cluster/ClusterMachine.js" import * as internalMachine from "./machine.js" +import * as Protocol from "./protocol.js" import type { EnsureExecutable } from "./readiness.js" type EntityAddress = EntityAddress.EntityAddress @@ -248,7 +249,7 @@ export const make = < OutputStates, InputEvents > - const eventSchema = Schema.Union(machine.events as MachineEvents) + const eventSchema = Schema.Union(Protocol.inputEventSchemas(machine) as MachineEvents) const rpc = Rpc.make("send", { payload: eventSchema, success: SendResult diff --git a/src/internal/machine/machine.ts b/src/internal/machine/machine.ts index 4b7b81b..3dba2c2 100644 --- a/src/internal/machine/machine.ts +++ b/src/internal/machine/machine.ts @@ -86,7 +86,8 @@ interface DefineStates { ): Machine.DefinedStates (states: InvalidDefinedStateTreeInput): never } -type ValidateInputEventProtocol> = InputEvents +type ValidateInputEventProtocol> = InputEvents extends + ReadonlyArray ? unknown : never type ValidateInternalEventProtocol< InputEvents extends ReadonlyArray, InternalEvents extends ReadonlyArray @@ -769,9 +770,11 @@ type MakeConfig< > = { readonly id?: string readonly states: States & DefineStateTreeInput> - readonly events: InputEvents & ValidateInputEventProtocol> + readonly events: + & Machine.EventProtocol<"public", InputEvents> + & ValidateInputEventProtocol> readonly internalEvents?: - & InternalEvents + & Machine.EventProtocol<"internal", InternalEvents> & ValidateInternalEventProtocol< NoInfer, NoInfer @@ -845,8 +848,8 @@ export const make: Make = (< config: { readonly id?: string readonly states: States - readonly events: InputEvents - readonly internalEvents?: InternalEvents + readonly events: Machine.EventProtocol<"public", InputEvents> + readonly internalEvents?: Machine.EventProtocol<"internal", InternalEvents> readonly emits?: Emits readonly input?: Input readonly initial: (...args: [...Machine.InputArgs]) => Machine.InitialResult @@ -856,7 +859,7 @@ export const make: Make = (< const self = Object.create(Proto) self.states = config.states self.events = config.events - self.internalEvents = config.internalEvents ?? [] + self.internalEvents = config.internalEvents ?? Protocol.makeEventProtocol("internal", [] as const) self.emits = config.emits ?? [] self.input = config.input self.id = config.id @@ -869,28 +872,15 @@ export const make: Make = (< return self }) as Make -type EventConstructorArgs = {} extends EventSchema["~type.make.in"] ? - [input?: EventSchema["~type.make.in"]] - : [input: EventSchema["~type.make.in"]] +export const events = >( + ...schemas: Schemas +): Machine.EventProtocol<"public", readonly [...Schemas]> => + Protocol.makeEventProtocol<"public", readonly [...Schemas]>("public", schemas) -export const event = < - const M extends Machine.Any, - const EventSchema extends Machine.TaggedSchema ->( - machine: M, - schema: EventSchema & ([EventSchema["Type"]] extends [Machine.Event] ? unknown : never), - ...args: EventConstructorArgs -): EventSchema["Type"] => Protocol.makeEvent(machine, schema, args.length === 0 ? {} : args[0]) - -export const events = ( - machine: M -): Machine.EventConstructors> => - Protocol.eventConstructors(machine.events) as Machine.EventConstructors> - -export const internalEvents = ( - machine: M -): Machine.EventConstructors> => - Protocol.eventConstructors(machine.internalEvents) as Machine.EventConstructors> +export const internalEvents = >( + ...schemas: Schemas +): Machine.EventProtocol<"internal", readonly [...Schemas]> => + Protocol.makeEventProtocol<"internal", readonly [...Schemas]>("internal", schemas) export const encodeSnapshot: < const States extends Machine.StateSchemas, diff --git a/src/internal/machine/protocol.ts b/src/internal/machine/protocol.ts index 14a4259..e485ada 100644 --- a/src/internal/machine/protocol.ts +++ b/src/internal/machine/protocol.ts @@ -27,15 +27,28 @@ interface MachineProtocolSchemas { readonly trustedEvents: WeakSet } +interface EventProtocolDefinition { + readonly kind: Machine.EventProtocolKind + readonly schemas: ReadonlyArray +} + +const eventProtocolDefinitions = new WeakMap() + export const EventConstructionTypeId: unique symbol = Symbol("effect/Machine/EventConstruction") interface EventConstruction { readonly [EventConstructionTypeId]: typeof EventConstructionTypeId readonly _tag: PropertyKey +} + +interface EventConstructionDefinition { readonly schema: Machine.TaggedSchema readonly input: unknown + readonly inputError?: unknown } +const eventConstructionDefinitions = new WeakMap() + type BoundaryDecoder = (value: unknown) => Effect.Effect type BoundaryResultDecoder = (value: unknown) => Result.Result @@ -105,8 +118,40 @@ const collectEventConstructors = ( return constructors } +const getEventProtocolDefinition = ( + protocol: Machine.EventProtocol.Any, + expectedKind?: Machine.EventProtocolKind +): EventProtocolDefinition => { + const definition = eventProtocolDefinitions.get(protocol as object) + if (definition === undefined) { + throw new Error("Machine expected an event protocol created with Machine.events or Machine.internalEvents") + } + if (expectedKind !== undefined && definition.kind !== expectedKind) { + throw new Error(`Machine expected a ${expectedKind} event protocol`) + } + return definition +} + +export const eventProtocolSchemas = >( + protocol: Machine.EventProtocol +): Schemas => getEventProtocolDefinition(protocol).schemas as Schemas + +export const inputEventSchemas = (machine: Machine.Any): ReadonlyArray => + getEventProtocolDefinition(machine.events, "public").schemas + +export const internalEventSchemas = (machine: Machine.Any): ReadonlyArray => + getEventProtocolDefinition(machine.internalEvents, "internal").schemas + export const setProtocol = (machine: Machine.Any): void => { - const events = [...machine.events, ...machine.internalEvents] + const inputEvents = inputEventSchemas(machine) + const localEvents = internalEventSchemas(machine) + const publicTags = new Set(Reflect.ownKeys(machine.events)) + for (const tag of Reflect.ownKeys(machine.internalEvents)) { + if (publicTags.has(tag)) { + throw new Error(`Public and internal machine event tags must be disjoint: ${String(tag)}`) + } + } + const events = [...inputEvents, ...localEvents] setProtocolSchemas(machine, { event: Schema.Union(events), emit: Schema.Union(machine.emits), @@ -125,16 +170,33 @@ const makeEventConstruction = ( schema: Machine.TaggedSchema, tag: PropertyKey, input: unknown -): EventConstruction => ({ - [EventConstructionTypeId]: EventConstructionTypeId, - _tag: tag, - schema, - input -}) +): EventConstruction => { + let ownedInput = input + let inputError: unknown + if (typeof input === "object" && input !== null) { + try { + ownedInput = { ...input } + } catch (cause) { + ownedInput = undefined + inputError = cause + } + } + const construction: EventConstruction = { + [EventConstructionTypeId]: EventConstructionTypeId, + _tag: tag + } + eventConstructionDefinitions.set(construction, { + schema, + input: ownedInput, + ...(inputError === undefined ? {} : { inputError }) + }) + return Object.freeze(construction) +} -const isEventConstruction = (value: unknown): value is EventConstruction => hasProperty(value, EventConstructionTypeId) +const isEventConstruction = (value: unknown): value is EventConstruction => + typeof value === "object" && value !== null && eventConstructionDefinitions.has(value) -export const eventConstructors = ( +const eventConstructors = ( schemas: ReadonlyArray ): Readonly) => EventConstruction>> => { const leaves: Array = [] @@ -182,20 +244,15 @@ export const eventConstructors = ( for (const schema of leaves) { const discriminants = schemaTags(schema) if (discriminants.length === 0) { - throw new Error("Machine event constructors require finite literal or unique symbol event tags") + continue } for (const tag of discriminants) { if (hasProperty(constructors, tag)) { throw new Error(`Duplicate machine event constructor tag: ${String(tag)}`) } Object.defineProperty(constructors, tag, { - value: (...args: ReadonlyArray) => { - const fields = args.length === 0 ? {} : args[0] - const input = typeof fields === "object" && fields !== null - ? { ...fields, _tag: tag } - : { _tag: tag } - return makeEventConstruction(schema, tag, input) - }, + value: (...args: ReadonlyArray) => + makeEventConstruction(schema, tag, args.length === 0 ? {} : args[0]), enumerable: true }) } @@ -203,6 +260,19 @@ export const eventConstructors = ( return constructors } +export const makeEventProtocol = < + Kind extends Machine.EventProtocolKind, + Schemas extends ReadonlyArray +>( + kind: Kind, + schemas: Schemas +): Machine.EventProtocol => { + const ownedSchemas = Object.freeze(Array.from(schemas)) as unknown as Schemas + const protocol = eventConstructors(ownedSchemas) as Machine.EventProtocol + eventProtocolDefinitions.set(protocol as object, { kind, schemas: ownedSchemas }) + return Object.freeze(protocol) as Machine.EventProtocol +} + export const decodeBoundary = ( machine: Machine.Any, schema: Schema.Top, @@ -264,29 +334,6 @@ const makeBoundarySync = ( } } -/** Constructs an event through one of the machine protocol's own schemas and - * records the decoded value as trusted by that protocol. Machine clones share - * the protocol record, while unrelated machines retain independent trust. */ -export const makeEvent = ( - machine: Machine.Any, - schema: Schema, - input: unknown -): Schema["Type"] => { - const protocol = getProtocolSchemas(machine) - if (!protocol.eventConstructors.has(schema as object)) { - throw new Error("Machine.event expected a schema from the machine event protocol") - } - const inputName = getEventName(input) - const event = makeBoundarySync( - machine, - schema, - input, - inputName === undefined ? { boundary: "event" } : { boundary: "event", event: inputName } - ) - protocol.trustedEvents.add(event as object) - return event -} - const eventConstructionProtocolError = ( machine: Machine.Any, construction: EventConstruction @@ -298,22 +345,52 @@ const eventConstructionProtocolError = ( cause: Cause.die(new Error("Constructed event schema does not belong to the machine event protocol")) }) +const eventConstructionInput = ( + construction: EventConstruction, + definition: EventConstructionDefinition +): unknown => { + if (definition.inputError !== undefined) throw definition.inputError + return typeof definition.input === "object" && definition.input !== null + ? { ...definition.input, _tag: construction._tag } + : { _tag: construction._tag } +} + +const eventConstructionInputError = ( + machine: Machine.Any, + construction: EventConstruction, + cause: unknown +): MachineSchemaDecodeError => + new MachineSchemaDecodeError({ + machineId: machine.id, + boundary: "event", + event: String(construction._tag), + cause: Cause.die(cause) + }) + const decodeEventConstruction = ( machine: Machine.Any, protocol: MachineProtocolSchemas, construction: EventConstruction ): Effect.Effect => { - if (!protocol.eventConstructors.has(construction.schema as object)) { + const definition = eventConstructionDefinitions.get(construction) + if (definition === undefined || !protocol.eventConstructors.has(definition.schema as object)) { return Effect.fail(eventConstructionProtocolError(machine, construction)) } - return construction.schema.makeEffect(construction.input as never).pipe( - Effect.mapError((cause) => - new MachineSchemaDecodeError({ - machineId: machine.id, - boundary: "event", - event: String(construction._tag), - cause: new Schema.SchemaError(cause) - }) + return Effect.try({ + try: () => eventConstructionInput(construction, definition), + catch: (cause) => eventConstructionInputError(machine, construction, cause) + }).pipe( + Effect.flatMap((input) => + definition.schema.makeEffect(input as never).pipe( + Effect.mapError((cause) => + new MachineSchemaDecodeError({ + machineId: machine.id, + boundary: "event", + event: String(construction._tag), + cause: new Schema.SchemaError(cause) + }) + ) + ) ), Effect.tap((event) => Effect.sync(() => protocol.trustedEvents.add(event as object))) ) @@ -324,10 +401,17 @@ const decodeEventConstructionSync = ( protocol: MachineProtocolSchemas, construction: EventConstruction ): unknown => { - if (!protocol.eventConstructors.has(construction.schema as object)) { + const definition = eventConstructionDefinitions.get(construction) + if (definition === undefined || !protocol.eventConstructors.has(definition.schema as object)) { throw eventConstructionProtocolError(machine, construction) } - const event = makeBoundarySync(machine, construction.schema, construction.input, { + let input: unknown + try { + input = eventConstructionInput(construction, definition) + } catch (cause) { + throw eventConstructionInputError(machine, construction, cause) + } + const event = makeBoundarySync(machine, definition.schema, input, { boundary: "event", event: String(construction._tag) }) diff --git a/src/internal/testing/machine/finiteModel.ts b/src/internal/testing/machine/finiteModel.ts index fd2655a..a49d322 100644 --- a/src/internal/testing/machine/finiteModel.ts +++ b/src/internal/testing/machine/finiteModel.ts @@ -1487,7 +1487,7 @@ export const compileModel = (model: FiniteModel): Machine.Machine.Any => { const initial = byPath.get(model.initial)! const machine = Machine.make({ states: defined.states as any, - events: eventSchemas as any, + events: Machine.events(...eventSchemas) as any, initial: () => selectSnapshot(defined.initial as any, initial.path, byPath, [initial.path], 0) as any }) const transitions = new Map(model.transitions.map((transition) => [ diff --git a/src/internal/testing/machine/runtime.ts b/src/internal/testing/machine/runtime.ts index dc3c157..46b3cda 100644 --- a/src/internal/testing/machine/runtime.ts +++ b/src/internal/testing/machine/runtime.ts @@ -15,6 +15,7 @@ import * as Stream from "effect/Stream" import { FastCheck, TestClock } from "effect/testing" import * as Machine from "../../../Machine.js" import type { CausalRuntimeEvidence, Probe, ProbeStep, RuntimeInvariant } from "../../../testing/MachineTest.js" +import * as Protocol from "../../machine/protocol.js" import { type SchemaArbitraryReport, toArbitraryWithReport } from "./arbitrary.js" import { assertRuntimeInvariants, type RuntimeInvariantError } from "./runtimeInvariant.js" @@ -1523,7 +1524,7 @@ export const runtimeCommands = ( const reports: Array = [] const eventArbitraries = options.eventArbitrary === undefined - ? machine.events.map((schema) => { + ? Protocol.inputEventSchemas(machine).map((schema) => { const derived = toArbitraryWithReport(schema) reports.push(derived.report) return derived.value as FastCheck.Arbitrary> diff --git a/src/internal/testing/machine/verification.ts b/src/internal/testing/machine/verification.ts index 5bc62e9..5e3d2cb 100644 --- a/src/internal/testing/machine/verification.ts +++ b/src/internal/testing/machine/verification.ts @@ -35,6 +35,7 @@ import type { VerificationViolation, VerifyOptions } from "../../../testing/MachineTest.js" +import * as Protocol from "../../machine/protocol.js" import { toArbitraryWithReport } from "./arbitrary.js" import type { FiniteModel } from "./finiteModel.js" import * as ReferenceModel from "./referenceModel.js" @@ -167,6 +168,7 @@ export const scenarios = ( machine: M, options: ScenarioOptions = {} as ScenarioOptions ): Scenarios => { + const eventSchemas = Protocol.inputEventSchemas(machine) const minEvents = options.minEvents ?? 0 const maxEvents = options.maxEvents ?? 50 if (options.eventsArbitrary === undefined) { @@ -175,7 +177,7 @@ export const scenarios = ( if (minEvents > maxEvents) { throw new Error("MachineTest.scenarios expected minEvents to be less than or equal to maxEvents") } - if (machine.events.length === 0 && minEvents > 0) { + if (eventSchemas.length === 0 && minEvents > 0) { throw new Error( "MachineTest.scenarios cannot generate a non-empty event sequence for a machine without public events" ) @@ -184,7 +186,7 @@ export const scenarios = ( const diagnostics: Array = [] const eventArbitraries = options.eventsArbitrary === undefined - ? machine.events.map((schema, index) => { + ? eventSchemas.map((schema, index) => { const derived = toArbitraryWithReport(schema) diagnostics.push({ boundary: "event", @@ -524,7 +526,7 @@ const publicEventTags = (machine: M): { } => { const tags: Array[number]>> = [] const diagnostics: Array<{ readonly schemaIndex: number; readonly message: string }> = [] - machine.events.forEach((schema, schemaIndex) => { + Protocol.inputEventSchemas(machine).forEach((schema, schemaIndex) => { const values = finiteTagValues(SchemaAST.toType(schema.ast)) if (values === undefined) { diagnostics.push({ diff --git a/src/testing/MachineTest.ts b/src/testing/MachineTest.ts index 501cc35..1b6c3c2 100644 --- a/src/testing/MachineTest.ts +++ b/src/testing/MachineTest.ts @@ -230,7 +230,7 @@ export interface Scenarios { * const States = Machine.defineStates({ Idle }) * const machine = Machine.make({ * states: States.states, - * events: [Reset], + * events: Machine.events(Reset), * initial: () => States.initial.Idle.from() * }).handle({ Idle: { on: { Reset: () => States.initial.Idle.from() } } }) * @@ -479,7 +479,7 @@ export { ProbeUnavailableError } from "../internal/testing/machine/verification. * const States = Machine.defineStates({ Idle }) * const machine = Machine.make({ * states: States.states, - * events: [], + * events: Machine.events(), * initial: () => States.initial.Idle.from() * }).handle({ Idle: {} }) * @@ -1089,7 +1089,7 @@ export const Invariant: { * const States = Machine.defineStates({ Count }) * const machine = Machine.make({ * states: States.states, - * events: [], + * events: Machine.events(), * initial: () => States.initial.Count(new Count({ value: 0 })) * }).handle({ Count: {} }) * @@ -1407,7 +1407,7 @@ export type ExploreOptions States.initial.Count(new Count({ value: 0 })) * }).handle({ * Count: { on: { Increment: ({ state }) => @@ -1591,7 +1591,7 @@ export type RunServices = IsAny< * const States = Machine.defineStates({ Idle }) * const machine = Machine.make({ * states: States.states, - * events: [], + * events: Machine.events(), * initial: () => States.initial.Idle.from() * }).handle({ Idle: {} }) * @@ -1818,7 +1818,7 @@ export interface Coverage { * const States = Machine.defineStates({ Idle }) * const machine = Machine.make({ * states: States.states, - * events: [], + * events: Machine.events(), * initial: () => States.initial.Idle.from() * }).handle({ Idle: {} }) * @@ -2034,7 +2034,7 @@ export interface VerifyOptions { * const States = Machine.defineStates({ Idle }) * const machine = Machine.make({ * states: States.states, - * events: [], + * events: Machine.events(), * initial: () => States.initial.Idle.from() * }).handle({ Idle: {} }) * diff --git a/src/unstable/cluster/ClusterMachine.ts b/src/unstable/cluster/ClusterMachine.ts index dfa2f73..acb79bc 100644 --- a/src/unstable/cluster/ClusterMachine.ts +++ b/src/unstable/cluster/ClusterMachine.ts @@ -315,7 +315,7 @@ export const layerMemory: Layer.Layer = internal.layerMemory * const States = Machine.defineStates({ Idle }) * const machine = Machine.make({ * states: States.states, - * events: [], + * events: Machine.events(), * initial: () => States.initial.Idle.from() * }).handle({ Idle: {} }) * diff --git a/src/unstable/reactivity/AtomMachine.ts b/src/unstable/reactivity/AtomMachine.ts index a7bf348..c07e298 100644 --- a/src/unstable/reactivity/AtomMachine.ts +++ b/src/unstable/reactivity/AtomMachine.ts @@ -328,7 +328,7 @@ type ChildState = RefState States.initial.Count(new Count({ value: 0 })) * }).handle({ Count: {} }) * const machineAtom = AtomMachine.make(machine) @@ -439,7 +439,7 @@ export const selectSnapshotChild: < * const States = Machine.defineStates({ Idle }) * const machine = Machine.make({ * states: States.states, - * events: [], + * events: Machine.events(), * initial: () => States.initial.Idle.from() * }).handle({ Idle: {} }) * const machineAtom = AtomMachine.make(machine) @@ -607,7 +607,7 @@ export interface Bound { * const States = Machine.defineStates({ Idle }) * const machine = Machine.make({ * states: States.states, - * events: [], + * events: Machine.events(), * initial: () => States.initial.Idle.from() * }).handle({ Idle: {} }) * diff --git a/test/internal/machine/activities.test.ts b/test/internal/machine/activities.test.ts index c735f8e..94c6eb4 100644 --- a/test/internal/machine/activities.test.ts +++ b/test/internal/machine/activities.test.ts @@ -16,7 +16,7 @@ const childStates = Machine.defineStates({ ChildIdle }) const childMachine = Machine.make({ id: "document-worker", states: childStates.states, - events: [], + events: Machine.events(), initial: () => childStates.initial.ChildIdle(new ChildIdle({})) }) const child = Machine.child("child", childMachine) @@ -27,7 +27,7 @@ const activityStates = Machine.defineStates({ Loading, Dynamic }) const activityMachine = Machine.make({ id: "activity-inspection", states: activityStates.states, - events: [WorkSucceeded, WorkFailed, LoadTimedOut], + events: Machine.events(WorkSucceeded, WorkFailed, LoadTimedOut), initial: () => activityStates.initial.Loading(new Loading({})) }).handle({ Loading: { @@ -155,7 +155,7 @@ describe("machine activity metadata", () => { const id = `generated-timer-${idSuffix}` const generated = Machine.make({ states: activityStates.states, - events: [LoadTimedOut], + events: Machine.events(LoadTimedOut), initial: () => activityStates.initial.Loading(new Loading({})) }).handle({ Loading: { diff --git a/test/internal/machine/protocol.test.ts b/test/internal/machine/protocol.test.ts index 11a193a..3d1f08b 100644 --- a/test/internal/machine/protocol.test.ts +++ b/test/internal/machine/protocol.test.ts @@ -9,13 +9,37 @@ const PublicEvent = Schema.TaggedStruct("PublicEvent", { value: Schema.String }) const InternalEvent = Schema.TaggedStruct("InternalEvent", { value: Schema.String }) describe("machine protocols", () => { + it("rejects forged, misclassified, and overlapping event descriptors", () => { + const states = Machine.defineStates({ ProtocolIdle }) + const initial = () => states.initial.ProtocolIdle(new ProtocolIdle({})) + + assert.throws( + () => Machine.make({ states: states.states, events: [PublicEvent] as any, initial }), + /expected an event protocol/ + ) + assert.throws( + () => Machine.make({ states: states.states, events: Machine.internalEvents(PublicEvent) as any, initial }), + /expected a public event protocol/ + ) + assert.throws( + () => + Machine.make({ + states: states.states, + events: Machine.events(PublicEvent), + internalEvents: Machine.internalEvents(PublicEvent) as any, + initial + }), + /must be disjoint/ + ) + }) + it.effect("keeps the complete event protocol private across handler clones", () => Effect.gen(function*() { const states = Machine.defineStates({ ProtocolIdle }) const machine = Machine.make({ states: states.states, - events: [PublicEvent], - internalEvents: [InternalEvent], + events: Machine.events(PublicEvent), + internalEvents: Machine.internalEvents(InternalEvent), initial: () => states.initial.ProtocolIdle(new ProtocolIdle({})) }).handle({}) diff --git a/test/internal/machine/strategyDifferential.test.ts b/test/internal/machine/strategyDifferential.test.ts index 2cd81d2..2545353 100644 --- a/test/internal/machine/strategyDifferential.test.ts +++ b/test/internal/machine/strategyDifferential.test.ts @@ -27,7 +27,7 @@ const makeFlatMachine = () => { }) return Machine.make({ states: states.states, - events: [Noop, Increment, Reenter, Finish], + events: Machine.events(Noop, Increment, Reenter, Finish), initial: () => states.initial.Count(new Count({ value: 0 })) }).handle({ Count: { @@ -88,7 +88,7 @@ describe("machine planner and runtime strategies", () => { }) const machine = Machine.make({ states: states.states, - events: [Advance], + events: Machine.events(Advance), initial: () => states.initial.Root( new Root({}), @@ -178,7 +178,7 @@ describe("machine planner and runtime strategies", () => { const states = Machine.defineStates({ Idle, Ready }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Idle(new Idle({})) }).handle({ Idle: { always: ({ target }) => target.full.Ready(new Ready({})) }, @@ -199,7 +199,7 @@ describe("machine planner and runtime strategies", () => { const states = Machine.defineStates({ Idle: {} }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Idle.from() }).handle({ Idle: {} }) @@ -231,7 +231,7 @@ describe("machine planner and runtime strategies", () => { }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), input: Input, initial: (input) => states.initial.Complete(new Complete({ value: input.value })) }).handle({ @@ -283,10 +283,10 @@ describe("machine planner and runtime strategies", () => { const states = Machine.defineStates({ Count }) const definition = Machine.make({ states: states.states, - events: [Event], + events: Machine.events(Event), initial: () => states.initial.Count(new Count({ value: 0 })) }) - const events = Machine.events(definition) + const events = definition.events const machine = definition.handle({ Count: { on: { @@ -397,7 +397,7 @@ describe("machine planner and runtime strategies", () => { }) const machine = Machine.make({ states: states.states, - events: [Load, Loaded], + events: Machine.events(Load, Loaded), initial: () => states.initial.Idle(new Idle({})) }).handle({ Idle: { @@ -442,7 +442,7 @@ describe("machine planner and runtime strategies", () => { }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Loading(new Loading({})) }).handle({ Loading: { @@ -483,7 +483,7 @@ describe("machine planner and runtime strategies", () => { const states = Machine.defineStates({ Loading, Failed }) const machine = Machine.make({ states: states.states, - events: [Reenter, Stale], + events: Machine.events(Reenter, Stale), initial: () => states.initial.Loading(new Loading({ epoch: 0 })) }).handle({ Loading: { diff --git a/test/machine/ActivityLifecycleModel.test.ts b/test/machine/ActivityLifecycleModel.test.ts index 7f2639f..63c777c 100644 --- a/test/machine/ActivityLifecycleModel.test.ts +++ b/test/machine/ActivityLifecycleModel.test.ts @@ -72,7 +72,7 @@ describe("machine activity lifecycle model", () => { const states = Machine.defineStates({ Idle, Active }) const machine = Machine.make({ states: states.states, - events: [Enter, Leave, Restart], + events: Machine.events(Enter, Leave, Restart), initial: () => states.initial.Idle(new Idle({})) }).handle({ Idle: { @@ -146,8 +146,8 @@ describe("machine activity lifecycle model", () => { }) const machine = Machine.make({ states: states.states, - events: [], - internalEvents: [Completed], + events: Machine.events(), + internalEvents: Machine.internalEvents(Completed), initial: () => states.initial.Active(new Active({})) }).handle({ Active: { @@ -182,8 +182,8 @@ describe("machine activity lifecycle model", () => { const states = Machine.defineStates({ Active: EpochActive, Done }) const machine = Machine.make({ states: states.states, - events: [Restart, QueueBarrier], - internalEvents: [Completed], + events: Machine.events(Restart, QueueBarrier), + internalEvents: Machine.internalEvents(Completed), initial: () => states.initial.Active(new EpochActive({ acknowledged: 0 })) }).handle({ Active: { @@ -280,7 +280,7 @@ describe("machine activity lifecycle model", () => { } } }, - events: [LeaveLeft], + events: Machine.events(LeaveLeft), initial: () => ({ path: "Root" as const, value: new Root({}), @@ -362,8 +362,8 @@ describe("machine activity lifecycle model", () => { const states = Machine.defineStates({ Idle, Active, Done }) const machine = Machine.make({ states: states.states, - events: [Leave], - internalEvents: [TimerFired], + events: Machine.events(Leave), + internalEvents: Machine.internalEvents(TimerFired), initial: () => states.initial.Active(new Active({})) }).handle({ Idle: {}, @@ -409,7 +409,7 @@ describe("machine activity lifecycle model", () => { const states = Machine.defineStates({ Active }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Active(new Active({})) }).handle({ Active: { @@ -457,7 +457,7 @@ describe("machine activity lifecycle model", () => { const states = Machine.defineStates({ Active }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Active(new Active({})) }).handle({ Active: { diff --git a/test/machine/Annotations.test.ts b/test/machine/Annotations.test.ts index a96afd2..7d6ac3f 100644 --- a/test/machine/Annotations.test.ts +++ b/test/machine/Annotations.test.ts @@ -46,7 +46,7 @@ const States = Machine.defineStates({ const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: () => States.initial.Workflow(new Workflow({}), (workflow) => workflow.Idle(new Idle({}))) }) diff --git a/test/machine/AnnotationsVisualization.test.ts b/test/machine/AnnotationsVisualization.test.ts index 86959a6..9150530 100644 --- a/test/machine/AnnotationsVisualization.test.ts +++ b/test/machine/AnnotationsVisualization.test.ts @@ -32,7 +32,7 @@ const States = Machine.defineStates({ const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: () => States.initial.Workflow(new Workflow({}), (workflow) => workflow.Idle(new Idle({}))) }) diff --git a/test/machine/Choice.test.ts b/test/machine/Choice.test.ts index 9fd8764..3523be9 100644 --- a/test/machine/Choice.test.ts +++ b/test/machine/Choice.test.ts @@ -22,7 +22,7 @@ const States = Machine.defineStates({ const machine = Machine.make({ states: States.states, - events: [Recheck], + events: Machine.events(Recheck), initial: () => States.initial.Flow(new Flow({ score: 80 }), (flow) => flow.Routing()) }).handle({ Flow: { @@ -93,7 +93,7 @@ describe("Machine choice pseudo-states", () => { }) const chained = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Flow(new Flow({ score: 80 }), (flow) => flow.First()) }).handle({ Flow: { @@ -146,7 +146,7 @@ describe("Machine choice pseudo-states", () => { const looping = Machine.make({ id: "ChoiceLoopMachine", states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Flow(new Flow({ score: 80 }), (flow) => flow.First()) }).handle({ Flow: { @@ -182,7 +182,7 @@ describe("Machine choice pseudo-states", () => { }) const alwaysMachine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Flow( new Flow({ score: 10 }), @@ -240,7 +240,7 @@ describe("Machine choice pseudo-states", () => { }) const completion = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Flow(new Flow({ score: 0 }), (flow) => flow.Done(new Done({}))) }).handle({ Flow: { @@ -300,7 +300,7 @@ describe("Machine choice pseudo-states", () => { }) const parallel = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Board( new Board({}), @@ -366,7 +366,7 @@ describe("Machine choice pseudo-states", () => { }) const history = Machine.make({ states: states.states, - events: [Leave, Resume], + events: Machine.events(Leave, Resume), initial: () => states.initial.Flow(new Flow({ score: 1 }), (flow) => flow.Active(new Active({}))) }).handle({ Flow: { @@ -420,7 +420,7 @@ describe("Machine choice pseudo-states", () => { }) const initialHistory = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Flow(new Flow({ score: 1 }), (flow) => flow.Routing()) }).handle({ Flow: { @@ -467,7 +467,7 @@ describe("Machine choice pseudo-states", () => { }) const historyChoice = Machine.make({ states: states.states, - events: [Resume], + events: Machine.events(Resume), initial: () => states.initial.Outside(new Outside({})) }).handle({ Flow: { diff --git a/test/machine/DeepHandlers.test.ts b/test/machine/DeepHandlers.test.ts index 79e4237..055365d 100644 --- a/test/machine/DeepHandlers.test.ts +++ b/test/machine/DeepHandlers.test.ts @@ -109,7 +109,7 @@ const initial = States.initial.n0( const machine = Machine.make({ states: States.states, - events: [Advance], + events: Machine.events(Advance), initial: () => initial }).handle({ n0: { diff --git a/test/machine/History.test.ts b/test/machine/History.test.ts index d64e676..e03f3ff 100644 --- a/test/machine/History.test.ts +++ b/test/machine/History.test.ts @@ -122,7 +122,7 @@ const makeCheckoutMachine = ( ) => Machine.make({ states: CheckoutStates.states, - events: [Leave, ResumeShallow, ResumeDeep, GoShipping, EnterVerifying, ReenterHistory], + events: Machine.events(Leave, ResumeShallow, ResumeDeep, GoShipping, EnterVerifying, ReenterHistory), initial: () => initial }).handle({ checkout: { @@ -291,7 +291,7 @@ const activeWorkspace: Machine.Machine.Snapshot = const makeWorkspaceMachine = (initialized: Array) => Machine.make({ states: WorkspaceStates.states, - events: [LeaveWorkspace, ResumeWorkspaceShallow, ResumeWorkspaceDeep], + events: Machine.events(LeaveWorkspace, ResumeWorkspaceShallow, ResumeWorkspaceDeep), initial: () => activeWorkspace }).handle({ workspace: { @@ -398,7 +398,7 @@ const nestedParallelSnapshot: Machine.Machine.Snapshot NestedHistoryStates.initial.workspace( new Workspace({ id: "workspace-1" }), diff --git a/test/machine/Inspection.test.ts b/test/machine/Inspection.test.ts index 6fc8c97..090fdc3 100644 --- a/test/machine/Inspection.test.ts +++ b/test/machine/Inspection.test.ts @@ -36,7 +36,7 @@ const States = Machine.defineStates({ const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: () => States.initial.root(new Root({}), (root) => root @@ -57,7 +57,7 @@ const ChoiceStates = Machine.defineStates({ const choiceMachine = Machine.make({ states: ChoiceStates.states, - events: [], + events: Machine.events(), initial: () => ChoiceStates.initial.Flow(new ChoiceFlow({}), (flow) => flow.Routing()) }).handle({ Flow: { diff --git a/test/machine/Invoke.test.ts b/test/machine/Invoke.test.ts index 0f13b39..ca9abda 100644 --- a/test/machine/Invoke.test.ts +++ b/test/machine/Invoke.test.ts @@ -19,7 +19,7 @@ describe("inline invoke", () => { Effect.gen(function*() { const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: () => States.initial.Loading.from() }).handle({ Loading: { @@ -49,7 +49,7 @@ describe("inline invoke", () => { Effect.gen(function*() { const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: () => States.initial.Loading.from() }).handle({ Loading: { @@ -73,7 +73,7 @@ describe("inline invoke", () => { const defect = new Error("source defect") const machine = Machine.make({ states: States.states, - events: [Start], + events: Machine.events(Start), initial: () => States.initial.Idle.from() }).handle({ Idle: { @@ -111,7 +111,7 @@ describe("inline invoke", () => { }) const machine = Machine.make({ states: States.states, - events: [Start], + events: Machine.events(Start), initial: () => States.initial.Idle.from() }).handle({ Idle: { diff --git a/test/machine/Machine.test.ts b/test/machine/Machine.test.ts index 24bbe95..bc6f227 100644 --- a/test/machine/Machine.test.ts +++ b/test/machine/Machine.test.ts @@ -114,7 +114,7 @@ describe("Machine", () => { const on: { Ping?: typeof transition } = { Ping: transition } const machine = Machine.make({ states: states.states, - events: [Ping], + events: Machine.events(Ping), initial: () => states.initial.Stable(new Stable({})) }).handle({ Stable: { on } }) @@ -289,7 +289,7 @@ describe("Machine", () => { const states = Machine.defineStates({ Idle }) const machine = Machine.make({ states: states.states, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => states.initial.Idle(new Idle({ userId: input.userId })) }) @@ -304,7 +304,7 @@ describe("Machine", () => { const states = Machine.defineStates({ Idle }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Idle(new Idle({ userId: "user-1" })) }) @@ -324,7 +324,7 @@ describe("Machine", () => { const machine = Machine.make({ id: "UserMachine", states: states.states, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => states.initial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -351,7 +351,7 @@ describe("Machine", () => { const defined = Machine.defineStates(states) const machine = Machine.make({ states: defined.states, - events: [Submit], + events: Machine.events(Submit), initial: () => defined.initial.idle(new Idle({ userId: "user-1" })) }) @@ -462,7 +462,7 @@ describe("Machine", () => { const entering = new EnteringPayment({ amount: 100 }) const machine = Machine.make({ states: states.states, - events: [Authorize], + events: Machine.events(Authorize), initial: () => states.initial.payment( payment, @@ -511,7 +511,7 @@ describe("Machine", () => { const quoting = new QuotingShipping({ postalCode: "12345" }) const machine = Machine.make({ states: states.states, - events: [ReserveInventory], + events: Machine.events(ReserveInventory), initial: () => states.initial.fulfillment( fulfillment, @@ -574,14 +574,14 @@ describe("Machine", () => { }) const State = Schema.TaggedStruct("DeferredEventState", { value: Schema.String }) const states = Machine.defineStates({ Active: State }) + const events = Machine.events(PublicEvent, Defaulted, FiniteEvent) + const internalEvents = Machine.internalEvents(InternalEvent) const definition = Machine.make({ states: states.states, - events: [PublicEvent, Defaulted, FiniteEvent], - internalEvents: [InternalEvent], + events, + internalEvents, initial: () => states.initial.Active.from({ value: "initial" }) }) - const events = Machine.events(definition) - const internalEvents = Machine.internalEvents(definition) const machine = definition.handle({ Active: { on: { @@ -600,9 +600,21 @@ describe("Machine", () => { assert.deepStrictEqual(Object.keys(events), ["SetValue", "Reset", "Defaulted", "Alpha", "Beta"]) assert.deepStrictEqual(Object.keys(internalEvents), ["Loaded", "TimedOut"]) + assert.strictEqual(definition.events, events) + assert.strictEqual(definition.internalEvents, internalEvents) + assert.strictEqual(Object.isFrozen(events), true) + assert.strictEqual(Object.hasOwn(events, "schemas"), false) + assert.strictEqual(Object.hasOwn(events, "cases"), false) + const reset = events.Reset() + assert.strictEqual(Object.isFrozen(reset), true) + assert.strictEqual(Object.hasOwn(reset, "schema"), false) + assert.strictEqual(Object.hasOwn(reset, "input"), false) const initial = yield* Machine.planInitial(machine) - const set = yield* Machine.plan(machine, initial.state, events.SetValue({ value: "next" })) + const fields = { value: "next" } + const setValue = events.SetValue(fields) + fields.value = "mutated" + const set = yield* Machine.plan(machine, initial.state, setValue) assert.deepStrictEqual(set.next, { path: "Active", value: { _tag: "DeferredEventState", value: "next" } @@ -636,20 +648,30 @@ describe("Machine", () => { const definition = Machine.make({ id: "deferred-event-failure", states: states.states, - events: [Event], + events: Machine.events(Event), initial: () => states.initial.Idle.from() }) - const events = Machine.events(definition) + const events = definition.events const machine = definition.handle({ Idle: { on: { Submit: () => undefined } } }) let construction: ReturnType | undefined assert.doesNotThrow(() => { construction = events.Submit({ value: "" }) }) + const accessorFailure = events.Submit({ + get value(): string { + throw new Error("accessor failed") + } + }) const initial = yield* Machine.planInitial(machine) const planningError = yield* Machine.plan(machine, initial.state, construction!).pipe(Effect.flip) assertMachineSchemaDecodeError(planningError, "event", { event: "Submit" }) + const accessorError = yield* Machine.plan(machine, initial.state, accessorFailure).pipe(Effect.flip) + assert.instanceOf(accessorError, Machine.MachineSchemaDecodeError) + assert.strictEqual(accessorError.boundary, "event") + assert.strictEqual(accessorError.event, "Submit") + assert.isTrue(Cause.isCause(accessorError.cause)) const actor = yield* Machine.start(machine) const snapshot = yield* sendAndWaitForSnapshot( @@ -670,8 +692,8 @@ describe("Machine", () => { const states = Machine.defineStates({ Loading: {}, Waiting: {}, Done: {} }) const definition = Machine.make({ states: states.states, - events: [], - internalEvents: [InternalEvent], + events: Machine.events(), + internalEvents: Machine.internalEvents(InternalEvent), initial: () => states.initial.Loading.from() }) const machine = definition.handle({ @@ -716,15 +738,15 @@ describe("Machine", () => { const states = Machine.defineStates({ Idle: {} }) const first = Machine.make({ states: states.states, - events: [FirstEvent], + events: Machine.events(FirstEvent), initial: () => states.initial.Idle.from() }) const second = Machine.make({ states: states.states, - events: [SecondEvent], + events: Machine.events(SecondEvent), initial: () => states.initial.Idle.from() }).handle({ Idle: { on: { Submit: () => undefined } } }) - const construction = Machine.events(first).Submit({ value: "value" }) + const construction = first.events.Submit({ value: "value" }) const initial = yield* Machine.planInitial(second) const error = yield* Machine.plan(second, initial.state, construction).pipe(Effect.flip) @@ -733,104 +755,6 @@ describe("Machine", () => { assert.strictEqual(error.event, "Submit") assert.isTrue(Cause.isCause(error.cause)) })) - - it.effect("validates once and shares trust only with derived machine definitions", () => - Effect.gen(function*() { - let validations = 0 - const Tick = Schema.TaggedStruct("ConstructedTick", { value: Schema.Number }).pipe( - Schema.refine((event): event is typeof event => { - validations += 1 - return true - }) - ) - const AlternateTick = Schema.TaggedStruct("ConstructedTick", { value: Schema.Number }) - const Counter = Schema.TaggedStruct("ConstructedCounter", { value: Schema.Number }) - const states = Machine.defineStates({ Counter }) - const definition = Machine.make({ - states: states.states, - events: [Tick], - initial: () => states.initial.Counter.from({ value: 0 }) - }) - - const tick = Machine.event(definition, Tick, { value: 1 }) - assert.deepStrictEqual(tick, { _tag: "ConstructedTick", value: 1 }) - assert.strictEqual(validations, 1) - - const machine = definition.handle({ - Counter: { - on: { - ConstructedTick: () => undefined - } - } - }) - const initial = yield* Machine.planInitial(machine) - for (let index = 0; index < 5; index++) { - yield* Machine.plan(machine, initial.state, tick) - } - assert.strictEqual(validations, 1) - - const raw = Tick.make({ value: 2 }) - assert.strictEqual(validations, 2) - yield* Machine.plan(machine, initial.state, raw) - assert.strictEqual(validations, 3) - - const unrelated = Machine.make({ - states: states.states, - events: [Tick], - initial: () => states.initial.Counter.from({ value: 0 }) - }).handle({ - Counter: { - on: { - ConstructedTick: () => undefined - } - } - }) - yield* Machine.plan(unrelated, (yield* Machine.planInitial(unrelated)).state, tick) - assert.strictEqual(validations, 4) - - assert.throws( - () => Machine.event(machine, AlternateTick, { value: 1 }), - "Machine.event expected a schema from the machine event protocol" - ) - - let invalid: unknown - try { - Machine.event(definition, Tick, { value: "invalid" } as never) - } catch (cause) { - invalid = cause - } - assertMachineSchemaDecodeError(invalid, "event") - })) - - it("constructs configured TaggedUnion cases", () => { - const Event = Schema.TaggedUnion({ - Ping: { message: Schema.String }, - Stop: {} - }) - class Defaulted extends Schema.TaggedClass("ConstructedDefaulted")("Defaulted", { - id: Schema.String, - label: Schema.String.pipe( - Schema.optionalKey, - Schema.withConstructorDefault(Effect.succeed("default-label")) - ) - }) {} - const State = Schema.TaggedStruct("EventConstructorIdle", {}) - const states = Machine.defineStates({ Idle: State }) - const machine = Machine.make({ - states: states.states, - events: [Event, Defaulted], - initial: () => states.initial.Idle.from() - }) - - assert.deepStrictEqual( - Machine.event(machine, Event.cases.Ping, { message: "hello" }), - { _tag: "Ping", message: "hello" } - ) - assert.deepStrictEqual(Machine.event(machine, Event.cases.Stop), { _tag: "Stop" }) - const defaulted = Machine.event(machine, Defaulted, { id: "event-1" }) - assert.instanceOf(defaulted, Defaulted) - assert.deepStrictEqual(defaulted, new Defaulted({ id: "event-1", label: "default-label" })) - }) }) describe("state builder from", () => { @@ -840,7 +764,7 @@ describe("Machine", () => { const machine = Machine.make({ id: "from-default", states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.idle.from({ id: "idle-1" }) }) @@ -868,7 +792,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: states.states, - events: [Event], + events: Machine.events(Event), initial: () => states.initial.Idle.from() }).handle({ Idle: { @@ -903,7 +827,7 @@ describe("Machine", () => { const machine = Machine.make({ id: "from-default-only", states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.DefaultOnly.from() }) @@ -954,13 +878,13 @@ describe("Machine", () => { const machine = Machine.make({ id: "from-empty-targets", states: states.states, - events: [ + events: Machine.events( Event.cases.Local, Event.cases.LocalWith, Event.cases.Branch, Event.cases.Full, Event.cases.Finish - ], + ), initial: () => states.initial.Flow.from((flow) => flow.Idle.from()) }).handle({ Flow: { @@ -1038,7 +962,7 @@ describe("Machine", () => { const machine = Machine.make({ id: "from-empty-parallel", states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Parallel.from((parallel) => parallel @@ -1067,7 +991,7 @@ describe("Machine", () => { const machine = Machine.make({ id: "from-empty-refinement", states: states.states, - events: [], + events: Machine.events(), initial: () => invalid }) @@ -1084,7 +1008,7 @@ describe("Machine", () => { const machine = Machine.make({ id: "from-refinement", states: states.states, - events: [], + events: Machine.events(), initial: () => invalid }) @@ -1100,7 +1024,7 @@ describe("Machine", () => { const machine = Machine.make({ id: "from-transition-refinement", states: states.states, - events: [NonEmptySubmit], + events: Machine.events(NonEmptySubmit), initial: () => states.initial.NonEmptyIdle.from({ userId: "user-1" }) }).handle({ NonEmptyIdle: { @@ -1152,7 +1076,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: states.states, - events: [Submit], + events: Machine.events(Submit), initial: () => states.initial.idle.from({ userId: "user-1" }) }).handle({ idle: { @@ -1212,7 +1136,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: states.states, - events: [Submit], + events: Machine.events(Submit), initial: () => states.initial.payment.from( { id: "payment-1" }, @@ -1263,7 +1187,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: states.states, - events: [Submit], + events: Machine.events(Submit), initial: () => states.initial.workflow.from( { id: "workflow-1" }, @@ -1309,7 +1233,7 @@ describe("Machine", () => { const states = Machine.defineStates({ NonEmptyIdle }) const machine = Machine.make({ states: states.states, - events: [NonEmptySubmit], + events: Machine.events(NonEmptySubmit), input: NonEmptyInput, initial: (input) => states.initial.NonEmptyIdle(new NonEmptyIdle({ userId: input.userId })) }) @@ -1324,7 +1248,7 @@ describe("Machine", () => { const states = Machine.defineStates({ NonEmptyIdle }) const machine = Machine.make({ states: states.states, - events: [NonEmptySubmit], + events: Machine.events(NonEmptySubmit), initial: () => states.initial.NonEmptyIdle(unsafeTagged({ _tag: "NonEmptyIdle", userId: "" })) }) @@ -1338,7 +1262,7 @@ describe("Machine", () => { const states = Machine.defineStates({ NonEmptyIdle }) const machine = Machine.make({ states: states.states, - events: [NonEmptySubmit], + events: Machine.events(NonEmptySubmit), initial: () => states.initial.NonEmptyIdle(new NonEmptyIdle({ userId: "user-1" })) }).handle({ NonEmptyIdle: { @@ -1364,7 +1288,7 @@ describe("Machine", () => { const states = Machine.defineStates({ NonEmptyIdle }) const machine = Machine.make({ states: states.states, - events: [NonEmptySubmit], + events: Machine.events(NonEmptySubmit), initial: () => states.initial.NonEmptyIdle(new NonEmptyIdle({ userId: "user-1" })) }).handle({ NonEmptyIdle: { @@ -1399,7 +1323,7 @@ describe("Machine", () => { const states = Machine.defineStates({ NonEmptyIdle, NonEmptyLoading }) const machine = Machine.make({ states: states.states, - events: [NonEmptySubmit], + events: Machine.events(NonEmptySubmit), initial: () => states.initial.NonEmptyIdle(new NonEmptyIdle({ userId: "user-1" })) }).handle({ NonEmptyIdle: { @@ -1426,7 +1350,7 @@ describe("Machine", () => { const states = Machine.defineStates({ NonEmptyIdle }) const machine = Machine.make({ states: states.states, - events: [NonEmptySubmit], + events: Machine.events(NonEmptySubmit), initial: () => states.initial.NonEmptyIdle(new NonEmptyIdle({ userId: "user-1" })) }).handle({ NonEmptyIdle: { @@ -1461,7 +1385,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: states.states, - events: [NonEmptySubmit], + events: Machine.events(NonEmptySubmit), initial: () => states.initial.NonEmptyIdle(new NonEmptyIdle({ userId: "user-1" })) }).handle({ NonEmptyIdle: { @@ -1506,7 +1430,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.all( new ParallelRoot({ id: "all" }), @@ -1531,7 +1455,7 @@ describe("Machine", () => { const states = Machine.defineStates({ NonEmptyIdle }) const machine = Machine.make({ states: states.states, - events: [NonEmptySubmit], + events: Machine.events(NonEmptySubmit), initial: () => states.initial.NonEmptyIdle(new NonEmptyIdle({ userId: "user-1" })) }) @@ -1555,7 +1479,7 @@ describe("Machine", () => { const machine = Machine.make({ id: "Counter", states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.count(new EncodedCount({ count: 1 })) }) const planned = yield* Machine.planInitial(machine) @@ -1602,7 +1526,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.fulfillment( new Fulfillment({ id: "fulfillment-1" }), @@ -1651,7 +1575,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.all( new ParallelRoot({ id: "all" }), @@ -1695,7 +1619,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.all( new ParallelRoot({ id: "all" }), @@ -1725,7 +1649,7 @@ describe("Machine", () => { const states = Machine.defineStates({ NonEmptyIdle }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.NonEmptyIdle(new NonEmptyIdle({ userId: "user-1" })) }) @@ -1742,7 +1666,7 @@ describe("Machine", () => { const states = Machine.defineStates({ NonEmptyIdle }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.NonEmptyIdle(new NonEmptyIdle({ userId: "user-1" })) }) @@ -1760,7 +1684,7 @@ describe("Machine", () => { const states = Machine.defineStates({ NonEmptyIdle }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.NonEmptyIdle(new NonEmptyIdle({ userId: "user-1" })) }) @@ -1789,7 +1713,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.payment( new Payment({ id: "payment-1" }), @@ -1817,7 +1741,7 @@ describe("Machine", () => { idle: Idle, loading: Loading }, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => LowercaseInitial.idle(new Idle({ userId: input.userId })) }).handle({ @@ -1849,7 +1773,7 @@ describe("Machine", () => { a: Duplicate, b: Duplicate }, - events: [Submit, Reset], + events: Machine.events(Submit, Reset), initial: () => DuplicateInitial.a(new Duplicate({ value: "a" })) }).handle({ a: { @@ -1889,7 +1813,7 @@ describe("Machine", () => { a: Duplicate, b: Duplicate }, - events: [Submit], + events: Machine.events(Submit), initial: () => DuplicateInitial.a(new Duplicate({ value: "a" })) }).handle({ a: { @@ -1921,7 +1845,7 @@ describe("Machine", () => { type: "final" } }, - events: [Submit], + events: Machine.events(Submit), initial: () => LowercaseInitial.idle(new Idle({ userId: "user-1" })) }).handle({ idle: { @@ -1959,7 +1883,7 @@ describe("Machine", () => { }, failed: Failed }, - events: [Authorize], + events: Machine.events(Authorize), initial: () => ({ path: "payment", value: payment, @@ -2016,7 +1940,7 @@ describe("Machine", () => { }, failed: Failed }, - events: [Authorize, Reset], + events: Machine.events(Authorize, Reset), initial: () => ({ path: "payment", value: payment, @@ -2077,7 +2001,7 @@ describe("Machine", () => { } } }, - events: [Reset], + events: Machine.events(Reset), initial: () => ({ path: "payment", value: payment, @@ -2137,7 +2061,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: states.states, - events: [Submit], + events: Machine.events(Submit), initial: () => states.initial.idle(new Idle({ userId: "user-1" })) }).handle({ idle: { @@ -2229,7 +2153,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: states.states, - events: [Submit], + events: Machine.events(Submit), initial: () => states.initial.workflow( workflow, @@ -2341,7 +2265,7 @@ describe("Machine", () => { ) const machine = Machine.make({ states: states.states, - events: [Submit], + events: Machine.events(Submit), initial: () => initial }).handle({ app: { @@ -2426,7 +2350,7 @@ describe("Machine", () => { const quoting = new QuotingShipping({ postalCode: "12345" }) const machine = Machine.make({ states: states.states, - events: [ReserveInventory], + events: Machine.events(ReserveInventory), initial: () => states.initial.fulfillment( fulfillment, @@ -2513,7 +2437,7 @@ describe("Machine", () => { const nextInventory = new Inventory({ warehouse: "warehouse-2" }) const machine = Machine.make({ states: states.states, - events: [ReserveInventory], + events: Machine.events(ReserveInventory), initial: () => states.initial.fulfillment( fulfillment, @@ -2604,7 +2528,7 @@ describe("Machine", () => { const nextInventory = new Inventory({ warehouse: "warehouse-2" }) const machine = Machine.make({ states: states.states, - events: [ReserveInventory], + events: Machine.events(ReserveInventory), initial: () => states.initial.fulfillment( fulfillment, @@ -2696,7 +2620,7 @@ describe("Machine", () => { const quoting = new QuotingShipping({ postalCode: "12345" }) const machine = Machine.make({ states: states.states, - events: [ReserveInventory], + events: Machine.events(ReserveInventory), initial: () => states.initial.fulfillment( fulfillment, @@ -2790,7 +2714,7 @@ describe("Machine", () => { const shipping = new Shipping({ address: "Main Street" }) const machine = Machine.make({ states: states.states, - events: [ReserveInventory], + events: Machine.events(ReserveInventory), initial: () => states.initial.payment( payment, @@ -2855,7 +2779,7 @@ describe("Machine", () => { } } }, - events: [Authorize, Reset], + events: Machine.events(Authorize, Reset), initial: () => ({ path: "payment", value: payment, @@ -2912,7 +2836,7 @@ describe("Machine", () => { } } }, - events: [Reset], + events: Machine.events(Reset), initial: () => ({ path: "payment", value: payment, @@ -2961,7 +2885,7 @@ describe("Machine", () => { } } }, - events: [Authorize, Reset], + events: Machine.events(Authorize, Reset), initial: () => ({ path: "payment", value: payment, @@ -3039,7 +2963,7 @@ describe("Machine", () => { }, failed: Failed }, - events: [ReserveInventory, Reset], + events: Machine.events(ReserveInventory, Reset), initial: () => ({ path: "checkout", value: checkout, @@ -3127,7 +3051,7 @@ describe("Machine", () => { } } }, - events: [ReserveInventory], + events: Machine.events(ReserveInventory), initial: () => ({ path: "fulfillment", value: fulfillment, @@ -3242,7 +3166,7 @@ describe("Machine", () => { } } }, - events: [ReserveInventory], + events: Machine.events(ReserveInventory), initial: () => ({ path: "fulfillment", value: fulfillment, @@ -3382,7 +3306,7 @@ describe("Machine", () => { } } }, - events: [ReserveInventory, Resolve], + events: Machine.events(ReserveInventory, Resolve), initial: () => ({ path: "fulfillment", value: fulfillment, @@ -3505,7 +3429,7 @@ describe("Machine", () => { } } }, - events: [ReserveInventory], + events: Machine.events(ReserveInventory), initial: () => ({ path: "fulfillment", value: fulfillment, @@ -3610,7 +3534,7 @@ describe("Machine", () => { } } }, - events: [ReserveInventory, Resolve], + events: Machine.events(ReserveInventory, Resolve), initial: () => ({ path: "fulfillment", value: fulfillment, @@ -3693,7 +3617,7 @@ describe("Machine", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Idle }, - events: [Submit], + events: Machine.events(Submit), initial: () => FlatInitial.Idle(new Idle({ userId: "user-1" })) }) @@ -3706,7 +3630,7 @@ describe("Machine", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Idle, Loading }, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -3734,7 +3658,7 @@ describe("Machine", () => { it("enabled returns the event tags handled by the current state", () => { const machine = Machine.make({ states: { Idle, Loading }, - events: [Submit, Reset], + events: Machine.events(Submit, Reset), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -3763,7 +3687,7 @@ describe("Machine", () => { Idle, Success: { schema: Success, type: "final" } }, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -3785,7 +3709,7 @@ describe("Machine", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Idle, Success: SuccessOutput }, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -3815,7 +3739,7 @@ describe("Machine", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Idle, Success: SuccessOutput }, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -3843,7 +3767,7 @@ describe("Machine", () => { let outputCalls = 0 const machine = Machine.make({ states: { Success: SuccessOutput }, - events: [Submit], + events: Machine.events(Submit), initial: () => FlatInitial.Success(new Success({ requestId: "request-1" })) }).handle({ Success: { @@ -3877,7 +3801,7 @@ describe("Machine", () => { let outputCalls = 0 const machine = Machine.make({ states: { Success: SuccessOutput }, - events: [Submit], + events: Machine.events(Submit), initial: () => FlatInitial.Success(new Success({ requestId: "request-1" })) }).handle({ Success: { @@ -3906,7 +3830,7 @@ describe("Machine", () => { Idle, Success: { schema: Success, type: "final" } }, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -3940,7 +3864,7 @@ describe("Machine", () => { Idle, Success: { schema: Success, type: "final" } }, - events: [Submit, Reset], + events: Machine.events(Submit, Reset), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -3973,7 +3897,7 @@ describe("Machine", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Idle, Loading }, - events: [Submit], + events: Machine.events(Submit), initial: () => FlatInitial.Idle(new Idle({ userId: "user-1" })) }).handle({ Idle: { @@ -4000,7 +3924,7 @@ describe("Machine", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Idle, Loading }, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -4031,7 +3955,7 @@ describe("Machine", () => { Idle, Success: { schema: Success, type: "final" } }, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -4050,7 +3974,7 @@ describe("Machine", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Idle, Loading }, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -4073,7 +3997,7 @@ describe("Machine", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Idle, Loading }, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -4117,7 +4041,7 @@ describe("Machine", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Idle, Success: SuccessOutput }, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -4152,7 +4076,7 @@ describe("Machine", () => { const machine = Machine.make({ id: "UserMachine", states: { Idle, Loading }, - events: [Submit, Reset], + events: Machine.events(Submit, Reset), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -4176,7 +4100,7 @@ describe("Machine", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Idle, Loading, Success: SuccessOutput }, - events: [Submit, RequestSucceeded], + events: Machine.events(Submit, RequestSucceeded), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -4217,7 +4141,7 @@ describe("Machine", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Idle, Loading, Success: SuccessOutput }, - events: [Submit, RequestSucceeded], + events: Machine.events(Submit, RequestSucceeded), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -4259,14 +4183,14 @@ describe("Machine", () => { const childStates = Machine.defineStates({ Idle }) const childMachine = Machine.make({ states: childStates.states, - events: [], + events: Machine.events(), initial: () => childStates.initial.Idle(new Idle({ userId: "child" })) }) const Child = Machine.child("shared-child", childMachine) const parentStates = Machine.defineStates({ Loading }) const parentMachine = Machine.make({ states: parentStates.states, - events: [], + events: Machine.events(), initial: () => parentStates.initial.Loading(new Loading({ requestId: "parent" })) }).handle({ Loading: { @@ -4310,14 +4234,14 @@ describe("Machine", () => { const childStates = Machine.defineStates({ Idle }) const childMachine = Machine.make({ states: childStates.states, - events: [], + events: Machine.events(), initial: () => childStates.initial.Idle(new Idle({ userId: "child" })) }).handle({ Idle: {} }) const Child = Machine.child("owned-child", childMachine) const parentStates = Machine.defineStates({ Loading }) const parentMachine = Machine.make({ states: parentStates.states, - events: [], + events: Machine.events(), initial: () => parentStates.initial.Loading(new Loading({ requestId: "parent" })) }).handle({ Loading: { invoke: Machine.invoke({ child: Child }) } @@ -4343,7 +4267,7 @@ describe("Machine", () => { const childStates = Machine.defineStates({ Idle }) const childMachine = Machine.make({ states: childStates.states, - events: [], + events: Machine.events(), input: Input, initial: (input) => { starts += 1 @@ -4354,7 +4278,7 @@ describe("Machine", () => { const parentStates = Machine.defineStates({ Loading }) const parentMachine = Machine.make({ states: parentStates.states, - events: [], + events: Machine.events(), initial: () => parentStates.initial.Loading(new Loading({ requestId: "parent" })) }).handle({ Loading: { @@ -4389,7 +4313,7 @@ describe("Machine", () => { }) const childMachine = Machine.make({ states: childStates.states, - events: [], + events: Machine.events(), initial: () => childStates.initial.Success(new Success({ requestId: "child-output" })) }).handle({ Success: { output: ({ state }) => state.requestId } @@ -4401,7 +4325,7 @@ describe("Machine", () => { }) const parentMachine = Machine.make({ states: parentStates.states, - events: [ChildFinished], + events: Machine.events(ChildFinished), initial: () => parentStates.initial.Loading(new Loading({ requestId: "parent" })) }).handle({ Loading: { @@ -4423,7 +4347,7 @@ describe("Machine", () => { const states = Machine.defineStates({ Idle }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), input: Input, initial: (input) => states.initial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -4453,14 +4377,14 @@ describe("Machine", () => { const childStates = Machine.defineStates({ Idle }) const child = Machine.make({ states: childStates.states, - events: [], + events: Machine.events(), initial: () => childStates.initial.Idle(new Idle({ userId: "child" })) }) const Child = Machine.child("child-machine", child) const parentStates = Machine.defineStates({ Loading }) const parent = Machine.make({ states: parentStates.states, - events: [], + events: Machine.events(), initial: () => parentStates.initial.Loading(new Loading({ requestId: "request-1" })) }).handle({ Loading: { @@ -4489,7 +4413,7 @@ describe("Machine", () => { const parentStates = Machine.defineStates({ Loading }) const parent = Machine.make({ states: parentStates.states, - events: [], + events: Machine.events(), initial: () => parentStates.initial.Loading(new Loading({ requestId: "request-1" })) }).handle({ Loading: { @@ -4521,7 +4445,7 @@ describe("Machine", () => { const error = new InvokeError({ message: "boom" }) const machine = Machine.make({ states: { Idle, Loading, Failed: FailedOutput }, - events: [Submit, RequestFailed], + events: Machine.events(Submit, RequestFailed), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -4562,8 +4486,8 @@ describe("Machine", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Idle, Loading, Success: SuccessOutput }, - events: [Submit], - internalEvents: [RequestSucceeded], + events: Machine.events(Submit), + internalEvents: Machine.internalEvents(RequestSucceeded), initial: () => FlatInitial.Idle(new Idle({ userId: "user-1" })) }).handle({ Idle: { @@ -4594,7 +4518,7 @@ describe("Machine", () => { const childStarted = yield* Deferred.make() const machine = Machine.make({ states: { Loading, Success: SuccessOutput }, - events: [RequestSucceeded], + events: Machine.events(RequestSucceeded), initial: () => FlatInitial.Loading(new Loading({ requestId: "request-1" })) }).handle({ Loading: { @@ -4631,7 +4555,7 @@ describe("Machine", () => { const childStarted = yield* Deferred.make() const machine = Machine.make({ states: { Idle, Loading, Success: SuccessOutput }, - events: [Resolve, RequestSucceeded], + events: Machine.events(Resolve, RequestSucceeded), initial: () => FlatInitial.Loading(new Loading({ requestId: "request-1" })) }).handle({ Idle: { @@ -4683,8 +4607,8 @@ describe("Machine", () => { const failure = new InvokeError({ message: "unavailable" }) const machine = Machine.make({ states: { Loading, Failed: FailedOutput }, - events: [], - internalEvents: [RequestSucceeded, RequestFailed], + events: Machine.events(), + internalEvents: Machine.internalEvents(RequestSucceeded, RequestFailed), initial: () => FlatInitial.Loading(new Loading({ requestId: "request-1" })) }).handle({ Loading: { @@ -4711,8 +4635,8 @@ describe("Machine", () => { }) const machine = Machine.make({ states: { Loading, Success: SuccessOutput }, - events: [], - internalEvents: [RequestSucceeded], + events: Machine.events(), + internalEvents: Machine.internalEvents(RequestSucceeded), initial: () => FlatInitial.Loading(new Loading({ requestId: "request-1" })) }).handle({ Loading: { @@ -4741,8 +4665,8 @@ describe("Machine", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Loading, Success: SuccessOutput }, - events: [], - internalEvents: [RequestSucceeded], + events: Machine.events(), + internalEvents: Machine.internalEvents(RequestSucceeded), initial: () => FlatInitial.Loading(new Loading({ requestId: "request-1" })) }).handle({ Loading: { @@ -4768,7 +4692,7 @@ describe("Machine", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Idle, Loading, Success: SuccessOutput }, - events: [Submit, RequestProgress], + events: Machine.events(Submit, RequestProgress), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -4812,7 +4736,7 @@ describe("Machine", () => { const error = new InvokeError({ message: "boom" }) const machine = Machine.make({ states: { Idle, Loading }, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -4850,7 +4774,7 @@ describe("Machine", () => { const release = yield* Deferred.make() const machine = Machine.make({ states: { Idle, Loading, Success: SuccessOutput }, - events: [Submit, RequestProgress], + events: Machine.events(Submit, RequestProgress), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -4910,7 +4834,7 @@ describe("Machine", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Idle, Loading }, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -4965,7 +4889,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: { Idle, Loading, Success: SuccessOutput }, - events: [Submit, Resolve, RequestSucceeded], + events: Machine.events(Submit, Resolve, RequestSucceeded), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -5049,7 +4973,7 @@ describe("Machine", () => { } } }, - events: [Authorize], + events: Machine.events(Authorize), initial: () => ({ path: "payment", value: payment, @@ -5167,7 +5091,7 @@ describe("Machine", () => { output: Schema.String } }, - events: [ReserveInventory], + events: Machine.events(ReserveInventory), initial: () => ({ path: "fulfillment", value: fulfillment, @@ -5255,7 +5179,7 @@ describe("Machine", () => { const defect = new Error("initializer defect") const machine = Machine.make({ states: { Idle }, - events: [], + events: Machine.events(), initial: () => { throw defect } @@ -5292,7 +5216,7 @@ describe("Machine", () => { } const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => invalidInitialState as any }) @@ -5310,7 +5234,7 @@ describe("Machine", () => { const machine = Machine.make({ id: "LoopMachine", states: { Idle, Loading }, - events: [Submit], + events: Machine.events(Submit), input: Input, initial: (input) => FlatInitial.Idle(new Idle({ userId: input.userId })) }).handle({ @@ -5340,7 +5264,7 @@ describe("Machine", () => { const machine = Machine.make({ id: "InitialLoopMachine", states: { Idle, Loading }, - events: [], + events: Machine.events(), initial: () => FlatInitial.Idle(new Idle({ userId: "user-1" })) }).handle({ Idle: { @@ -5380,7 +5304,7 @@ describe("Machine", () => { const machine = Machine.make({ id: "CompletionLoopMachine", states: states.states, - events: [Submit], + events: Machine.events(Submit), initial: () => states.initial.idle(new Idle({ userId: "user-1" })) }).handle({ idle: { @@ -5444,7 +5368,7 @@ describe("Machine", () => { const makeParallelCounterMachine = () => Machine.make({ states: ParallelCounterStates.states, - events: [AdvanceCounters], + events: Machine.events(AdvanceCounters), initial: () => ParallelCounterStates.initial.running( new CounterRunning({}), @@ -5473,7 +5397,7 @@ describe("Machine", () => { const states = Machine.defineStates({ ConcurrentIdle }) return Machine.make({ states: states.states, - events: [ConcurrentPing], + events: Machine.events(ConcurrentPing), initial: () => states.initial.ConcurrentIdle(new ConcurrentIdle({})) }).handle({ ConcurrentIdle: { diff --git a/test/machine/PublicPrototype.test.ts b/test/machine/PublicPrototype.test.ts index d1ea72e..ba4a066 100644 --- a/test/machine/PublicPrototype.test.ts +++ b/test/machine/PublicPrototype.test.ts @@ -9,7 +9,7 @@ it("uses the public pipeable and inspectable prototypes", () => { const states = Machine.defineStates({ Idle }) const machine = Machine.make({ states: states.states, - events: [Start], + events: Machine.events(Start), initial: () => states.initial.Idle(new Idle()) }) diff --git a/test/machine/Resume.test.ts b/test/machine/Resume.test.ts index 47b86dc..16bc4f9 100644 --- a/test/machine/Resume.test.ts +++ b/test/machine/Resume.test.ts @@ -69,7 +69,7 @@ describe("Machine.resume", () => { }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Inactive(new Inactive({})) }).handle({ Root: { @@ -157,18 +157,19 @@ describe("Machine.resume", () => { root .left(new Left({}), (left) => left.A(new LeftA({}))) .right(new Right({}), (right) => right.A(new RightA({})))) - const machine = Machine.make({ states: states.states, events: [Advance], initial: () => initial }).handle({ - Root: { - states: { - left: { - states: { A: { on: { Advance: ({ target }) => target.local.B(new LeftB({})) } } } - }, - right: { - states: { A: { on: { Advance: ({ target }) => target.local.B(new RightB({})) } } } + const machine = Machine.make({ states: states.states, events: Machine.events(Advance), initial: () => initial }) + .handle({ + Root: { + states: { + left: { + states: { A: { on: { Advance: ({ target }) => target.local.B(new LeftB({})) } } } + }, + right: { + states: { A: { on: { Advance: ({ target }) => target.local.B(new RightB({})) } } } + } } } - } - }) + }) const ref = yield* Machine.resume(machine, initial) yield* sendAndWait( ref, @@ -205,7 +206,7 @@ describe("Machine.resume", () => { }) const machine = Machine.make({ states: states.states, - events: [Finish], + events: Machine.events(Finish), initial: () => states.initial.Count(new Count({ value: 0 })) }).handle({ Count: { on: { Finish: ({ target }) => target.full.Done(new Done({ value: 9 })) } }, @@ -242,10 +243,11 @@ describe("Machine.resume", () => { { path: "Flow", output: undefined } ] } - const machine = Machine.make({ states: states.states, events: [Ping], initial: () => logical }).handle({ - Flow: { onDone: ({ target }) => target.full.Next(new Next({})) }, - Next: {} - }) + const machine = Machine.make({ states: states.states, events: Machine.events(Ping), initial: () => logical }) + .handle({ + Flow: { onDone: ({ target }) => target.full.Next(new Next({})) }, + Next: {} + }) const decoded = yield* Machine.decodeSnapshot(machine, yield* Machine.encodeSnapshot(machine, logical)) const ref = yield* Machine.resume(machine, decoded) @@ -263,7 +265,7 @@ describe("Machine.resume", () => { const states = Machine.defineStates({ A, B }) const machine = Machine.make({ states: states.states, - events: [Ping], + events: Machine.events(Ping), initial: () => states.initial.A(new A({})) }).handle({ A: { always: ({ target }) => target.full.B(new B({})) }, @@ -286,8 +288,8 @@ describe("Machine.resume", () => { const states = Machine.defineStates({ Waiting, Cancelled, TimedOut }) const machine = Machine.make({ states: states.states, - events: [Cancel], - internalEvents: [Timeout], + events: Machine.events(Cancel), + internalEvents: Machine.internalEvents(Timeout), initial: () => states.initial.Cancelled(new Cancelled({})) }).handle({ Waiting: { @@ -329,8 +331,8 @@ describe("Machine.resume", () => { const states = Machine.defineStates({ Loading, Loaded }) const machine = Machine.make({ states: states.states, - events: [], - internalEvents: [LoadedEvent], + events: Machine.events(), + internalEvents: Machine.internalEvents(LoadedEvent), initial: () => states.initial.Loaded(new Loaded({ value: "initial" })) }).handle({ Loading: { @@ -364,8 +366,8 @@ describe("Machine.resume", () => { const states = Machine.defineStates({ Loading, Failed }) const machine = Machine.make({ states: states.states, - events: [], - internalEvents: [FailedEvent], + events: Machine.events(), + internalEvents: Machine.internalEvents(FailedEvent), initial: () => states.initial.Failed(new Failed({ message: "initial" })) }).handle({ Loading: { @@ -402,7 +404,7 @@ describe("Machine.resume", () => { }) const child = Machine.make({ states: childStates.states, - events: [ChildFinish], + events: Machine.events(ChildFinish), initial: () => childStates.initial.ChildIdle(new ChildIdle({ value: 1 })) }).handle({ ChildIdle: { @@ -414,7 +416,7 @@ describe("Machine.resume", () => { const states = Machine.defineStates({ Parent, ChildOutput }) const machine = Machine.make({ states: states.states, - events: [ChildOutput], + events: Machine.events(ChildOutput), initial: () => states.initial.ChildOutput(new ChildOutput({ value: 0 })) }).handle({ Parent: { @@ -460,7 +462,7 @@ describe("Machine.resume", () => { root .left(new Region({}), (left) => left.Leaf(new Leaf({ value: 1 }))) .right(new Region({}), (right) => right.Leaf(new Leaf({ value: 2 })))) - const machine = Machine.make({ states: states.states, events: [], initial: () => valid }) + const machine = Machine.make({ states: states.states, events: Machine.events(), initial: () => valid }) const forged: ReadonlyArray = [ { path: "Missing", value: {} }, { path: "Root", value: new Root({}), states: { left: valid.states.left } }, @@ -497,7 +499,7 @@ describe("Machine.resume", () => { const states = Machine.defineStates({ Count }) const machine = Machine.make({ states: states.states, - events: [Add], + events: Machine.events(Add), initial: () => states.initial.Count(new Count({ value: 0 })) }).handle({ Count: { diff --git a/test/machine/RuntimeDifferential.test.ts b/test/machine/RuntimeDifferential.test.ts index af52aec..9bf15c6 100644 --- a/test/machine/RuntimeDifferential.test.ts +++ b/test/machine/RuntimeDifferential.test.ts @@ -52,8 +52,8 @@ describe("pure planning and managed runtime differential", () => { }) const machine = Machine.make({ states: states.states, - events: [Cascade, Ignore, Finish], - internalEvents: [Increment], + events: Machine.events(Cascade, Ignore, Finish), + internalEvents: Machine.internalEvents(Increment), initial: () => states.initial.Count(new Count({ value: 0 })) }).handle({ Count: { @@ -137,8 +137,8 @@ describe("pure planning and managed runtime differential", () => { }> = [] const machine = Machine.make({ states: states.states, - events: [Advance, Inspect, Finish], - internalEvents: [Bump], + events: Machine.events(Advance, Inspect, Finish), + internalEvents: Machine.internalEvents(Bump), initial: () => states.initial.Running( new Running({}), @@ -423,8 +423,8 @@ describe("pure planning and managed runtime differential", () => { }) const machine = Machine.make({ states: states.states, - events: [Begin], - internalEvents: [RaisedOne, RaisedTwo], + events: Machine.events(Begin), + internalEvents: Machine.internalEvents(RaisedOne, RaisedTwo), emits: [Notice], initial: () => states.initial.Idle(new Idle({})) }).handle({ @@ -534,7 +534,7 @@ describe("pure planning and managed runtime differential", () => { const states = Machine.defineStates({ Idle, Active }) const machine = Machine.make({ states: states.states, - events: [Ignore, Go], + events: Machine.events(Ignore, Go), initial: () => states.initial.Idle(new Idle({})) }).handle({ Idle: { on: { Go: () => states.initial.Active(new Active({})) } }, diff --git a/test/machine/Scheduling.test.ts b/test/machine/Scheduling.test.ts index 737a629..0176a65 100644 --- a/test/machine/Scheduling.test.ts +++ b/test/machine/Scheduling.test.ts @@ -19,8 +19,8 @@ describe("machine scheduling", () => { const states = Machine.defineStates({ SchedulingActive }) const machine = Machine.make({ states: states.states, - events: [StartBurst], - internalEvents: [Burst], + events: Machine.events(StartBurst), + internalEvents: Machine.internalEvents(Burst), initial: () => states.initial.SchedulingActive(new SchedulingActive({ count: 0 })) }).handle({ SchedulingActive: { diff --git a/test/machine/SnapshotCodecAdversarial.test.ts b/test/machine/SnapshotCodecAdversarial.test.ts index 772e851..8fa0a2f 100644 --- a/test/machine/SnapshotCodecAdversarial.test.ts +++ b/test/machine/SnapshotCodecAdversarial.test.ts @@ -53,7 +53,7 @@ const TopologyStates = Machine.defineStates({ const topologyMachine = Machine.make({ id: "codec-topology", states: TopologyStates.states, - events: [], + events: Machine.events(), initial: () => topologyActive() }) @@ -122,7 +122,7 @@ const HistoryStates = Machine.defineStates({ const historyMachine = Machine.make({ id: "codec-history", states: HistoryStates.states, - events: [], + events: Machine.events(), initial: () => HistoryStates.initial.Outside(new Outside({})) }) @@ -227,7 +227,7 @@ describe("snapshot codec adversarial boundaries", () => { const original = Machine.make({ id: "codec-automatic-original", states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Before(new Before({})) }).handle({ Before: { always: ({ target }) => target.full.Boundary(new Boundary({})) }, @@ -237,7 +237,7 @@ describe("snapshot codec adversarial boundaries", () => { const changed = Machine.make({ id: "codec-automatic-changed", states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Before(new Before({})) }).handle({ Before: {}, diff --git a/test/machine/SnapshotContext.test.ts b/test/machine/SnapshotContext.test.ts index 075b5cf..e3ccf8f 100644 --- a/test/machine/SnapshotContext.test.ts +++ b/test/machine/SnapshotContext.test.ts @@ -46,7 +46,7 @@ describe("Machine transition snapshot context", () => { let captured: Machine.Machine.Snapshot | undefined const machine = Machine.make({ states: States.states, - events: [BufferReady], + events: Machine.events(BufferReady), initial }).handle({ System: { @@ -85,7 +85,7 @@ describe("Machine transition snapshot context", () => { const captured: Array> = [] const machine = Machine.make({ states: States.states, - events: [Disconnect], + events: Machine.events(Disconnect), initial }).handle({ System: { @@ -134,7 +134,7 @@ describe("Machine transition snapshot context", () => { let captured: Machine.Machine.Snapshot | undefined const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial }).handle({ System: { @@ -193,7 +193,7 @@ describe("Machine transition snapshot context", () => { let captured: Machine.Machine.Snapshot | undefined const machine = Machine.make({ states: completionStates.states, - events: [], + events: Machine.events(), initial: () => completionStates.initial.System( new System({}), diff --git a/test/machine/StateDefinition.test.ts b/test/machine/StateDefinition.test.ts index ab315c4..e3f2761 100644 --- a/test/machine/StateDefinition.test.ts +++ b/test/machine/StateDefinition.test.ts @@ -36,7 +36,7 @@ const expectDefinitionError = ( const makeFromUnknownStates = (states: unknown): unknown => Machine.make({ states: states as Machine.Machine.StateSchemas, - events: [], + events: Machine.events(), initial: (): never => { throw new Error("unreachable") } @@ -65,7 +65,7 @@ describe("exact state-definition runtime validation", () => { }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Idle.from() }) @@ -88,7 +88,7 @@ describe("exact state-definition runtime validation", () => { const states = Machine.defineStates({ Idle: AnnotatedIdle }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Idle(new Idle({})) }) @@ -105,7 +105,7 @@ describe("exact state-definition runtime validation", () => { const states = Machine.defineStates({ Opaque: OpaqueState }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Opaque({ _tag: "OpaqueState", value: 1 }) }) diff --git a/test/machine/StructuralStates.test.ts b/test/machine/StructuralStates.test.ts index ddc96d9..0e757a6 100644 --- a/test/machine/StructuralStates.test.ts +++ b/test/machine/StructuralStates.test.ts @@ -78,7 +78,7 @@ const initial = States.initial.player.from((player) => const makeMachine = () => Machine.make({ states: States.states, - events: [SourceSelected, Loaded, Play, Mute], + events: Machine.events(SourceSelected, Loaded, Play, Mute), initial: () => initial }).handle({ player: { @@ -150,7 +150,7 @@ const historyFallback = () => const historyMachine = Machine.make({ states: HistoryStates.states, - events: [Edit, Leave, ResumeShallow, ResumeDeep], + events: Machine.events(Edit, Leave, ResumeShallow, ResumeDeep), initial: historyFallback }).handle({ flow: { @@ -311,7 +311,7 @@ describe("structural active states", () => { Effect.gen(function*() { const machine = Machine.make({ states: FinalStates.states, - events: [], + events: Machine.events(), initial: () => FinalStates.initial.Done.from() }).handle({ Done: { diff --git a/test/machine/Totality.test.ts b/test/machine/Totality.test.ts index 66fdf27..8c3d2c8 100644 --- a/test/machine/Totality.test.ts +++ b/test/machine/Totality.test.ts @@ -256,7 +256,7 @@ describe("machine operation totality", () => { }) const machine = Machine.make({ states: states.states, - events: [Finish], + events: Machine.events(Finish), initial: () => states.initial.Value({ _tag: "Value", amount: 42 }) }).handle({ Value: { on: { Finish: ({ target }) => target.full.Done({ _tag: "Done" }) } }, diff --git a/test/machine/Visualization.test.ts b/test/machine/Visualization.test.ts index f496c7b..925880f 100644 --- a/test/machine/Visualization.test.ts +++ b/test/machine/Visualization.test.ts @@ -72,7 +72,7 @@ const initialWorkflow = (): Machine.Machine.CompleteSnapshotContaining< const machine = Machine.make({ id: "inspection-example", states: States.states, - events: [Start, Disconnect, Refresh], + events: Machine.events(Start, Disconnect, Refresh), initial: () => initial }).handle({ application: { @@ -135,7 +135,7 @@ const LifecycleStates = Machine.defineStates({ const lifecycleMachine = Machine.make({ id: "lifecycle-inspection", states: LifecycleStates.states, - events: [], + events: Machine.events(), initial: () => LifecycleStates.initial.idle(new Idle({})) }).handle({ idle: { @@ -211,7 +211,7 @@ describe("Machine structural visualization", () => { it("describes reentry, eventless, and completion handlers", () => { const metadataMachine = Machine.make({ states: { idle: Idle }, - events: [Refresh], + events: Machine.events(Refresh), initial: () => ({ path: "idle", value: new Idle({}) }) }).handle({ idle: { diff --git a/test/testing/Coverage.test.ts b/test/testing/Coverage.test.ts index e0c46a0..e3fa8b9 100644 --- a/test/testing/Coverage.test.ts +++ b/test/testing/Coverage.test.ts @@ -19,7 +19,7 @@ const CounterStates = Machine.defineStates({ count: Count, done: Done }) const counterMachine = Machine.make({ states: CounterStates.states, - events: [Add, Finish], + events: Machine.events(Add, Finish), initial: () => CounterStates.initial.count(new Count({ value: 0 })) }).handle({ count: { @@ -45,7 +45,7 @@ class Opaque extends Schema.TaggedClass("Opaque")("Opaque", { const OpaqueStates = Machine.defineStates({ opaque: Opaque }) const opaqueMachine = Machine.make({ states: OpaqueStates.states, - events: [], + events: Machine.events(), input: Schema.Any, initial: (payload) => OpaqueStates.initial.opaque(new Opaque({ payload })) }) @@ -53,7 +53,7 @@ const opaqueMachine = Machine.make({ const StartupStates = Machine.defineStates({ count: Count }) const startupMachine = Machine.make({ states: StartupStates.states, - events: [Add], + events: Machine.events(Add), initial: () => StartupStates.initial.count(new Count({ value: 0 })) }).handle({ count: { @@ -76,7 +76,7 @@ const OpenEvent = Schema.Struct({ _tag: Schema.String }) const EventStates = Machine.defineStates({ count: Count }) const finiteEventMachine = Machine.make({ states: EventStates.states, - events: [TickEvent, ChoiceEvent], + events: Machine.events(TickEvent, ChoiceEvent), initial: () => EventStates.initial.count(new Count({ value: 0 })) }).handle({ count: { @@ -89,7 +89,7 @@ const finiteEventMachine = Machine.make({ }) const openEventMachine = Machine.make({ states: EventStates.states, - events: [OpenEvent], + events: Machine.events(OpenEvent), initial: () => EventStates.initial.count(new Count({ value: 0 })) }).handle({ count: {} }) diff --git a/test/testing/Exploration.test.ts b/test/testing/Exploration.test.ts index 0be7d95..c31e216 100644 --- a/test/testing/Exploration.test.ts +++ b/test/testing/Exploration.test.ts @@ -16,7 +16,7 @@ const States = Machine.defineStates({ counter: Counter }) const machine = Machine.make({ states: States.states, - events: [Increment, Reset, Corrupt], + events: Machine.events(Increment, Reset, Corrupt), initial: () => States.initial.counter(new Counter({ count: 0 })) }).handle({ counter: { @@ -206,7 +206,7 @@ describe("MachineTest bounded exploration", () => { Effect.gen(function*() { const inputMachine = Machine.make({ states: States.states, - events: [Increment], + events: Machine.events(Increment), input: Seed, initial: ({ count }) => States.initial.counter(new Counter({ count })) }).handle({ counter: {} }) diff --git a/test/testing/Invariant.test.ts b/test/testing/Invariant.test.ts index e331b02..2d35dd9 100644 --- a/test/testing/Invariant.test.ts +++ b/test/testing/Invariant.test.ts @@ -21,7 +21,7 @@ const States = Machine.defineStates({ account: Account }) const makeAccountMachine = (withdraw: (balance: number, amount: number) => number) => Machine.make({ states: States.states, - events: [Withdraw, Deposit], + events: Machine.events(Withdraw, Deposit), initial: () => States.initial.account(new Account({ balance: 10 })) }).handle({ account: { diff --git a/test/testing/MachineTest.test.ts b/test/testing/MachineTest.test.ts index 51e4f6a..13b846a 100644 --- a/test/testing/MachineTest.test.ts +++ b/test/testing/MachineTest.test.ts @@ -27,7 +27,7 @@ const States = Machine.defineStates({ Idle, Ready }) const makeTraceMachine = (onAction: () => void) => Machine.make({ states: States.states, - events: [Start, Add], + events: Machine.events(Start, Add), input: TestInput, initial: ({ userId }) => States.initial.Ready(new Ready({ count: userId.length - userId.length })) }).handle({ @@ -96,7 +96,7 @@ describe("MachineTest", () => { }) const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), input: PositiveInput, initial: () => States.initial.Idle(new Idle({ userId: "user-1" })) }) @@ -115,7 +115,7 @@ describe("MachineTest", () => { it("rejects a non-empty minimum for machines without public events", () => { const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: () => States.initial.Idle(new Idle({ userId: "user-1" })) }) @@ -187,7 +187,7 @@ describe("MachineTest", () => { }) const machine = Machine.make({ states: ParallelStates.states, - events: [Stop], + events: Machine.events(Stop), initial: () => ParallelStates.initial.app( new App({}), @@ -244,7 +244,7 @@ describe("MachineTest", () => { Effect.gen(function*() { const machine = Machine.make({ states: { Idle }, - events: [Start], + events: Machine.events(Start), initial: () => ({ path: "Idle", value: new Idle({ userId: "user-1" }) }) }).handle({ Idle: { diff --git a/test/testing/Probe.test.ts b/test/testing/Probe.test.ts index 48218e5..ca474c8 100644 --- a/test/testing/Probe.test.ts +++ b/test/testing/Probe.test.ts @@ -21,8 +21,8 @@ const states = Machine.defineStates({ Counter }) const machine = Machine.make({ states: states.states, - events: [Increment, Noop, Ignored, Burst, Reenter], - internalEvents: [RaisedIncrement], + events: Machine.events(Increment, Noop, Ignored, Burst, Reenter), + internalEvents: Machine.internalEvents(RaisedIncrement), initial: () => states.initial.Counter(new Counter({ count: 0 })) }).handle({ Counter: { @@ -140,7 +140,7 @@ describe("MachineTest probe", () => { let starts = 0 const invokeMachine = Machine.make({ states: invokeStates.states, - events: [Load], + events: Machine.events(Load), initial: () => invokeStates.initial.Idle(new Idle({})) }).handle({ Idle: { diff --git a/test/testing/ReferenceModel.test.ts b/test/testing/ReferenceModel.test.ts index 860c246..efa94fc 100644 --- a/test/testing/ReferenceModel.test.ts +++ b/test/testing/ReferenceModel.test.ts @@ -939,7 +939,7 @@ describe("MachineTest finite-model reference interpreter", () => { }) const machine = Machine.make({ states: states.states, - events: [Local, Exit], + events: Machine.events(Local, Exit), initial: () => states.initial.root({ _tag: "Root", version: 0 }, (regions) => regions diff --git a/test/testing/Runtime.test.ts b/test/testing/Runtime.test.ts index 7c5a5c1..b674d97 100644 --- a/test/testing/Runtime.test.ts +++ b/test/testing/Runtime.test.ts @@ -25,8 +25,8 @@ const CounterStates = Machine.defineStates({ Counter }) const makeCounterMachine = () => Machine.make({ states: CounterStates.states, - events: [Add], - internalEvents: [InternalAdd], + events: Machine.events(Add), + internalEvents: Machine.internalEvents(InternalAdd), initial: () => CounterStates.initial.Counter(new Counter({ count: 0 })) }).handle({ Counter: { @@ -40,8 +40,8 @@ const makeCounterMachine = () => const causalMachine = Machine.make({ states: CounterStates.states, - events: [Add, Noop, Ignored, Burst], - internalEvents: [InternalAdd], + events: Machine.events(Add, Noop, Ignored, Burst), + internalEvents: Machine.internalEvents(InternalAdd), initial: () => CounterStates.initial.Counter(new Counter({ count: 0 })) }).handle({ Counter: { @@ -233,8 +233,8 @@ describe("MachineTest runtime commands", () => { const states = Machine.defineStates({ Waiting, TimedOut }) const machine = Machine.make({ states: states.states, - events: [], - internalEvents: [Timeout], + events: Machine.events(), + internalEvents: Machine.internalEvents(Timeout), initial: () => states.initial.Waiting(new Waiting({})) }).handle({ Waiting: { @@ -713,8 +713,8 @@ describe("MachineTest causal runtime commands", () => { const states = Machine.defineStates({ Waiting, TimedOut }) const timerMachine = Machine.make({ states: states.states, - events: [], - internalEvents: [Timeout], + events: Machine.events(), + internalEvents: Machine.internalEvents(Timeout), initial: () => states.initial.Waiting(new Waiting({})) }).handle({ Waiting: { diff --git a/test/testing/Verification.test.ts b/test/testing/Verification.test.ts index b303d14..e6e17c6 100644 --- a/test/testing/Verification.test.ts +++ b/test/testing/Verification.test.ts @@ -29,7 +29,7 @@ const NavigationStates = Machine.defineStates({ const navigationMachine = Machine.make({ states: NavigationStates.states, - events: [Go], + events: Machine.events(Go), initial: () => NavigationStates.initial.off(new Off({})) }).handle({ off: { @@ -44,7 +44,7 @@ const navigationMachine = Machine.make({ const raisedNavigationMachine = Machine.make({ states: NavigationStates.states, - events: [Go], + events: Machine.events(Go), initial: () => NavigationStates.initial.off(new Off({})) }).handle({ off: { @@ -59,7 +59,7 @@ const CounterStates = Machine.defineStates({ counter: Counter }) const counterMachine = Machine.make({ states: CounterStates.states, - events: [Increment, Noop], + events: Machine.events(Increment, Noop), initial: () => CounterStates.initial.counter(new Counter({ count: 0 })) }).handle({ counter: { @@ -72,7 +72,7 @@ const counterMachine = Machine.make({ const reentryMachine = Machine.make({ states: NavigationStates.states, - events: [Restart], + events: Machine.events(Restart), initial: () => NavigationStates.initial.app( new App({}), @@ -106,7 +106,7 @@ const ParallelStates = Machine.defineStates({ const parallelMachine = Machine.make({ states: ParallelStates.states, - events: [], + events: Machine.events(), initial: () => ParallelStates.initial.dashboard( new Dashboard({}), @@ -148,7 +148,7 @@ const HistoryStates = Machine.defineStates({ const historyMachine = Machine.make({ states: HistoryStates.states, - events: [Leave], + events: Machine.events(Leave), initial: () => HistoryStates.initial.workspace( new Workspace({}), @@ -214,7 +214,7 @@ const structuralHistoryInitial = () => const structuralHistoryMachine = Machine.make({ states: StructuralHistoryStates.states, - events: [Leave], + events: Machine.events(Leave), initial: structuralHistoryInitial }).handle({ workspace: { @@ -239,7 +239,7 @@ const CompletionStates = Machine.defineStates({ const completionMachine = Machine.make({ states: CompletionStates.states, - events: [], + events: Machine.events(), initial: () => CompletionStates.initial.finished(new Finished({})) }).handle({ finished: { @@ -267,7 +267,7 @@ const DoneTransitionStates = Machine.defineStates({ const doneTransitionMachine = Machine.make({ states: DoneTransitionStates.states, - events: [], + events: Machine.events(), initial: () => DoneTransitionStates.initial.workflow( new Workflow({}), @@ -286,7 +286,7 @@ const doneTransitionMachine = Machine.make({ const nestedCompletionMachine = Machine.make({ states: DoneTransitionStates.states, - events: [], + events: Machine.events(), initial: () => DoneTransitionStates.initial.workflow( new Workflow({}), diff --git a/test/unstable/cluster/ClusterMachine.test.ts b/test/unstable/cluster/ClusterMachine.test.ts index d89236a..fa3c3fc 100644 --- a/test/unstable/cluster/ClusterMachine.test.ts +++ b/test/unstable/cluster/ClusterMachine.test.ts @@ -43,7 +43,7 @@ const CounterStates = Machine.defineStates({ const UnsupportedChildMachine = Machine.make({ states: { Count }, - events: [], + events: Machine.events(), initial: () => ({ path: "Count", value: new Count({ value: 0 }) }) }) const UnsupportedChild = Machine.child("unsupported", UnsupportedChildMachine) @@ -58,7 +58,7 @@ const makeCounter = (state: { Machine.make({ id: "Counter", states: CounterStates.states, - events: [Increment, Fail, Finish, RaiseFromAction, SpawnFromAction], + events: Machine.events(Increment, Fail, Finish, RaiseFromAction, SpawnFromAction), emits: [Changed], initial: () => CounterStates.initial.Count(new Count({ value: 0 })) }).handle({ @@ -561,7 +561,7 @@ describe("ClusterMachine", () => { const invoked = Machine.make({ id: "Invoked", states: states.states, - events: [Increment], + events: Machine.events(Increment), initial: () => states.initial.Count(new Count({ value: 0 })) }).handle({ Count: { diff --git a/test/unstable/reactivity/AtomMachine.test.ts b/test/unstable/reactivity/AtomMachine.test.ts index 5124df6..41dfc28 100644 --- a/test/unstable/reactivity/AtomMachine.test.ts +++ b/test/unstable/reactivity/AtomMachine.test.ts @@ -72,7 +72,7 @@ const waitForResult = ( const makeCounterMachine = () => Machine.make({ states: CounterStates.states, - events: [Finish], + events: Machine.events(Finish), initial: () => CounterStates.initial.Count(new Count({ value: 0 })) }).handle({ Count: { @@ -91,7 +91,7 @@ describe("AtomMachine", () => { const invokeStopped = yield* Deferred.make() const machine = Machine.make({ states: CounterStates.states, - events: [Finish], + events: Machine.events(Finish), initial: () => { initialCalls += 1 return CounterStates.initial.Count(new Count({ value: 0 })) @@ -149,7 +149,7 @@ describe("AtomMachine", () => { const Child = Machine.child("counter", childMachine) const parent = Machine.make({ states: { Count, ValueRead }, - events: [Finish, ReadValue], + events: Machine.events(Finish, ReadValue), initial: () => MachineInitial.Count(new Count({ value: 0 })) }).handle({ Count: { @@ -331,7 +331,7 @@ describe("AtomMachine", () => { }) const machine = Machine.make({ states: states.states, - events: [], + events: Machine.events(), initial: () => states.initial.Ready(new Ready({}), (ready) => ready @@ -464,7 +464,7 @@ describe("AtomMachine", () => { output: Schema.Number } }, - events: [Finish], + events: Machine.events(Finish), initial: () => MachineInitial.Count(new Count({ value: 1 })) }).handle({ Count: { diff --git a/typetest/machine/Activities.tst.ts b/typetest/machine/Activities.tst.ts index 55b1829..b8c701d 100644 --- a/typetest/machine/Activities.tst.ts +++ b/typetest/machine/Activities.tst.ts @@ -9,7 +9,7 @@ class TimedOut extends Schema.TaggedClass("TimedOut")("TimedOut", {}) const States = Machine.defineStates({ Loading, Dynamic }) const machine = Machine.make({ states: States.states, - events: [TimedOut], + events: Machine.events(TimedOut), initial: () => States.initial.Loading(new Loading({})) }).handle({ Loading: { diff --git a/typetest/machine/Annotations.tst.ts b/typetest/machine/Annotations.tst.ts index 9674fb2..dd5223d 100644 --- a/typetest/machine/Annotations.tst.ts +++ b/typetest/machine/Annotations.tst.ts @@ -38,7 +38,7 @@ const States = Machine.defineStates({ const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: () => States.initial.Workflow(new Workflow({}), (workflow) => workflow.Idle(new Idle({}))) }) diff --git a/typetest/machine/Choice.tst.ts b/typetest/machine/Choice.tst.ts index 452eae9..3c50d6e 100644 --- a/typetest/machine/Choice.tst.ts +++ b/typetest/machine/Choice.tst.ts @@ -33,7 +33,7 @@ describe("Machine choice pseudo-states", () => { it("exposes only choice context and requires implementation before planning", () => { const incomplete = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: () => States.initial.Flow(new Flow({ score: 80 }), (flow) => flow.Routing()) }) expect(Machine.planInitial).type.not.toBeCallableWith(incomplete) @@ -61,7 +61,7 @@ describe("Machine choice pseudo-states", () => { it("rejects Effects returned by choice resolvers", () => { const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: () => States.initial.Flow(new Flow({ score: 80 }), (flow) => flow.Routing()) }) expect(machine.handle).type.not.toBeCallableWith({ @@ -103,7 +103,7 @@ describe("Machine choice pseudo-states", () => { const base = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: () => States.initial.Flow(new Flow({ score: 80 }), (flow) => flow.Routing()) }) const invalidHandlers = [ @@ -126,7 +126,7 @@ describe("Machine choice pseudo-states", () => { it("rejects void and undeclared choice results", () => { const base = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: () => States.initial.Flow(new Flow({ score: 80 }), (flow) => flow.Routing()) }) expect(base.handle).type.not.toBeCallableWith({ diff --git a/typetest/machine/DeepHandlers.tst.ts b/typetest/machine/DeepHandlers.tst.ts index 7bc8ef7..23ee69a 100644 --- a/typetest/machine/DeepHandlers.tst.ts +++ b/typetest/machine/DeepHandlers.tst.ts @@ -160,7 +160,7 @@ const deepHistoryFallback = ( const makeDeepMachine = () => Machine.make({ states: DeepStates.states, - events: [Advance], + events: Machine.events(Advance), initial: (): never => { throw new Error("type-only") } @@ -427,7 +427,7 @@ describe("deep handler trees", () => { }) const machine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: (): never => { throw new Error("type-only") } diff --git a/typetest/machine/EventByTag.tst.ts b/typetest/machine/EventByTag.tst.ts index 02df7a0..1ac64dc 100644 --- a/typetest/machine/EventByTag.tst.ts +++ b/typetest/machine/EventByTag.tst.ts @@ -34,7 +34,7 @@ describe("Machine.EventByTag", () => { const states = Machine.defineStates({ Idle }) Machine.make({ states: states.states, - events: [FiniteUnion], + events: Machine.events(FiniteUnion), initial: () => states.initial.Idle(new Idle({})) }).handle({ Idle: { diff --git a/typetest/machine/EventConstructors.tst.ts b/typetest/machine/EventConstructors.tst.ts index 9589714..3eaf752 100644 --- a/typetest/machine/EventConstructors.tst.ts +++ b/typetest/machine/EventConstructors.tst.ts @@ -27,16 +27,15 @@ describe("Machine event constructor collections", () => { }) const states = Machine.defineStates({ Idle: {} }) + const events = Machine.events(PublicEvent, SetLabel, FiniteEvent) + const internalEvents = Machine.internalEvents(InternalEvent) const machine = Machine.make({ states: states.states, - events: [PublicEvent, SetLabel, FiniteEvent], - internalEvents: [InternalEvent], + events, + internalEvents, initial: () => states.initial.Idle.from() }) - const events = Machine.events(machine) - const internalEvents = Machine.internalEvents(machine) - it("derives public constructors and their schema make inputs", () => { expect(events.Increment({ by: 1 })).type.toBe< Machine.Machine.EventConstruction @@ -52,6 +51,10 @@ describe("Machine event constructor collections", () => { expect(events.SetLabel({})).type.toRaiseError() expect(events.Alpha({ _tag: "Beta", value: "alpha" })).type.toRaiseError() expect(events.Loaded).type.toRaiseError() + expect>().type.toBe< + typeof PublicEvent.Type | SetLabel | typeof FiniteEvent.Type + >() + expect(machine.events).type.toBe() }) it("keeps internal constructors separate from public constructors", () => { @@ -64,6 +67,37 @@ describe("Machine event constructor collections", () => { expect(internalEvents.Increment).type.toRaiseError() expect(internalEvents.Loaded()).type.toRaiseError() + expect(machine.internalEvents).type.toBe() + }) + + it("keeps public and internal protocol descriptors nominally separate", () => { + expect(Machine.make).type.not.toBeCallableWith({ + states: states.states, + events: internalEvents, + initial: () => states.initial.Idle.from() + }) + expect(Machine.make).type.not.toBeCallableWith({ + states: states.states, + events, + internalEvents: events, + initial: () => states.initial.Idle.from() + }) + + const Reset = Schema.TaggedStruct("Reset", {}) + expect(Machine.events).type.not.toBeCallableWith(Reset, PublicEvent) + }) + + it("keeps open discriminator schemas in the protocol without inventing constructor keys", () => { + const OpenEvent = Schema.Struct({ _tag: Schema.String, value: Schema.Number }) + const openEvents = Machine.events(OpenEvent) + const openMachine = Machine.make({ + states: states.states, + events: openEvents, + initial: () => states.initial.Idle.from() + }) + + expect(openEvents.Dynamic).type.toRaiseError() + expect>().type.toBe() }) it("accepts public constructions at machine delivery boundaries", () => { diff --git a/typetest/machine/History.tst.ts b/typetest/machine/History.tst.ts index bd9c668..8737ddb 100644 --- a/typetest/machine/History.tst.ts +++ b/typetest/machine/History.tst.ts @@ -162,7 +162,7 @@ describe("Machine history states", () => { it("exposes zero-argument history targets without value overrides", () => { const machine = Machine.make({ states: States.states, - events: [Resume], + events: Machine.events(Resume), initial: () => States.initial.support(new Support({})) }).handle({ support: { @@ -188,7 +188,7 @@ describe("Machine history states", () => { const machine = Machine.make({ states: States.states, - events: [Resume], + events: Machine.events(Resume), initial: () => States.initial.support(new Support({})) }).handle({ support: { @@ -242,7 +242,7 @@ describe("Machine history states", () => { it("rejects defaults outside the history parent and wrong initial child values", () => { const machine = Machine.make({ states: States.states, - events: [Resume], + events: Machine.events(Resume), initial: () => States.initial.support(new Support({})) }) @@ -285,7 +285,7 @@ describe("Machine history states", () => { const machine = Machine.make({ states: NestedStates.states, - events: [Resume], + events: Machine.events(Resume), initial: () => NestedStates.initial.Closed(new Closed({})) }) @@ -411,7 +411,7 @@ describe("Machine history states", () => { it("rejects Effects returned by nested history defaults", () => { const machine = Machine.make({ states: NestedStates.states, - events: [Resume], + events: Machine.events(Resume), initial: () => NestedStates.initial.Closed(new Closed({})) }) expect(machine.handle).type.not.toBeCallableWith({ @@ -432,7 +432,7 @@ describe("Machine history states", () => { it("tracks defaults and shallow initializers across successive handle calls", () => { const machine = Machine.make({ states: States.states, - events: [Resume], + events: Machine.events(Resume), initial: () => States.initial.support(new Support({})) }) const afterDefaults = machine.handle({ @@ -498,7 +498,7 @@ describe("Machine history states", () => { const machine = Machine.make({ states: DeepOnlyStates.states, - events: [Resume], + events: Machine.events(Resume), initial: () => DeepOnlyStates.initial.support(new Support({})) }).handle({ checkout: { @@ -549,7 +549,7 @@ describe("Machine history states", () => { const machine = Machine.make({ states: ParallelStates.states, - events: [Resume], + events: Machine.events(Resume), initial: () => ParallelStates.initial.support(new Support({})) }) const complete = machine.handle({ diff --git a/typetest/machine/Inspection.tst.ts b/typetest/machine/Inspection.tst.ts index c12c085..abd51f9 100644 --- a/typetest/machine/Inspection.tst.ts +++ b/typetest/machine/Inspection.tst.ts @@ -21,7 +21,7 @@ describe("Machine inspection", () => { const initial = States.initial.root(new Root({}), (root) => root.idle(new Idle({}))) const machine = Machine.make({ states: States.states, - events: [Reset], + events: Machine.events(Reset), initial: () => initial }).handle({ root: { @@ -112,7 +112,7 @@ describe("Machine inspection", () => { }) const choiceMachine = Machine.make({ states: ChoiceStates.states, - events: [], + events: Machine.events(), initial: () => ChoiceStates.initial.Flow(new Root({}), (flow) => flow.Routing()) }) const flow = Machine.stateNodes(choiceMachine).find((node) => node.type === "compound")! @@ -151,7 +151,7 @@ describe("Machine inspection", () => { const FlatStates = Machine.defineStates({ idle: Idle, running: Running }) const flat = Machine.make({ states: FlatStates.states, - events: [Reset], + events: Machine.events(Reset), initial: () => FlatStates.initial.idle(new Idle({})) }) const target = flat.makeTargetBuilder("idle") diff --git a/typetest/machine/Machine.tst.ts b/typetest/machine/Machine.tst.ts index 19362a0..701e1c2 100644 --- a/typetest/machine/Machine.tst.ts +++ b/typetest/machine/Machine.tst.ts @@ -195,7 +195,7 @@ describe("Machine", () => { >() }) - it("constructs exact public and internal event values from machine-owned schemas", () => { + it("constructs exact public and internal event instructions from protocol descriptors", () => { const Event = Schema.TaggedUnion({ Tick: { amount: Schema.Number }, Stop: {} @@ -203,30 +203,28 @@ describe("Machine", () => { class Internal extends Schema.TaggedClass("ConstructedInternal")("ConstructedInternal", { id: Schema.String }) {} - class Unknown extends Schema.TaggedClass("ConstructedUnknown")("ConstructedUnknown", {}) {} - class InvalidTick extends Schema.TaggedClass("InvalidTick")("Tick", { - amount: Schema.String - }) {} const State = Schema.TaggedStruct("ConstructedEventState", {}) const States = Machine.defineStates({ State }) - const machine = Machine.make({ + const Events = Machine.events(Event) + const InternalEvents = Machine.internalEvents(Internal) + Machine.make({ states: States.states, - events: [Event], - internalEvents: [Internal], + events: Events, + internalEvents: InternalEvents, initial: () => States.initial.State.from() }) - expect(Machine.event(machine, Event.cases.Tick, { amount: 1 })).type.toBe<{ - readonly _tag: "Tick" - readonly amount: number - }>() - expect(Machine.event(machine, Event.cases.Stop)).type.toBe<{ readonly _tag: "Stop" }>() - expect(Machine.event(machine, Internal, { id: "internal-1" })).type.toBe() - expect(Machine.event).type.not.toBeCallableWith(machine, Event.cases.Tick) - expect(Machine.event).type.not.toBeCallableWith(machine, Event.cases.Tick, { amount: "1" }) - expect(Machine.event).type.not.toBeCallableWith(machine, Internal, {}) - expect(Machine.event).type.not.toBeCallableWith(machine, Unknown, {}) - expect(Machine.event).type.not.toBeCallableWith(machine, InvalidTick, { amount: "1" }) + expect(Events.Tick({ amount: 1 })).type.toBe< + Machine.Machine.EventConstruction + >() + expect(Events.Stop()).type.toBe>() + expect(InternalEvents.ConstructedInternal({ id: "internal-1" })).type.toBe< + Machine.Machine.EventConstruction + >() + expect>().type.toBe() + expect(Events.Tick).type.not.toBeCallableWith() + expect(Events.Tick).type.not.toBeCallableWith({ amount: "1" }) + expect(InternalEvents.ConstructedInternal).type.not.toBeCallableWith({}) }) it("machine contexts expose type-safe parent state values", () => { @@ -393,7 +391,7 @@ describe("Machine", () => { it("make accepts defined states", () => { const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.down(new Down({})) }) @@ -403,7 +401,7 @@ describe("Machine", () => { it("make rejects raw decoded initial states", () => { expect(Machine.make).type.not.toBeCallableWith({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => new Down({}) }) }) @@ -411,7 +409,7 @@ describe("Machine", () => { it("encodes and decodes snapshots with typed effects", () => { const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.down(new Down({})) }) @@ -432,7 +430,7 @@ describe("Machine", () => { it("planInitial is synchronous at the transition boundary", () => { const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.down(new Down({})) }).handle({ down: { @@ -447,7 +445,7 @@ describe("Machine", () => { expect(Machine.make).type.not.toBeCallableWith({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => Effect.succeed(UpStates.initial.down(new Down({}))) }) expect(machine.handle).type.not.toBeCallableWith({ @@ -459,7 +457,7 @@ describe("Machine", () => { const worker = Machine.childAddress("worker") const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), emits: [SignInCompleted], initial: () => UpStates.initial.down(new Down({})) }).handle({ @@ -522,7 +520,7 @@ describe("Machine", () => { it("invoke handles one-shot outputs directly in the owning state", () => { const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.down(new Down({})) }) @@ -548,7 +546,7 @@ describe("Machine", () => { it("contextually types dynamic Effect sources on direct inline objects", () => { const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.down(new Down({})) }) @@ -573,7 +571,7 @@ describe("Machine", () => { const load = (userId: string) => Effect.fail(new LoadFailure()).pipe(Effect.as({ userId })) const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.down(new Down({})) }) @@ -602,7 +600,7 @@ describe("Machine", () => { } const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.down(new Down({})) }) @@ -701,8 +699,8 @@ describe("Machine", () => { it("separates public input events from the complete internal protocol", () => { const machine = Machine.make({ states: UpStates.states, - events: [SignIn], - internalEvents: [SignInCompleted], + events: Machine.events(SignIn), + internalEvents: Machine.internalEvents(SignInCompleted), initial: () => UpStates.initial.down(new Down({})) }).handle({ down: { @@ -735,23 +733,16 @@ describe("Machine", () => { UpStates.initial.down(new Down({})), new SignInCompleted({ userId: "user-1" }) ) + const publicEvents = Machine.events(SignIn) + const overlappingInternalEvents = Machine.internalEvents(SignIn) expect(Machine.make).type.not.toBeCallableWith({ states: UpStates.states, - events: [SignIn], - internalEvents: [SignIn], - initial: () => UpStates.initial.down(new Down({})) - }) - expect(Machine.make).type.not.toBeCallableWith({ - states: UpStates.states, - events: [SignIn, SignIn], - initial: () => UpStates.initial.down(new Down({})) - }) - expect(Machine.make).type.not.toBeCallableWith({ - states: UpStates.states, - events: [SignIn], - internalEvents: [SignInCompleted, SignInCompleted], + events: publicEvents, + internalEvents: overlappingInternalEvents, initial: () => UpStates.initial.down(new Down({})) }) + expect(Machine.events).type.not.toBeCallableWith(SignIn, SignIn) + expect(Machine.internalEvents).type.not.toBeCallableWith(SignInCompleted, SignInCompleted) }) it("invoke requires only the lifecycle handlers reachable from the source type", () => { @@ -759,8 +750,8 @@ describe("Machine", () => { const erasedFailure = failure as Effect.Effect const machine = Machine.make({ states: UpStates.states, - events: [SignIn], - internalEvents: [SignInCompleted], + events: Machine.events(SignIn), + internalEvents: Machine.internalEvents(SignInCompleted), initial: () => UpStates.initial.down(new Down({})) }) @@ -828,7 +819,7 @@ describe("Machine", () => { }) const child = Machine.make({ states: childStates.states, - events: [SignIn], + events: Machine.events(SignIn), emits: [SignIn], input: ChildInput, initial: () => childStates.initial.done(new Down({})) @@ -842,7 +833,7 @@ describe("Machine", () => { expect(Machine.sendTo).type.not.toBeCallableWith(Child, new Down({})) const parent = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.down(new Down({})) }) @@ -867,7 +858,7 @@ describe("Machine", () => { const incompatibleEmits = Machine.make({ states: childStates.states, - events: [SignIn], + events: Machine.events(SignIn), emits: [Down], input: ChildInput, initial: () => childStates.initial.done(new Down({})) @@ -909,7 +900,7 @@ describe("Machine", () => { it("types nested invocation output handlers against their owning state", () => { const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.down(new Down({})) }) @@ -972,7 +963,7 @@ describe("Machine", () => { it("start exposes machine infrastructure failure channels", () => { const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.down(new Down({})) }).handle({ down: { @@ -1010,7 +1001,7 @@ describe("Machine", () => { it("plan and getters require snapshots", () => { const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.down(new Down({})) }) @@ -1040,7 +1031,7 @@ describe("Machine", () => { it("handlers reject raw decoded state returns", () => { const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.down(new Down({})) }) @@ -1064,7 +1055,7 @@ describe("Machine", () => { it("handle accepts nested states through reserved states objects", () => { const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.down(new Down({})) }) @@ -1092,7 +1083,7 @@ describe("Machine", () => { it("handle accepts parent config and child config in the same object", () => { const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.down(new Down({})) }) @@ -1139,7 +1130,7 @@ describe("Machine", () => { it("onDone handlers receive typed state context without Effect requirements", () => { const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.up( new Up({ id: "up-1" }), @@ -1196,7 +1187,7 @@ describe("Machine", () => { it("handle rejects old property and callback APIs", () => { const machine = Machine.make({ states: UpStates.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => UpStates.initial.down(new Down({})) }) @@ -1216,7 +1207,7 @@ describe("Machine", () => { output: Schema.Void } }, - events: [SignIn], + events: Machine.events(SignIn), initial: () => Machine.defineStates({ down: { schema: Down, type: "final", output: Schema.Void } }).initial.down( new Down({}) @@ -1243,7 +1234,7 @@ describe("Machine", () => { const machine = Machine.make({ states: States.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => States.initial.signedIn(new SignedIn({ userId: "user-1" })) }).handle({ signedIn: { @@ -1274,7 +1265,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: States.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => States.initial.signedIn(new SignedIn({ userId: "user-1" })) }) type ForgedCompleteMachine = Machine.Machine< @@ -1336,7 +1327,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: States.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => States.initial.active(new Down({})) }).handle({ succeeded: { @@ -1348,7 +1339,7 @@ describe("Machine", () => { const activeOnly = Machine.make({ states: { active: Down }, - events: [SignIn], + events: Machine.events(SignIn), initial: () => Machine.defineStates({ active: Down }).initial.active(new Down({})) }) const activeRef = Machine.start(activeOnly) @@ -1374,7 +1365,7 @@ describe("Machine", () => { const machine = Machine.make({ states: States.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => States.initial.auth(new Auth({ userId: "user-1" }), (auth) => auth.signedOut(new SignedOut({}))) }) @@ -1410,7 +1401,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: States.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => States.initial.auth(new Auth({ userId: "user-1" }), (auth) => auth.signedOut(new SignedOut({}))) }) @@ -1449,7 +1440,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: States.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => States.initial.payment(new Payment({}), (payment) => payment.pending(new PendingPayment({}))) }) @@ -1520,7 +1511,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: States.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => States.initial.up( new Up({ id: "up-1" }), @@ -1595,7 +1586,7 @@ describe("Machine", () => { }) const machine = Machine.make({ states: States.states, - events: [SignIn], + events: Machine.events(SignIn), initial: () => States.initial.up( new Up({ id: "up-1" }), @@ -2295,7 +2286,7 @@ describe("Machine", () => { } } }, - events: [], + events: Machine.events(), initial: (): never => { throw new Error("unreachable") } diff --git a/typetest/machine/Readiness.tst.ts b/typetest/machine/Readiness.tst.ts index 22f9465..74341a9 100644 --- a/typetest/machine/Readiness.tst.ts +++ b/typetest/machine/Readiness.tst.ts @@ -25,7 +25,7 @@ const choiceStates = Machine.defineStates({ const choiceIncomplete = Machine.make({ states: choiceStates.states, - events: [Tick], + events: Machine.events(Tick), initial: () => choiceStates.initial.Ready(new Ready({})) }) const choiceSnapshot = choiceStates.initial.Ready(new Ready({})) @@ -44,7 +44,7 @@ const historyStates = Machine.defineStates({ const historyIncomplete = Machine.make({ states: historyStates.states, - events: [Tick], + events: Machine.events(Tick), initial: () => historyStates.initial.Ready(new Ready({})) }) const historySnapshot = historyStates.initial.Ready(new Ready({})) @@ -60,7 +60,7 @@ const outputStates = Machine.defineStates({ const outputIncomplete = Machine.make({ states: outputStates.states, - events: [Tick], + events: Machine.events(Tick), initial: () => outputStates.initial.Ready(new Ready({})) }) const outputSnapshot = outputStates.initial.Ready(new Ready({})) @@ -137,7 +137,7 @@ describe("executable machine readiness", () => { }) const complete = Machine.make({ states: completeStates.states, - events: [Tick], + events: Machine.events(Tick), initial: () => completeStates.initial.Ready(new Ready({})) }).handle({ Flow: { diff --git a/typetest/machine/Resume.tst.ts b/typetest/machine/Resume.tst.ts index 36cd535..9338bae 100644 --- a/typetest/machine/Resume.tst.ts +++ b/typetest/machine/Resume.tst.ts @@ -12,7 +12,7 @@ type Snapshot = Machine.Machine.Snapshot const machine = Machine.make({ states: States.states, - events: [Tick], + events: Machine.events(Tick), input: Schema.Struct({ seed: Schema.Number }), initial: (input) => States.initial.Idle(new Idle({ value: input.seed })) }).handle({ diff --git a/typetest/machine/SnapshotContext.tst.ts b/typetest/machine/SnapshotContext.tst.ts index 1822bf5..5306938 100644 --- a/typetest/machine/SnapshotContext.tst.ts +++ b/typetest/machine/SnapshotContext.tst.ts @@ -36,7 +36,7 @@ describe("Machine transition snapshot context", () => { it("infers the complete machine snapshot for event, always, and onDone handlers", () => { Machine.make({ states: States.states, - events: [Advance], + events: Machine.events(Advance), initial: () => States.initial.Root( new Root({}), @@ -92,7 +92,7 @@ describe("Machine transition snapshot context", () => { }) Machine.make({ states: choiceStates.states, - events: [], + events: Machine.events(), initial: () => choiceStates.initial.Flow(new Flow({}), (flow) => flow.Routing()) }).handle({ Flow: { diff --git a/typetest/machine/StateDefinition.tst.ts b/typetest/machine/StateDefinition.tst.ts index 2b0fd34..1ba5f9b 100644 --- a/typetest/machine/StateDefinition.tst.ts +++ b/typetest/machine/StateDefinition.tst.ts @@ -78,7 +78,7 @@ describe("exact state definitions", () => { states: { Idle: { schema: Idle, nestedUnknown: true } } } }, - events: [], + events: Machine.events(), initial: (): never => { throw new Error("unreachable") } diff --git a/typetest/machine/StructuralStates.tst.ts b/typetest/machine/StructuralStates.tst.ts index 3a98a95..cd29753 100644 --- a/typetest/machine/StructuralStates.tst.ts +++ b/typetest/machine/StructuralStates.tst.ts @@ -122,7 +122,7 @@ describe("structural active state types", () => { it("types structural handler contexts and targets without fake values", () => { Machine.make({ states: States.states, - events: [Select, Loaded, Play], + events: Machine.events(Select, Loaded, Play), initial: () => States.initial.player.from((player) => player diff --git a/typetest/testing/Coverage.tst.ts b/typetest/testing/Coverage.tst.ts index aa4c83c..6f3362f 100644 --- a/typetest/testing/Coverage.tst.ts +++ b/typetest/testing/Coverage.tst.ts @@ -13,7 +13,7 @@ describe("MachineTest coverage and observed graph", () => { const States = Machine.defineStates({ idle: Idle, done: Done }) const machine = Machine.make({ states: States.states, - events: [Start], + events: Machine.events(Start), initial: () => States.initial.idle(new Idle({})) }).handle({ idle: { diff --git a/typetest/testing/Exploration.tst.ts b/typetest/testing/Exploration.tst.ts index e592b3a..1c6e7c1 100644 --- a/typetest/testing/Exploration.tst.ts +++ b/typetest/testing/Exploration.tst.ts @@ -12,8 +12,8 @@ describe("MachineTest exploration", () => { const States = Machine.defineStates({ counter: Counter }) const machine = Machine.make({ states: States.states, - events: [Increment], - internalEvents: [Internal], + events: Machine.events(Increment), + internalEvents: Machine.internalEvents(Internal), input: Input, initial: ({ seed }) => States.initial.counter(new Counter({ count: seed })) }).handle({ @@ -65,7 +65,7 @@ describe("MachineTest exploration", () => { it("forbids input for machines without an input schema", () => { const noInput = Machine.make({ states: States.states, - events: [Increment], + events: Machine.events(Increment), initial: () => States.initial.counter(new Counter({ count: 0 })) }).handle({ counter: {} }) type Options = MachineTest.ExploreOptions diff --git a/typetest/testing/Invariant.tst.ts b/typetest/testing/Invariant.tst.ts index a1b0b66..a023a21 100644 --- a/typetest/testing/Invariant.tst.ts +++ b/typetest/testing/Invariant.tst.ts @@ -11,8 +11,8 @@ describe("MachineTest invariants", () => { const States = Machine.defineStates({ idle: Idle }) const machine = Machine.make({ states: States.states, - events: [Tick], - internalEvents: [Internal], + events: Machine.events(Tick), + internalEvents: Machine.internalEvents(Internal), initial: () => States.initial.idle(new Idle({ count: 0 })) }).handle({ idle: { diff --git a/typetest/testing/MachineTest.tst.ts b/typetest/testing/MachineTest.tst.ts index 4ccf762..8d3d37b 100644 --- a/typetest/testing/MachineTest.tst.ts +++ b/typetest/testing/MachineTest.tst.ts @@ -15,8 +15,8 @@ describe("MachineTest", () => { const States = Machine.defineStates({ idle: Idle }) const machine = Machine.make({ states: States.states, - events: [PublicEvent], - internalEvents: [InternalEvent], + events: Machine.events(PublicEvent), + internalEvents: Machine.internalEvents(InternalEvent), input: Input, initial: () => States.initial.idle(new Idle({})) }).handle({ @@ -50,7 +50,7 @@ describe("MachineTest", () => { it("omits input for machines without an input schema", () => { const noInput = Machine.make({ states: States.states, - events: [PublicEvent], + events: Machine.events(PublicEvent), initial: () => States.initial.idle(new Idle({})) }).handle({ idle: {} }) type Scenario = MachineTest.Scenario @@ -96,7 +96,7 @@ describe("MachineTest", () => { class InvokeRequirement extends Context.Service()("InvokeRequirement") {} const invokedMachine = Machine.make({ states: States.states, - events: [PublicEvent], + events: Machine.events(PublicEvent), initial: () => States.initial.idle(new Idle({})) }).handle({ idle: { diff --git a/typetest/testing/Probe.tst.ts b/typetest/testing/Probe.tst.ts index aaeb7f2..4141867 100644 --- a/typetest/testing/Probe.tst.ts +++ b/typetest/testing/Probe.tst.ts @@ -13,8 +13,8 @@ describe("MachineTest probe", () => { const states = Machine.defineStates({ State }) const machine = Machine.make({ states: states.states, - events: [PublicEvent], - internalEvents: [InternalEvent], + events: Machine.events(PublicEvent), + internalEvents: Machine.internalEvents(InternalEvent), initial: () => states.initial.State(new State({ count: 0 })) }).handle({ State: { diff --git a/typetest/testing/Verification.tst.ts b/typetest/testing/Verification.tst.ts index 4800001..d622755 100644 --- a/typetest/testing/Verification.tst.ts +++ b/typetest/testing/Verification.tst.ts @@ -10,7 +10,7 @@ describe("MachineTest.verify", () => { const States = Machine.defineStates({ idle: Idle }) const machine = Machine.make({ states: States.states, - events: [Tick], + events: Machine.events(Tick), initial: () => States.initial.idle(new Idle({})) }).handle({ idle: {} }) diff --git a/typetest/unstable/cluster/ClusterMachine.tst.ts b/typetest/unstable/cluster/ClusterMachine.tst.ts index e0172fa..c0279d0 100644 --- a/typetest/unstable/cluster/ClusterMachine.tst.ts +++ b/typetest/unstable/cluster/ClusterMachine.tst.ts @@ -45,7 +45,7 @@ describe("ClusterMachine", () => { const machine = Machine.make({ id: "Counter", states: states.states, - events: [Increment, Reset], + events: Machine.events(Increment, Reset), initial: () => states.initial.Count(new Count({ value: 0 })) }).handle({ Count: { @@ -65,8 +65,8 @@ describe("ClusterMachine", () => { const internalMachine = Machine.make({ states: states.states, - events: [Increment], - internalEvents: [Reset], + events: Machine.events(Increment), + internalEvents: Machine.internalEvents(Reset), initial: () => states.initial.Count(new Count({ value: 0 })) }) const internalBridge = ClusterMachine.make("InternalCounterEntity", internalMachine, { @@ -87,7 +87,7 @@ describe("ClusterMachine", () => { const inputMachine = Machine.make({ id: "InputCounter", states: states.states, - events: [Reset], + events: Machine.events(Reset), input: Input, initial: (input) => states.initial.Count(new Count({ value: input.value })) }) @@ -119,7 +119,7 @@ describe("ClusterMachine", () => { }) const incomplete = Machine.make({ states: outputStates.states, - events: [Reset], + events: Machine.events(Reset), initial: () => outputStates.initial.Done(new Done({ value: "done" })) }) @@ -158,7 +158,7 @@ describe("ClusterMachine", () => { const contextualStates = Machine.defineStates({ ContextualCount }) const contextualMachine = Machine.make({ states: contextualStates.states, - events: [Reset], + events: Machine.events(Reset), initial: () => contextualStates.initial.ContextualCount(new ContextualCount({ value: 0 })) }) const layer = ClusterMachine.make("ContextualCounter", contextualMachine, { version: "1" }).toLayer() diff --git a/typetest/unstable/reactivity/AtomMachine.tst.ts b/typetest/unstable/reactivity/AtomMachine.tst.ts index e3f0290..8625e97 100644 --- a/typetest/unstable/reactivity/AtomMachine.tst.ts +++ b/typetest/unstable/reactivity/AtomMachine.tst.ts @@ -109,7 +109,7 @@ const StructuralNestedStates = Machine.defineStates({ const makeMachine = () => Machine.make({ states: States.states, - events: [Tick], + events: Machine.events(Tick), initial: () => States.initial.Idle(new Idle({})) }).handle({ Idle: {} @@ -121,7 +121,7 @@ describe("AtomMachine", () => { const Child = Machine.child("child", childMachine) const parentMachine = Machine.make({ states: States.states, - events: [], + events: Machine.events(), initial: () => States.initial.Idle(new Idle({})) }).handle({ Idle: { @@ -276,8 +276,8 @@ describe("AtomMachine", () => { it("only exposes public input events through atom send boundaries", () => { const machine = Machine.make({ states: States.states, - events: [Tick], - internalEvents: [InternalTick], + events: Machine.events(Tick), + internalEvents: Machine.internalEvents(InternalTick), initial: () => States.initial.Idle(new Idle({})) }).handle({ Idle: { @@ -305,7 +305,7 @@ describe("AtomMachine", () => { }) const incomplete = Machine.make({ states: OutputStates.states, - events: [Tick], + events: Machine.events(Tick), initial: () => OutputStates.initial.Idle(new Idle({})) }) const runtime = Atom.runtime(Layer.empty)