diff --git a/.changeset/calm-selectors-settle.md b/.changeset/calm-selectors-settle.md new file mode 100644 index 0000000..ba80e01 --- /dev/null +++ b/.changeset/calm-selectors-settle.md @@ -0,0 +1,9 @@ +--- +"@typeonce/effect-machine": minor +--- + +Make definition-time topology instructions immutable values. Use `to.none`, declared `.initial` and history properties, and `to.local.with` without an empty call; state and choice destinations such as `to.full.Running()` remain callable. + +Author machine startup through the same target-first grammar: `initial: (to) => to.Flow.initial.resolve(...)`. The selector is captured once and its resolver remains lazy until initial planning. + +Remove `Machine.targetless` and the `{ target: Machine.targetless, resolve }` shorthand. Use `(to) => to.none` or `(to) => to.none.resolve(...)`; block-bodied targetless resolvers may omit an explicit `return undefined`. diff --git a/.changeset/fluent-transition-handlers.md b/.changeset/fluent-transition-handlers.md index 8c17267..6157d41 100644 --- a/.changeset/fluent-transition-handlers.md +++ b/.changeset/fluent-transition-handlers.md @@ -12,7 +12,7 @@ const handlers = { to.branches({ running: { target: to.full.Running() }, done: { target: to.full.Done() }, - unchanged: { target: to.none() } + unchanged: { target: to.none } }).resolve(({ event, select }) => event.cached ? select.done.from() : select.running.from()) } ``` diff --git a/README.md b/README.md index 2d9fff4..b58e889 100644 --- a/README.md +++ b/README.md @@ -66,10 +66,7 @@ const CounterDefinition = Machine.make({ id: "Counter", states: States.states, events: CounterEvent, - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Idle().resolve(({ target }) => target.from()) }) const Counter = CounterDefinition.handle({ @@ -166,10 +163,11 @@ const States = Machine.states({ } }) -initial: { - target: (to) => to.Form.initial(), - resolve: ({ target }) => target((form) => form.Editing.from()) -} +const definition = Machine.make({ + states: States.states, + events: Machine.events(), + initial: (to) => to.Form.initial.resolve(({ target }) => target((form) => form.Editing.from())) +}) ``` Schema-less states remain active, targetable, matchable, and visible through @@ -208,10 +206,7 @@ const definition = Machine.make({ events: CommandEvent, internalEvents: InternalEvent, emittedEvents: Emissions, - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Idle().resolve(({ target }) => target.from()) }) ``` @@ -323,10 +318,7 @@ const child = Machine.make({ states: ChildStates.states, events: ChildEvents, parentEvents: ParentEvents, - initial: { - target: (to) => to.Working(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Working().resolve(({ target }) => target.from()) }).handle({ Working: { on: { @@ -375,16 +367,22 @@ paths. `parent` always means the owning machine target. Every required transition handler selects a target from its inline `to` builder. A bare selection uses the target schema's default construction; call `.resolve(...)` when construction depends on handler context. An absent handler -ignores the trigger; `to.none()` handles +ignores the trigger; `to.none` handles it and retains queued commands, raised events, and emitted events without selecting a destination. Concrete destinations stay narrowed inside their resolver, and `to.branches({...})` gives the resolver only the declared named `select` builders. Builders describe the next logical configuration. Shared states exit and enter only when paths change; call `.reenter()` for resolver-free reentry or pass `{ reenter: true }` -to `.resolve(...)` when the source must restart. With `to.none()`, reentry +to `.resolve(...)` when the source must restart. With `to.none`, reentry restarts the source while retaining its configuration. +Topology-only definition instructions are values: `to.none`, declared +`.initial` and history selections, and `to.local.with`. Concrete state and +choice destinations remain calls such as `to.full.Running()`. Runtime named +branch builders remain callable, including `select.unchanged()`, because their +result carries the selected branch evidence. + Use `declinable: true` when a resolver may decide that its transition is not enabled. Only that resolver receives `decline()`, and its return type expands to accept the opaque declined result: @@ -477,22 +475,19 @@ macrostep commits: invoke: Machine.invoke({ id: "channel", stream: () => channelMessages, - onElement: { - target: Machine.targetless, - resolve: ({ element }, enqueue) => { + onElement: (to) => + to.none.resolve(({ element }, enqueue) => { enqueue.raise(Events.MessageReceived({ message: element })) - } - }, - onDone: { target: Machine.targetless }, + }), + onDone: (to) => to.none, onFailure: (to) => to.full.Failed().resolve(({ error, target }) => target.from({ error })) }) ``` -`target: Machine.targetless` is the direct shorthand for a non-reentering -transition that keeps the current configuration. Its optional `resolve` -callback may enqueue commands and must return `undefined`. Use -the same fluent `to` builder to select a target for transitions that change -state or reenter. +`to.none` is the targetless transition value. Return it directly to keep the +current configuration, or call `.resolve(...)` when the transition only needs +to enqueue commands. A block resolver may omit its return because it is +contextually typed to return `undefined`. Inside `.handle(...)`, `Machine.invoke(...)` receives the owning machine's public input and `parentEvents` protocols contextually. Its source and lifecycle @@ -511,14 +506,13 @@ const machine = Machine.make({ id: "notify-parent", effect: () => saveDocument, onDone: (to) => - to.none().resolve(({ parent, self }, enqueue) => { + to.none.resolve(({ parent, self }, enqueue) => { enqueue.sendTo(self, Commands.Save()) if (parent !== undefined) { enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" })) } - return undefined }), - onFailure: (to) => to.none() + onFailure: (to) => to.none }) } }) diff --git a/docs/agent-guide.md b/docs/agent-guide.md index c0f7b6d..2c2812e 100644 --- a/docs/agent-guide.md +++ b/docs/agent-guide.md @@ -231,10 +231,7 @@ const States = Machine.states({ } }) -initial: { - target: (to) => to.Form.initial(), - resolve: ({ target }) => target((form) => form.Editing.from()) -} +initial: (to) => to.Form.initial.resolve(({ target }) => target((form) => form.Editing.from())) ``` Schema-less states have the same control semantics as schema-backed states: @@ -245,7 +242,7 @@ in snapshots. They do not have a state value: Idle: { on: { Start: (to) => - to.full.Form.initial().resolve(({ state, target }) => { + to.full.Form.initial.resolve(({ state, target }) => { // state: undefined return target.from((form) => form.Editing.from()) }) @@ -329,10 +326,7 @@ const States = Machine.states({ const machine = Machine.make({ states: States.states, events: Machine.events(), - initial: { - target: (to) => to.Done(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Done().resolve(({ target }) => target.from()) }).handle({ Done: { output: () => "done" @@ -349,9 +343,12 @@ with `.initial`. This is available on top-level state methods under `target.branch`; atomic and final state methods do not expose it: ```ts -Open: (to) => to.full.opened.initial().resolve(({ target }) => target.from({ teamId: "team-1" })) +Open: (to) => to.full.opened.initial.resolve(({ target }) => target.from({ teamId: "team-1" })) ``` +The definition-time `.initial` property is a topology value. The exact +resolver `target` is still a callable runtime builder. + The selected state's own value is passed directly to `initial(value)` or constructed inside planning with `initial.from(input)`. A structural selected state uses `initial()`. @@ -426,9 +423,12 @@ checkout: { Target it without a value: ```ts -Resume: (to) => to.history.checkout.exact().resolve(({ target }) => target()) +Resume: (to) => to.history.checkout.exact.resolve(({ target }) => target()) ``` +Each declared history leaf is a topology value; the resolver's selected +history builder remains callable to construct restoration evidence. + Deep history restores the complete remembered subtree and its decoded values. Shallow history restores only parent and direct-child values. If the remembered child is compound, its configured initial child needs a freshly constructed @@ -478,6 +478,22 @@ prior effects, machine instances, and timers are not rewound. | `target.full` | The destination may be under any top-level root | Nothing is inferred for a newly selected root; build its complete active snapshot | | `target.history` | The destination is a declared history pseudo-state | Its parent's remembered configuration, or a source-independent complete default containing that owner before the first capture | +Definition-time instructions that only identify topology are values: +`to.none`, `to.full.Flow.initial`, `to.history.Flow.recent`, and +`to.local.with`. State and choice destinations remain calls, such as +`to.full.Running()` and `to.local.Routing()`, because those calls select the +node. Resolver-time builders also remain callable because they construct and, +for named branches, brand runtime evidence such as `select.unchanged()`. + +Use `to.local.with` when a descendant transition updates the nearest +schema-backed compound value while retaining that same compound scope: + +```ts +Play: (to) => + to.local.with.resolve(({ containingState, target }) => + target.from({ ...containingState, playing: true }, (flow) => flow.Playing.from())) +``` + Entering an inactive parallel state through `target.local` or `target.branch` requires a complete callback with one selection per region. A parallel state that is already active remains partially addressable through `target.branch`; @@ -498,7 +514,7 @@ When no resolver is needed, use the selected target directly and append ```ts Finish: (to) => to.full.Done() -Restart: (to) => to.none().reenter() +Restart: (to) => to.none.reenter() ``` Do not use `target.full` merely because it is easiest to discover. Prefer the @@ -578,7 +594,7 @@ microstep, before any selected transition is applied: BufferReady: (to) => to.branches({ online: { target: to.local.Playing() }, - unchanged: { target: to.none() } + unchanged: { target: to.none } }).resolve(({ snapshot, select }) => States.matches(snapshot, "Player.Network.Online") ? select.online.from() @@ -637,7 +653,7 @@ synchronously: Submit: (to) => to.branches({ valid: { target: to.local.Saving() }, - invalid: { target: to.none() } + invalid: { target: to.none } }).resolve(({ state, select }) => state.valid ? select.valid.from({ draft: state.draft }) : select.invalid() @@ -649,7 +665,7 @@ its `resolve` method. A branching transition calls `to.branches` with every possible target, then uses ordinary TypeScript control flow in `resolve` to return one typed `select` builder. Branch keys are stable testing and inspection identities; an optional `title` controls presentation and otherwise defaults to the key. -Selecting a branch whose target is `to.none()` handles the transition without a +Selecting a branch whose target is `to.none` handles the transition without a destination while retaining queued commands, raised events, and emitted events. Set `declinable: true` only when the resolver may decide that its transition is @@ -660,7 +676,7 @@ permits its opaque result: Submit: (to) => to.branches({ accepted: { target: to.local.Saving() }, - consumed: { target: to.none() } + consumed: { target: to.none } }).resolve(({ event, select, decline }) => { if (!belongsToThisState(event)) return decline() return event.consume ? select.consumed() : select.accepted.from() @@ -670,7 +686,7 @@ Submit: (to) => Declining discards that resolver's enqueue buffer and resumes hierarchical event or eventless selection at the next eligible ancestor. If no candidate accepts, the trigger is unhandled. This is deliberately different from -`to.none()`, which consumes the trigger. `decline()` is absent and its result is +`to.none`, which consumes the trigger. `decline()` is absent and its result is rejected unless the literal flag is present. Choice and initial routing remain total and cannot decline. Static inspection exposes the distinction through `TransitionDefinition.acceptance` without executing resolver code. Completion @@ -683,7 +699,7 @@ array-index and symbol keys are rejected. Treat the string key as semantic: reordering named properties may change their display index, but visualizers, coverage, and trace verification identify each branch by its key. -`reenter: true` remains meaningful with `to.none()`: the source exits and +`reenter: true` remains meaningful with `to.none`: the source exits and enters again while its logical configuration is retained. Closed statechart and machine operations use `enqueue`: @@ -788,11 +804,10 @@ const child = Machine.make({ Working: { on: { Finish: (to) => - to.none().resolve(({ parent }, enqueue) => { + to.none.resolve(({ parent }, enqueue) => { if (parent !== undefined) { enqueue.sendTo(parent, ParentEvents.ChildFinished()) } - return undefined }) } } @@ -868,10 +883,7 @@ const definition = Machine.make({ states: States.states, events: Events, internalEvents: InternalEvents, - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Idle().resolve(({ target }) => target.from()) }) ``` @@ -952,13 +964,11 @@ handles normal Stream completion and `onFailure` handles the typed Stream error: invoke: Machine.invoke({ id: "broadcast-channel", stream: () => messages, - onElement: { - target: Machine.targetless, - resolve: ({ element }, enqueue) => { + onElement: (to) => + to.none.resolve(({ element }, enqueue) => { enqueue.raise(Events.MessageReceived({ message: element })) - } - }, - onDone: { target: Machine.targetless }, + }), + onDone: (to) => to.none, onFailure: (to) => to.full.Disconnected().resolve(({ error, target }) => target.from({ error })) }) ``` @@ -968,10 +978,9 @@ after the selected parent macrostep commits. Exiting or reentering the owner interrupts the Stream and runs its finalizers. A later entry starts a fresh Stream. Stream defects and self-interruption fail the owning machine. -The direct `{ target: Machine.targetless, resolve }` shorthand is available -when a transition only enqueues commands. It is non-reentering and the resolver -must return `undefined`. Use the same fluent `to` selector for full state -selection, named branches, and reentry. +Use `to.none` when a transition keeps the current configuration. Call +`to.none.resolve(...)` when it also enqueues commands; a block resolver may +omit its return because it is contextually typed to return `undefined`. When a source function reads `state`, `containingState`, `ancestors`, or the entry `event`, `Machine.invoke` infers that owner context and the returned Effect's output, @@ -1002,14 +1011,13 @@ const machine = Machine.make({ id: "notify-parent", effect: () => saveDocument, onDone: (to) => - to.none().resolve(({ parent, self }, enqueue) => { + to.none.resolve(({ parent, self }, enqueue) => { enqueue.sendTo(self, Commands.Save()) if (parent !== undefined) { enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" })) } - return undefined }), - onFailure: (to) => to.none() + onFailure: (to) => to.none }) } }) @@ -1373,10 +1381,7 @@ reference model when correctness of the expected behavior matters. Select the initial root separately from constructing its value: ```ts -initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target.from() -} +initial: (to) => to.Idle().resolve(({ target }) => target.from()) ``` ### Invoked child expects events not accepted by the parent diff --git a/examples/platformer/src/machine.ts b/examples/platformer/src/machine.ts index 6f3a22b..9b81daf 100644 --- a/examples/platformer/src/machine.ts +++ b/examples/platformer/src/machine.ts @@ -120,9 +120,8 @@ export const CharacterMachine = Machine.make({ states: CharacterStates.states, events: CharacterEvents, internalEvents: InternalEvents, - initial: { - target: (to) => to.Character.initial(), - resolve: ({ target }) => + initial: (to) => + to.Character.initial.resolve(({ target }) => target.from((character) => character .locomotion.from((locomotion) => @@ -131,7 +130,7 @@ export const CharacterMachine = Machine.make({ .facing.from((facing) => facing.Right.from()) .contact.from((contact) => contact.NoWall.from()) ) - } + ) }).handle({ Character: { on: { @@ -206,7 +205,7 @@ export const CharacterMachine = Machine.make({ Move: (to) => to.branches({ moving: { target: to.local.Running() }, - unchanged: { target: to.none() } + unchanged: { target: to.none } }).resolve(({ event, select }) => event.axis === 0 ? select.unchanged() @@ -221,7 +220,7 @@ export const CharacterMachine = Machine.make({ Move: (to) => to.branches({ stopped: { target: to.local.Standing() }, - unchanged: { target: to.none() } + unchanged: { target: to.none } }).resolve(({ event, select }) => event.axis === 0 ? select.stopped.from() @@ -249,7 +248,7 @@ export const CharacterMachine = Machine.make({ id: "landing-settle", after: "140 millis", onDone: (to) => - to.none().resolve((_, enqueue) => { + to.none.resolve((_, enqueue) => { enqueue.raise(InternalEvents.LandingSettled()) return undefined }) @@ -276,7 +275,7 @@ export const CharacterMachine = Machine.make({ Airborne: { on: { JumpPressed: (to) => - to.none().resolve(({ event }, enqueue) => { + to.none.resolve(({ event }, enqueue) => { const push = awayFrom(event.wall) enqueue.raise( push === 0 @@ -366,7 +365,7 @@ export const CharacterMachine = Machine.make({ }, Paused: { on: { - Resume: (to) => to.history.Character.locomotion.Playing.resume().resolve(({ target }) => target()) + Resume: (to) => to.history.Character.locomotion.Playing.resume.resolve(({ target }) => target()) } } } @@ -376,11 +375,11 @@ export const CharacterMachine = Machine.make({ Left: { on: { Move: (to) => - to.branches({ right: { target: to.local.Right() }, unchanged: { target: to.none() } }).resolve(( + to.branches({ right: { target: to.local.Right() }, unchanged: { target: to.none } }).resolve(( { event, select } ) => event.axis === 1 ? select.right.from() : select.unchanged()), WallJump: (to) => - to.branches({ right: { target: to.local.Right() }, unchanged: { target: to.none() } }).resolve(( + to.branches({ right: { target: to.local.Right() }, unchanged: { target: to.none } }).resolve(( { event, select } ) => event.push === 1 ? select.right.from() : select.unchanged()) } @@ -388,11 +387,11 @@ export const CharacterMachine = Machine.make({ Right: { on: { Move: (to) => - to.branches({ left: { target: to.local.Left() }, unchanged: { target: to.none() } }).resolve(( + to.branches({ left: { target: to.local.Left() }, unchanged: { target: to.none } }).resolve(( { event, select } ) => event.axis === -1 ? select.left.from() : select.unchanged()), WallJump: (to) => - to.branches({ left: { target: to.local.Left() }, unchanged: { target: to.none() } }).resolve(( + to.branches({ left: { target: to.local.Left() }, unchanged: { target: to.none } }).resolve(( { event, select } ) => event.push === -1 ? select.left.from() : select.unchanged()) } diff --git a/examples/playground/src/examples/media-player/definition.ts b/examples/playground/src/examples/media-player/definition.ts index 658c0fa..ed8faeb 100644 --- a/examples/playground/src/examples/media-player/definition.ts +++ b/examples/playground/src/examples/media-player/definition.ts @@ -8,9 +8,8 @@ export const MediaPlayerDefinition = Machine.make({ states: MediaPlayerStates.states, events: MediaPlayerEvents, internalEvents: MediaPlayerInternalEvents, - initial: { - target: (to) => to.Player.initial(), - resolve: ({ target }) => + initial: (to) => + to.Player.initial.resolve(({ target }) => target.from((player) => player .transport.from((transport) => transport.Empty.from()) @@ -21,5 +20,5 @@ export const MediaPlayerDefinition = Machine.make({ }) ) ) - } + ) }) diff --git a/examples/playground/src/examples/media-player/machine.ts b/examples/playground/src/examples/media-player/machine.ts index 299bba4..237300d 100644 --- a/examples/playground/src/examples/media-player/machine.ts +++ b/examples/playground/src/examples/media-player/machine.ts @@ -27,12 +27,12 @@ export const MediaPlayerMachine = MediaPlayerDefinition.handle({ id: "load-audio", effect: ({ state }) => loadAudio(state.url), onDone: (to) => - to.none().resolve((_, enqueue) => { + to.none.resolve((_, enqueue) => { enqueue.raise(MediaPlayerInternalEvents.LoadSucceeded()) return undefined }), onFailure: (to) => - to.none().resolve(({ error }, enqueue) => { + to.none.resolve(({ error }, enqueue) => { enqueue.raise(MediaPlayerInternalEvents.OperationFailed({ message: error.message })) return undefined }) @@ -49,9 +49,9 @@ export const MediaPlayerMachine = MediaPlayerDefinition.handle({ invoke: Machine.invoke({ id: "pause-audio", effect: () => pauseAudio, - onDone: { target: Machine.targetless }, + onDone: (to) => to.none, onFailure: (to) => - to.none().resolve(({ error }, enqueue) => { + to.none.resolve(({ error }, enqueue) => { enqueue.raise(MediaPlayerInternalEvents.OperationFailed({ message: error.message })) return undefined }) @@ -75,9 +75,9 @@ export const MediaPlayerMachine = MediaPlayerDefinition.handle({ Machine.invoke({ id: "play-audio", effect: () => playAudio, - onDone: { target: Machine.targetless }, + onDone: (to) => to.none, onFailure: (to) => - to.none().resolve(({ error }, enqueue) => { + to.none.resolve(({ error }, enqueue) => { enqueue.raise(MediaPlayerInternalEvents.OperationFailed({ message: error.message })) return undefined }) @@ -85,19 +85,15 @@ export const MediaPlayerMachine = MediaPlayerDefinition.handle({ Machine.invoke({ id: "analyze-audio", stream: () => analyzeAudio, - onElement: { - target: Machine.targetless, - resolve: ({ element }, enqueue) => { + onElement: (to) => + to.none.resolve(({ element }, enqueue) => { enqueue.raise(MediaPlayerInternalEvents.LoudnessMeasured(element)) - } - }, - onDone: { target: Machine.targetless }, - onFailure: { - target: Machine.targetless, - resolve: ({ error }, enqueue) => { + }), + onDone: (to) => to.none, + onFailure: (to) => + to.none.resolve(({ error }, enqueue) => { enqueue.raise(MediaPlayerInternalEvents.OperationFailed({ message: error.message })) - } - } + }) }) ], on: { @@ -170,12 +166,12 @@ export const MediaPlayerMachine = MediaPlayerDefinition.handle({ id: "restart-audio", effect: () => restartAudio, onDone: (to) => - to.none().resolve((_, enqueue) => { + to.none.resolve((_, enqueue) => { enqueue.raise(MediaPlayerInternalEvents.RestartSucceeded()) return undefined }), onFailure: (to) => - to.none().resolve(({ error }, enqueue) => { + to.none.resolve(({ error }, enqueue) => { enqueue.raise(MediaPlayerInternalEvents.OperationFailed({ message: error.message })) return undefined }) @@ -211,7 +207,7 @@ export const MediaPlayerMachine = MediaPlayerDefinition.handle({ const mediaPlayer = yield* MediaPlayer yield* mediaPlayer.reportError(state.message) }), - onDone: { target: Machine.targetless } + onDone: (to) => to.none }) } } @@ -223,7 +219,7 @@ export const MediaPlayerMachine = MediaPlayerDefinition.handle({ invoke: Machine.invoke({ id: "apply-audio-settings", effect: ({ state }) => applyAudioSettings(state, false), - onDone: { target: Machine.targetless } + onDone: (to) => to.none }), on: { VolumeChanged: (to) => @@ -254,7 +250,7 @@ export const MediaPlayerMachine = MediaPlayerDefinition.handle({ invoke: Machine.invoke({ id: "apply-audio-settings", effect: ({ state }) => applyAudioSettings(state, true), - onDone: { target: Machine.targetless } + onDone: (to) => to.none }), on: { VolumeChanged: (to) => diff --git a/examples/playground/src/examples/microwave/machine.ts b/examples/playground/src/examples/microwave/machine.ts index 5ed19d3..cf68aff 100644 --- a/examples/playground/src/examples/microwave/machine.ts +++ b/examples/playground/src/examples/microwave/machine.ts @@ -39,15 +39,14 @@ export const MicrowaveMachine = Machine.make({ id: "Microwave", states: MicrowaveStates.states, events: MicrowaveEvents, - initial: { - target: (to) => to.Oven.initial(), - resolve: ({ target }) => + initial: (to) => + to.Oven.initial.resolve(({ target }) => target.from((oven) => oven .engine.from((engine) => engine.Idle.from()) .door.from((door) => door.Closed.from()) ) - } + ) }).handle({ Oven: { states: { @@ -58,7 +57,7 @@ export const MicrowaveMachine = Machine.make({ PowerPressed: (to) => to.branches({ doorClosed: { title: "Door closed", target: to.local.Cooking() }, - unchanged: { target: to.none() } + unchanged: { target: to.none } }).resolve(({ snapshot, select }) => MicrowaveStates.matches(snapshot, "Oven.door.Closed") ? select.doorClosed.from({ elapsedSeconds: 0 }) diff --git a/examples/playground/src/examples/traffic-light/machine.ts b/examples/playground/src/examples/traffic-light/machine.ts index 83b9702..e7af484 100644 --- a/examples/playground/src/examples/traffic-light/machine.ts +++ b/examples/playground/src/examples/traffic-light/machine.ts @@ -25,10 +25,7 @@ export const TrafficLightMachine = Machine.make({ id: "TrafficLight", states: TrafficLightStates.states, events: TrafficLightEvents, - initial: { - target: (to) => to.Red(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Red().resolve(({ target }) => target.from()) }).handle({ Red: { invoke: Machine.invoke({ diff --git a/examples/playground/src/examples/turnstile/machine.ts b/examples/playground/src/examples/turnstile/machine.ts index f79b968..c37e483 100644 --- a/examples/playground/src/examples/turnstile/machine.ts +++ b/examples/playground/src/examples/turnstile/machine.ts @@ -17,10 +17,7 @@ export const TurnstileMachine = Machine.make({ id: "Turnstile", states: TurnstileStates.states, events: TurnstileEvents, - initial: { - target: (to) => to.Locked(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Locked().resolve(({ target }) => target.from()) }).handle({ Locked: { on: { diff --git a/examples/playground/src/examples/worker-tabs/machine.ts b/examples/playground/src/examples/worker-tabs/machine.ts index 01b0c34..b5b8dcd 100644 --- a/examples/playground/src/examples/worker-tabs/machine.ts +++ b/examples/playground/src/examples/worker-tabs/machine.ts @@ -28,10 +28,7 @@ export const SharedMachine = Machine.make({ id: "WorkerHostedMachine", states: SharedMachineStates.states, events: SharedMachineEvents, - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target.from({ count: 0 }) - } + initial: (to) => to.Idle().resolve(({ target }) => target.from({ count: 0 })) }).handle({ Idle: { on: { diff --git a/examples/pokemon/src/machine.ts b/examples/pokemon/src/machine.ts index 7535ff7..ef910e3 100644 --- a/examples/pokemon/src/machine.ts +++ b/examples/pokemon/src/machine.ts @@ -18,10 +18,7 @@ export const ReplaceChild = Machine.child("replace", ReplaceMachine) const machine = Machine.make({ states: States.states, events: TeamEvents, - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Loading().resolve(({ target }) => target.from()) }).handle({ Loading: { invoke: Machine.invoke({ @@ -39,7 +36,7 @@ const machine = Machine.make({ invoke: [ Machine.invoke({ child: SelectionChild, - onDone: { target: Machine.targetless }, + onDone: (to) => to.none, onFailure: (to) => to.full.Failed().resolve(({ target }) => target.from()) }), Machine.invoke({ diff --git a/examples/pokemon/src/machines/replace.ts b/examples/pokemon/src/machines/replace.ts index 5658e12..3745caa 100644 --- a/examples/pokemon/src/machines/replace.ts +++ b/examples/pokemon/src/machines/replace.ts @@ -36,10 +36,7 @@ export const ReplaceMachine = Machine.make({ events: ReplaceEvents, internalEvents: ReplaceInternalEvents, parentEvents: TeamEvents, - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Idle().resolve(({ target }) => target.from()) }).handle({ Idle: { on: { @@ -51,9 +48,8 @@ export const ReplaceMachine = Machine.make({ id: "replaceWithRandom", effect: () => replaceWithRandom, onDone: (to) => - to.none().resolve(({ output }, enqueue) => { + to.none.resolve(({ output }, enqueue) => { enqueue.raise(ReplaceInternalEvents.Replaced({ pokemon: output.pokemon })) - return undefined }), onFailure: (to) => to.full.Idle().resolve(({ target }) => target.from()) }), diff --git a/examples/pokemon/src/machines/selection.ts b/examples/pokemon/src/machines/selection.ts index d520fb5..dba6656 100644 --- a/examples/pokemon/src/machines/selection.ts +++ b/examples/pokemon/src/machines/selection.ts @@ -73,22 +73,21 @@ export const SelectionMachine = Machine.make({ states: SelectionStates.states, events: SelectionEvents, parentEvents: TeamEvents, - initial: { - target: (to) => to.form.initial(), - resolve: ({ target }) => + initial: (to) => + to.form.initial.resolve(({ target }) => target.from((form) => form .search.from({ searchText: "" }, (search) => search.NoPokemon.from()) .selection.from((selection) => selection.Unselected.from()) ) - } + ) }).handle({ form: { states: { search: { on: { UpdateSearchText: (to) => - to.local.with().resolve( + to.local.with.resolve( ({ event, target }) => target.from({ searchText: event.value }, (search) => search.Searching.from()), { reenter: true } ) @@ -114,9 +113,8 @@ export const SelectionMachine = Machine.make({ id: "search", effect: ({ ancestors }) => searchPokemon(ancestors["form.search"].searchText), onDone: (to) => - to.none().resolve(({ output }, enqueue) => { + to.none.resolve(({ output }, enqueue) => { enqueue.raise(output) - return undefined }), onFailure: (to) => to.local.NoPokemon().resolve(({ target }) => target.from()) }), diff --git a/perf/runtime/counter.mjs b/perf/runtime/counter.mjs index 384a7d0..08cdb19 100644 --- a/perf/runtime/counter.mjs +++ b/perf/runtime/counter.mjs @@ -140,7 +140,7 @@ const hierarchicalCounterMachine = Machine.make({ states: HierarchicalStates.states, events: benchmarkApi.events(HierarchicalEvent.cases.Increment, HierarchicalEvent.cases.Finish), initial: benchmarkApi.initial({ - target: (to) => to.Active.initial(), + target: (to) => to.Active.initial, resolve: ({ target }) => target.from((active) => active.Count.from({ value: 0 })) }, () => HierarchicalStates.initial.Active.from( @@ -196,7 +196,7 @@ const parallelCounterMachine = Machine.make({ states: ParallelStates.states, events: benchmarkApi.events(HierarchicalEvent.cases.IncrementLeft, HierarchicalEvent.cases.IncrementRight, HierarchicalEvent.cases.Finish), initial: benchmarkApi.initial({ - target: (to) => to.Active.initial(), + target: (to) => to.Active.initial, resolve: ({ target }) => target.from((active) => active diff --git a/perf/runtime/effect-machine-compatibility.mjs b/perf/runtime/effect-machine-compatibility.mjs index 4225fb3..7452a5f 100644 --- a/perf/runtime/effect-machine-compatibility.mjs +++ b/perf/runtime/effect-machine-compatibility.mjs @@ -9,11 +9,13 @@ export const makeEffectMachineBenchmarkApi = (Machine) => { // The legacy process constructor takes `(initial, transition)`. The static // definition constructor is deliberately unary and returns its config. const hasStaticTransitions = typeof Machine.transition === "function" && Machine.transition.length === 1 - const hasFluentTransitions = !hasStaticTransitions && Machine.targetless?.["~effect/Machine/TargetlessSelector"] === true + const hasFluentTransitions = !hasStaticTransitions && typeof Machine.invoke === "function" + const hasValueSelectors = hasFluentTransitions && Machine.targetless === undefined const targetless = ({ target }) => typeof target.none === "function" ? target.none() : undefined + const selectInstruction = (selection) => typeof selection === "function" ? selection() : selection const fluentTransition = (definition) => (to) => { - const selection = definition.target(to) + const selection = selectInstruction(definition.target(to)) if (definition.resolve !== undefined) { return selection.resolve(definition.resolve, { ...(definition.reenter === true ? { reenter: true } : {}), @@ -23,19 +25,40 @@ export const makeEffectMachineBenchmarkApi = (Machine) => { return definition.reenter === true ? selection.reenter() : selection } + const fluentInitial = (definition) => (to) => { + const selection = selectInstruction(definition.target(to)) + return definition.resolve === undefined ? selection : selection.resolve(definition.resolve) + } + + const objectInitial = (definition) => ({ + ...definition, + target: (to) => selectInstruction(definition.target(to)) + }) + + const objectTransition = (definition) => ({ + ...definition, + target: (to) => selectInstruction(definition.target(to)) + }) + return { states: (definitions) => typeof Machine.states === "function" ? Machine.states(definitions) : Machine.defineStates(definitions), events: typeof Machine.event === "function" ? (...schemas) => schemas : (...schemas) => Machine.events(...schemas), - initial: (definition, legacy) => hasStaticTransitions || hasFluentTransitions ? definition : legacy, + initial: (definition, legacy) => hasValueSelectors + ? fluentInitial(definition) + : hasStaticTransitions || hasFluentTransitions + ? objectInitial(definition) + : legacy, transition: (definition, legacy) => hasStaticTransitions - ? Machine.transition(definition) + ? Machine.transition(objectTransition(definition)) : hasFluentTransitions ? fluentTransition(definition) : legacy, - targetless: hasStaticTransitions || hasFluentTransitions + targetless: hasValueSelectors + ? (to) => to.none + : hasStaticTransitions || hasFluentTransitions ? { target: Machine.targetless } : targetless, invokeChild: typeof Machine.invokeMachine === "function" diff --git a/perf/types/adapter-readiness-control.ts b/perf/types/adapter-readiness-control.ts index aa937b9..a5ef530 100644 --- a/perf/types/adapter-readiness-control.ts +++ b/perf/types/adapter-readiness-control.ts @@ -29,10 +29,7 @@ export const machine = Machine.make({ id: "perf-readiness", states: States.states, events: Machine.events(), - initial: { - target: (to) => to.Ready(), - resolve: ({ target }) => target(Ready.make({})) - } + initial: (to) => to.Ready().resolve(({ target }) => target(Ready.make({}))) }).handle({ Flow: { history: { diff --git a/perf/types/composition-control.ts b/perf/types/composition-control.ts index 0f9069a..2705f2c 100644 --- a/perf/types/composition-control.ts +++ b/perf/types/composition-control.ts @@ -65,9 +65,8 @@ export const States = Machine.states({ export const machine = Machine.make({ states: States.states, events: Machine.events(), - initial: { - target: (to) => to.App.initial(), - resolve: ({ target }) => + initial: (to) => + to.App.initial.resolve(({ target }) => target(App.make({}), (app) => app.Workspace( Workspace.make({}), @@ -76,5 +75,5 @@ export const machine = Machine.make({ .Editor(Editor.make({}), (editor) => editor.Editing(Editing.make({}))) .Sync(Sync.make({}), (sync) => sync.Idle(SyncIdle.make({}))) )) - } + ) }) diff --git a/perf/types/definition-variants-control.ts b/perf/types/definition-variants-control.ts index bf94944..1078582 100644 --- a/perf/types/definition-variants-control.ts +++ b/perf/types/definition-variants-control.ts @@ -34,8 +34,5 @@ export const States = Machine.states({ export const machine = Machine.make({ states: States.states, events: Machine.events(Start, Finish), - initial: { - target: (to) => to.Flow.initial(), - resolve: ({ target }) => target(Flow.make({}), (flow) => flow.Idle(Idle.make({}))) - } + initial: (to) => to.Flow.initial.resolve(({ target }) => target(Flow.make({}), (flow) => flow.Idle(Idle.make({})))) }) diff --git a/perf/types/dynamic-invoke-control.ts b/perf/types/dynamic-invoke-control.ts index 4c76b71..a74c200 100644 --- a/perf/types/dynamic-invoke-control.ts +++ b/perf/types/dynamic-invoke-control.ts @@ -14,8 +14,5 @@ export const loadUser = (userId: string) => Effect.fail(new LoadError()).pipe(Ef export const machine = Machine.make({ states: States.states, events: Machine.events(), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(Loading.make({ userId: "user-1" })) - } + initial: (to) => to.Loading().resolve(({ target }) => target(Loading.make({ userId: "user-1" }))) }) diff --git a/perf/types/dynamic-invoke.ts b/perf/types/dynamic-invoke.ts index 886358f..ca8189d 100644 --- a/perf/types/dynamic-invoke.ts +++ b/perf/types/dynamic-invoke.ts @@ -12,13 +12,13 @@ const invoked = machine.handle({ id: "load-user", effect: ({ state }) => loadUser(state.userId), onDone: (to) => - to.none().resolve(({ output }) => { + to.none.resolve(({ output }) => { const user: User = output void user return undefined }), onFailure: (to) => - to.none().resolve(({ error }) => { + to.none.resolve(({ error }) => { const loadError: LoadError = error void loadError return undefined diff --git a/perf/types/exact-channels-control.ts b/perf/types/exact-channels-control.ts index d001020..b8f1e28 100644 --- a/perf/types/exact-channels-control.ts +++ b/perf/types/exact-channels-control.ts @@ -23,8 +23,5 @@ export const machine = Machine.make({ internalEvents: Machine.internalEvents(Loaded), emittedEvents: Machine.emittedEvents(Notice), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input, target }) => target(Idle.make({ value: input.seed })) - } + initial: (to) => to.Idle().resolve(({ input, target }) => target(Idle.make({ value: input.seed }))) }) diff --git a/perf/types/handle-depth-24-control.ts b/perf/types/handle-depth-24-control.ts index 24a2c25..ae08dec 100644 --- a/perf/types/handle-depth-24-control.ts +++ b/perf/types/handle-depth-24-control.ts @@ -159,10 +159,8 @@ export const States = Machine.states({ export const machine = Machine.make({ states: States.states, events: Machine.events(), - initial: { - target: (to) => to.n0.initial(), - resolve: (): never => { + initial: (to) => + to.n0.initial.resolve((): 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 4b7e83e..95f85f8 100644 --- a/perf/types/handle-depth-wide-16-control.ts +++ b/perf/types/handle-depth-wide-16-control.ts @@ -142,10 +142,8 @@ export const States = Machine.states({ export const machine = Machine.make({ states: States.states, events: Machine.events(), - initial: { - target: (to) => to.n0.initial(), - resolve: (): never => { + initial: (to) => + to.n0.initial.resolve((): never => { throw new Error("type-performance fixture") - } - } + }) }) diff --git a/perf/types/handle.ts b/perf/types/handle.ts index ba5d4fb..455a8db 100644 --- a/perf/types/handle.ts +++ b/perf/types/handle.ts @@ -17,10 +17,7 @@ const States = Machine.states(State.cases) const machine = Machine.make({ states: States.states, events: Machine.events(Event.cases.Start, Event.cases.Finish), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(State.cases.Idle.make({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(State.cases.Idle.make({}))) }).handle({ Idle: { on: { diff --git a/perf/types/make.ts b/perf/types/make.ts index ca3e96f..7a11071 100644 --- a/perf/types/make.ts +++ b/perf/types/make.ts @@ -17,10 +17,7 @@ const States = Machine.states(State.cases) const machine = Machine.make({ states: States.states, events: Machine.events(Event.cases.Start, Event.cases.Finish), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(State.cases.Idle.make({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(State.cases.Idle.make({}))) }) void machine diff --git a/perf/types/named-branches-control.ts b/perf/types/named-branches-control.ts index d353d15..ea81768 100644 --- a/perf/types/named-branches-control.ts +++ b/perf/types/named-branches-control.ts @@ -13,8 +13,5 @@ export const States = Machine.states(State.cases) export const machine = Machine.make({ states: States.states, events: Machine.events(Route), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(State.cases.Idle.make({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(State.cases.Idle.make({}))) }) diff --git a/perf/types/named-branches.ts b/perf/types/named-branches.ts index 79bcc09..4ddae2e 100644 --- a/perf/types/named-branches.ts +++ b/perf/types/named-branches.ts @@ -11,12 +11,12 @@ const handled = machine.handle({ length3: { target: to.full.Text() }, length4: { target: to.full.Count() }, length5: { target: to.full.Text() }, - length6: { target: to.none() }, + length6: { target: to.none }, length7: { target: to.full.Count() }, length8: { target: to.full.Text() }, length9: { target: to.full.Count() }, length10: { target: to.full.Idle() }, - unchanged: { target: to.none() } + unchanged: { target: to.none } }).resolve(({ event, select }) => { const value = event.value switch (value.length) { diff --git a/scripts/fixtures/consumer/consumer.ts b/scripts/fixtures/consumer/consumer.ts index 0c5e30d..4801e14 100644 --- a/scripts/fixtures/consumer/consumer.ts +++ b/scripts/fixtures/consumer/consumer.ts @@ -27,17 +27,14 @@ const machine = Machine.make({ states: States.states, events: PublicEvents, internalEvents: InternalEvents, - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(State.cases.Idle.make({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(State.cases.Idle.make({}))) }).handle({ Idle: { on: { Start: (to) => to.branches({ cached: { target: to.full.Loading() }, - measured: { target: to.none() }, + measured: { target: to.none }, named: { target: to.full.Done() }, confirmed: { target: to.full.Idle() } }).resolve(({ select }) => select.cached(State.cases.Loading.make({}))) @@ -63,12 +60,12 @@ const cluster = ClusterMachine.make("ConsumerEntity", machine, { const invoked = Machine.invoke({ id: "fixture-load", effect: () => Effect.succeed("ready"), - onDone: { target: Machine.targetless } + onDone: (to) => to.none }) const delayed = Machine.invoke({ id: "fixture-delay", after: "1 second", - onDone: { target: Machine.targetless } + onDone: (to) => to.none }) const generated = MachineTest.scenarios(machine, { minEvents: 1, maxEvents: 2 }) diff --git a/scripts/fixtures/consumer/deep-bound.ts b/scripts/fixtures/consumer/deep-bound.ts index 2950d33..e8ab01e 100644 --- a/scripts/fixtures/consumer/deep-bound.ts +++ b/scripts/fixtures/consumer/deep-bound.ts @@ -49,10 +49,7 @@ const childMachine = Machine.make({ events: Machine.events(), parentEvents: ChildParentEvents, input: Schema.Struct({ value: Schema.String }), - initial: { - target: (to) => to.Done(), - resolve: ({ input, target }) => target(ChildState.cases.Done.make({ value: input.value })) - } + initial: (to) => to.Done().resolve(({ input, target }) => target(ChildState.cases.Done.make({ value: input.value }))) }).handle({ Done: { entry: ({ parent, state }, enqueue) => { @@ -95,17 +92,14 @@ const definition = Machine.make({ internalEvents: Machine.internalEvents(Internal.cases.Loaded, Internal.cases.ChildCompleted), emittedEvents: Emissions, input: Schema.Struct({ seed: Schema.String }), - initial: { - target: (to) => to.Idle(), - resolve: ({ input: { seed: _seed }, target }) => target(State.cases.Idle.make({})) - } + initial: (to) => to.Idle().resolve(({ input: { seed: _seed }, target }) => target(State.cases.Idle.make({}))) }) const machine = definition.handle({ Idle: { invoke: Machine.invoke({ id: "deep-inline-invoke", effect: () => Effect.asVoid(ExternalService), - onDone: { target: Machine.targetless } + onDone: (to) => to.none }), on: { Begin: (to) => @@ -131,14 +125,14 @@ const machine = definition.handle({ to.local.Saving().resolve(({ event, target }) => target(State.cases.Saving.make({ value: event.value })) ), - Loaded: { target: Machine.targetless } + Loaded: (to) => to.none } }, Saving: { invoke: Machine.invoke({ child: Child, input: ({ state }) => ({ value: state.value }), - onDone: { target: Machine.targetless } + onDone: (to) => to.none }), on: { ChildNotice: (to) => @@ -242,12 +236,10 @@ const PackagedDeepStates = Machine.states({ const packagedDeepMachine = Machine.make({ states: PackagedDeepStates.states, events: Machine.events(), - initial: { - target: (to) => to.n0.initial(), - resolve: (): never => { + initial: (to) => + to.n0.initial.resolve((): never => { throw new Error("type-only packaged consumer fixture") - } - } + }) }).handle({ n0: { states: { diff --git a/scripts/invoke-autocomplete.test.mjs b/scripts/invoke-autocomplete.test.mjs index 939d8bd..0fd0ee3 100644 --- a/scripts/invoke-autocomplete.test.mjs +++ b/scripts/invoke-autocomplete.test.mjs @@ -14,7 +14,9 @@ const States = Machine.states({ Loading: {}, Done: {}, Failed: {} }) const definition = Machine.make({ states: States.states, events: Machine.events(), - initial: { target: (to) => to.Loading(), resolve: ({ target }) => target.from() } + initial: (to) => + to./*initial-selector*/Loading()./*initial-operations*/resolve(({ /*initial-context*/ ...context }) => + context.target./*initial-exact-target*/from()) }) definition.handle({ @@ -41,8 +43,8 @@ definition.handle({ id: "updates", stream: () => Stream.make(1), onElement: (to) => - to.none().resolve(({ /*element-context*/ ...context }) => undefined), - onDone: (to) => to.none() + to.none.resolve(({ /*element-context*/ ...context }) => undefined), + onDone: (to) => to.none }) }, Done: {}, @@ -63,14 +65,14 @@ definition.handle({ definition.handle({ Loading: { - always: (to) => to./*transition-selector*/none() + always: (to) => to./*transition-selector*/none } }) definition.handle({ Loading: { always: (to) => - to.none().resolve(({ /*targetless-context*/ ...context }) => undefined) + to.none.resolve(({ /*targetless-context*/ ...context }) => undefined) } }) @@ -90,7 +92,7 @@ definition.handle({ title: "ready", target: to./*branch-target-scopes*/full.Done() }, - unchanged: { target: to.none() } + unchanged: { target: to.none } }).resolve(({ /*branch-resolve-context*/ ...context }) => context.select./*branch-select-keys*/ready.from()) } @@ -99,14 +101,14 @@ definition.handle({ definition.handle({ Loading: { always: (to) => - to.none().resolve(({ /*required-context*/ ...context }) => undefined) + to.none.resolve(({ /*required-context*/ ...context }) => undefined) } }) definition.handle({ Loading: { always: (to) => - to.none().resolve(({ /*declinable-context*/ ...context }) => context.decline(), { + to.none.resolve(({ /*declinable-context*/ ...context }) => context.decline(), { declinable: true }) } @@ -187,6 +189,22 @@ test("contextually completes Stream element handlers while authoring", () => { }) test("contextually completes transition definitions while authoring", () => { + const initialSelector = completions("initial-selector") + assert.equal(initialSelector.has("Loading"), true) + assert.equal(initialSelector.has("none"), false) + + const initialOperations = completions("initial-operations") + assert.equal(initialOperations.has("resolve"), true) + assert.equal(initialOperations.has("reenter"), false) + + const initialContext = completions("initial-context") + assert.equal(initialContext.has("input"), true) + assert.equal(initialContext.has("target"), true) + + const initialTarget = completions("initial-exact-target") + assert.equal(initialTarget.has("from"), true) + assert.equal(initialTarget.has("Done"), false) + const selector = completions("transition-selector") assert.equal(selector.has("none"), true) assert.equal(selector.has("branches"), true) diff --git a/scripts/runtime-performance-compatibility.test.mjs b/scripts/runtime-performance-compatibility.test.mjs index 15fed92..314f5c8 100644 --- a/scripts/runtime-performance-compatibility.test.mjs +++ b/scripts/runtime-performance-compatibility.test.mjs @@ -25,21 +25,22 @@ test("adapts wrapped static transition definitions when that capability is prese events: (...schemas) => schemas } const api = makeEffectMachineBenchmarkApi(Machine) - const definition = { target: "selected", resolve: "resolved" } + const definition = { target: (to) => to.selected, resolve: "resolved" } const legacy = () => undefined + const selected = Symbol("selected") - assert.equal(api.initial(definition, legacy), definition) - assert.deepEqual(api.transition(definition, legacy), { static: definition }) - assert.equal(api.targetless.target, targetless) + assert.equal(api.initial(definition, legacy).target({ selected }), selected) + assert.equal(api.transition(definition, legacy).static.target({ selected }), selected) + assert.deepEqual(api.targetless, { target: targetless }) assert.equal(calls.length, 1) }) -test("adapts benchmark definitions to fluent transition selectors", () => { +test("adapts benchmark definitions to callable fluent transition selectors", () => { const calls = [] const targetless = Object.assign((to) => to.none(), { "~effect/Machine/TargetlessSelector": true }) - const Machine = { targetless } + const Machine = { targetless, invoke: (config) => config } const api = makeEffectMachineBenchmarkApi(Machine) const resolve = () => undefined const selected = { @@ -57,8 +58,26 @@ test("adapts benchmark definitions to fluent transition selectors", () => { assert.equal(transition({ selected }), "resolved") assert.deepEqual(calls, [[resolve, { reenter: true, declinable: true }]]) - assert.equal(api.initial("static", "legacy"), "static") - assert.equal(api.targetless.target, targetless) + assert.equal(typeof api.initial({ target: (to) => to.selected }, "legacy"), "object") + assert.deepEqual(api.targetless, { target: targetless }) +}) + +test("adapts benchmark definitions to value selectors and target-first initial entry", () => { + const calls = [] + const Machine = { invoke: (config) => config } + const api = makeEffectMachineBenchmarkApi(Machine) + const selected = { + resolve: (resolver) => { + calls.push(resolver) + return "resolved" + } + } + const resolve = () => undefined + const initial = api.initial({ target: (to) => to.selected, resolve }, "legacy") + + assert.equal(initial({ selected }), "resolved") + assert.deepEqual(calls, [resolve]) + assert.equal(api.targetless({ none: selected }), selected) }) test("uses the current child invocation capability when available", () => { @@ -83,7 +102,7 @@ test("uses the current child invocation capability when available", () => { config }) assert.deepEqual(calls, [config]) - assert.equal(makeEffectMachineBenchmarkApi(Machine).targetless({ target: { none: () => noTarget } }), noTarget) + assert.equal(makeEffectMachineBenchmarkApi(Machine).targetless({ none: noTarget }), noTarget) }) test("adapts lifecycle names for the legacy child invocation capability", () => { diff --git a/src/Machine.ts b/src/Machine.ts index 1603a36..29ec6fa 100644 --- a/src/Machine.ts +++ b/src/Machine.ts @@ -592,6 +592,7 @@ type IncompatibleRuntime = Requirements extends Run const InvokeTypeId: typeof internal.InvokeTypeId = internal.InvokeTypeId const TransitionTypeId: typeof internal.TransitionTypeId = internal.TransitionTypeId declare const TransitionBuilderTypeId: unique symbol +declare const InitialBuilderTypeId: unique symbol type StateDefinitionError< Message extends string, @@ -717,6 +718,29 @@ type ValidateOutputSchema = "output" extends keyof Node ? Node extends { r : StateDefinitionError<"State output must be a schema"> : unknown +type SelectorChildKey = ActiveStateKey | ChoiceStateKey + +type ValidateInitialSelectorChild = "initial" extends + SelectorChildKey ? StateDefinitionError< + "Active and choice child states cannot use the reserved target selector key \"initial\"", + StateDefinitionPath, "initial">, + "initial" + > + : unknown + +type ValidateWithSelectorChild< + Node extends Machine.StateNodeConfig, + Children extends Machine.StateSchemas, + Path extends PropertyKey +> = Node extends { readonly schema: Machine.TaggedSchema } ? + "with" extends SelectorChildKey ? StateDefinitionError< + "Schema-backed compound child states cannot use the reserved local target selector key \"with\"", + StateDefinitionPath, "with">, + "with" + > + : unknown + : unknown + type ValidateStateNodeWithChildren< Node extends Machine.StateNodeConfig, Children, @@ -724,8 +748,9 @@ type ValidateStateNodeWithChildren< > = Children extends Machine.StateSchemas ? Node extends { readonly type: "final" } ? StateDefinitionError<"Final states cannot declare child states"> : Node extends { readonly type: "parallel" } ? - "initial" extends keyof Node ? StateDefinitionError<"Parallel states cannot declare an initial child"> - : { readonly states: ValidateStateTree> } & ValidateOutputSchema + & ValidateInitialSelectorChild + & ("initial" extends keyof Node ? StateDefinitionError<"Parallel states cannot declare an initial child"> + : { readonly states: ValidateStateTree> } & ValidateOutputSchema) : "output" extends keyof Node ? StateDefinitionError<"Only final and parallel states can declare output"> : ValidateCompoundStateNode : StateDefinitionError<"Child states must be a state tree"> @@ -735,9 +760,12 @@ type ValidateCompoundStateNode< Children extends Machine.StateSchemas, Path extends PropertyKey > = Node extends { readonly initial: infer Initial } ? - Initial extends ActiveStateKey | ChoiceStateKey ? { - readonly states: ValidateStateTree> - } + Initial extends ActiveStateKey | ChoiceStateKey ? + & ValidateInitialSelectorChild + & ValidateWithSelectorChild + & { + readonly states: ValidateStateTree> + } : StateDefinitionError< "Compound initial must be one of its direct child keys", Path, @@ -4402,11 +4430,14 @@ export declare namespace Machine { readonly "~effect/Machine/TargetSelectionResult"?: Types.Covariant } - type SelectionMethod = () => + type SelectionValue = TargetSelection + type SelectionMethod = () => + SelectionValue + type InitialSelectionMethod = Builder extends { readonly initial: infer Initial } ? { - readonly initial: SelectionMethod + readonly initial: SelectionValue } : {} @@ -4486,7 +4517,7 @@ export declare namespace Machine { LocalTargetBuilder extends infer Builder ? & SelectionTreeWithPrefix & ("with" extends keyof Builder ? { - readonly with: SelectionMethod + readonly with: SelectionValue } : {}) : {} @@ -4500,7 +4531,7 @@ export declare namespace Machine { Builder > = { readonly [Key in Extract, keyof Builder>]: States[Key] extends HistoryStateNodeConfig ? - SelectionMethod< + SelectionValue< Builder[Key], JoinPath, "history" @@ -4514,12 +4545,17 @@ export declare namespace Machine { : never } - /** Definition-time topology selector available to an ordinary transition. */ + /** + * Definition-time topology selector available to an ordinary transition. + * Topology-only instructions (`none`, declared `initial` and history + * selections, and `local.with`) are values. State and choice destinations + * remain callable selection methods. + */ export interface TargetSelector< States extends StateSchemas, Source extends StateNodeIdentifier > { - readonly none: SelectionMethod["none"], never, "none"> + readonly none: SelectionValue["none"], never, "none"> readonly local: LocalTargetSelector readonly branch: BranchTargetSelector readonly full: FullTargetSelector @@ -4527,10 +4563,10 @@ export declare namespace Machine { } /** Definition-time selector that can choose only a valid top-level initial entry. */ - export type InitialSelector = { + type InitialTargetSelector = { readonly [Key in Extract, keyof InitialBuilder>]: States[Key] extends { readonly states: StateSchemas } ? { - readonly initial: SelectionMethod[Key], Key, "initial"> + readonly initial: SelectionValue[Key], Key, "initial"> } : SelectionMethod[Key], Key> } @@ -5192,14 +5228,6 @@ export declare namespace Machine { } } - /** Type evidence for a targetless transition reusable in every owning context. */ - export interface TargetlessTransitionTyped { - readonly [TransitionTypeId]: { - readonly targetless: true - readonly acceptance: Types.Covariant - } - } - /** The only transition value accepted by machine handler APIs. */ export type TransitionConfig< States extends StateSchemas, @@ -5209,35 +5237,7 @@ export declare namespace Machine { Context, Reenter extends boolean = false, Acceptance extends TransitionAcceptance = "required" - > = - | TargetlessTransitionInput - | TransitionBuilderInput - - /** Direct shorthand for a non-reentering targetless transition. */ - // Keep the context generic so omitting `target` is deferred until a resolver - // is actually authored instead of expanding every TransitionConfig eagerly. - interface TargetlessTransitionResolver< - Events extends ReadonlyArray, - Emits extends ReadonlyArray, - Context - > { - >( - context: ResolvedContext, - enqueue: Enqueue, EmitOf> - ): undefined - } - - export type TargetlessTransitionInput< - Events extends ReadonlyArray, - Emits extends ReadonlyArray, - Context - > = { - readonly target: TargetlessSelector - readonly resolve?: TargetlessTransitionResolver - readonly branches?: never - readonly reenter?: never - readonly declinable?: false - } + > = TransitionBuilderInput export type SelectionBuilder = Selection extends TargetSelection ? Builder : never export type SelectionKind = Selection extends TargetSelection ? Kind : never @@ -5395,8 +5395,7 @@ export declare namespace Machine { Result, Acceptance extends TransitionAcceptance > = - & ([Exclude] extends [never] ? TargetlessTransitionTyped - : TransitionTyped) + & TransitionTyped & TransitionBuilderEvidence interface TransitionResolveRequired< @@ -5500,6 +5499,47 @@ export declare namespace Machine { : {} : {}) + /** @internal Type evidence retained by a machine initial-entry declaration. */ + export interface InitialBuilderEvidence { + readonly [InitialBuilderTypeId]: Types.Covariant + } + + /** A selected machine initial entry with its exact resolver target. */ + export type InitialTransitionTarget> = + & Selection + & (SelectionSupportsDefaultConstruction extends true ? InitialBuilderEvidence : {}) + & { + readonly resolve: ( + resolve: (context: { + readonly input: Input + readonly target: SelectionBuilder + }) => SelectedTargetResult + ) => InitialBuilderEvidence + } + + type InitialSelectorNode = Node extends (...args: infer Args) => infer Selection ? + Selection extends TargetSelection ? + & ((...args: Args) => InitialTransitionTarget) + & { + readonly [Key in keyof Node]: InitialSelectorNode + } + : never + : Node extends TargetSelection ? InitialTransitionTarget + : { + readonly [Key in keyof Node]: InitialSelectorNode + } + + /** Definition-time selector for a machine's top-level initial entry. */ + export type InitialSelector = InitialSelectorNode< + Input, + InitialTargetSelector + > + + /** Target-first initial-entry declaration accepted by {@link make}. */ + export type InitialBuilderInput = ( + to: InitialSelector + ) => InitialBuilderEvidence> + type TransitionSelectorNode< States extends StateSchemas, Events extends ReadonlyArray, @@ -5533,6 +5573,16 @@ export declare namespace Machine { > } : never + : Node extends TargetSelection ? TransitionTarget< + States, + Events, + Emits, + StateId, + Context, + Reenter, + Acceptance, + Node + > : { readonly [Key in keyof Node]: TransitionSelectorNode< States, @@ -5645,10 +5695,7 @@ export declare namespace Machine { > = ( to: TransitionSelector ) => - & ( - | TransitionTyped - | TargetlessTransitionTyped - ) + & TransitionTyped & TransitionBuilderEvidence export type InvokeTransition< @@ -7527,10 +7574,7 @@ export const state: StateConstructor = internal.state as StateConstructor * Machine.make({ * states: States.states, * events: Machine.events(), - * initial: { - * target: (to) => to.idle(), - * resolve: ({ target }) => target.from() - * } + * initial: (to) => to.idle().resolve(({ target }) => target.from()) * }) * ``` * @@ -7539,20 +7583,6 @@ export const state: StateConstructor = internal.state as StateConstructor */ export const states: StatesConstructor = internal.states -type InitialDirectInput< - States extends Machine.StateSchemas, - Input, - Selection extends Machine.TargetSelection -> = { - readonly target: (to: Machine.InitialSelector) => Selection - readonly resolve?: (context: { - readonly input: Input - readonly target: Machine.SelectionBuilder - }) => Machine.SelectedTargetResult - readonly cases?: never - readonly otherwise?: never -} - type MakeConfig< States extends Machine.StateSchemas, InputEvents extends ReadonlyArray, @@ -7611,15 +7641,14 @@ interface Make { InitialE = never, InitialR = never, const InternalEvents extends ReadonlyArray = readonly [], - const ParentEvents extends ReadonlyArray = readonly [], - const InitialSelection extends Machine.TargetSelection = Machine.TargetSelection + const ParentEvents extends ReadonlyArray = readonly [] >( config: & Omit< MakeConfig, "initial" > - & { readonly initial: InitialDirectInput }, + & { readonly initial: Machine.InitialBuilderInput }, ..._validation: ValidateDefinedStates> ): MakeResult < @@ -7649,6 +7678,11 @@ interface Make { * `states` or is passed inline. Call `handle` on the returned definition * to implement state behavior with ordinary TypeScript control flow. * + * `initial` is a target-first callback. Its outer selector runs once while the + * definition is captured; an attached `.resolve(...)` callback remains lazy + * until initial planning. Return a bare selected state when its schema supports + * default construction. + * * `Machine.events` defines the public input protocol. `Machine.internalEvents` * adds raised events and other machine-local deliveries. * `Machine.emittedEvents` defines outward ephemeral notifications, while @@ -7677,10 +7711,7 @@ interface Make { * const counter = Machine.make({ * states: States.states, * events: Events, - * initial: { - * target: (to) => to.Count(), - * resolve: ({ target }) => target(new Count({ value: 0 })) - * } + * initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) * }).handle({ * Count: { * on: { @@ -7840,10 +7871,7 @@ export const emittedEvents: { * const machine = Machine.make({ * states: States.states, * events: Machine.events(), - * initial: { - * target: (to) => to.Idle(), - * resolve: ({ target }) => target.from() - * } + * initial: (to) => to.Idle().resolve(({ target }) => target.from()) * }).handle({ Idle: {} }) * * const encoded = Effect.gen(function*() { @@ -7925,10 +7953,7 @@ export const encodeSnapshot: < * const machine = Machine.make({ * states: States.states, * events: Machine.events(), - * initial: { - * target: (to) => to.Idle(), - * resolve: ({ target }) => target.from() - * } + * initial: (to) => to.Idle().resolve(({ target }) => target.from()) * }).handle({ Idle: {} }) * * const roundTrip = Effect.gen(function*() { @@ -8189,47 +8214,6 @@ type ValidateTransitionBranchRecord = [keyof Branches] extends [never] InvalidStaticTransitionBranchKey > -/** - * Sentinel used by direct targetless transition shorthands. - * - * This is the concise form of `target: (to) => to.none()`. - * - * ```ts - * onElement: { - * target: Machine.targetless, - * resolve: ({ element }, enqueue) => { - * enqueue.raise(new MeasurementReceived({ value: element })) - * } - * } - * - * onDone: { target: Machine.targetless } - * ``` - * - * @category constructors - * @since 0.16.0 - */ -export interface TargetlessSelector { - readonly "~effect/Machine/TargetlessSelector": true - < - const States extends Machine.StateSchemas, - StateId extends Machine.StateNodeIdentifier - >(to: Machine.TargetSelector): ReturnType["none"]> -} - -/** - * Selects no transition target while keeping enqueue operations explicit. - * - * Use as the `target` of a direct handler object when the handler should keep - * the current state configuration and only raise, emit, or send events. - * - * @category constructors - * @since 0.16.0 - */ -export const targetless: TargetlessSelector = Object.assign( - (to: Machine.TargetSelector) => to.none(), - { "~effect/Machine/TargetlessSelector": true as const } -) - /** * Preserves inference for a state-owned invocation configuration. * @@ -8621,10 +8605,7 @@ export const invoke: { * const machine = Machine.make({ * states: States.states, * events: Machine.events(), - * initial: { - * target: (to) => to.Idle(), - * resolve: ({ target }) => target.from() - * } + * initial: (to) => to.Idle().resolve(({ target }) => target.from()) * }).handle({ Idle: {} }) * * const initialState = Effect.map(Machine.planInitial(machine), (plan) => plan.state) @@ -8882,10 +8863,7 @@ export const enabled: < * const machine = Machine.make({ * states: States.states, * events: Machine.events(Toggle), - * initial: { - * target: (to) => to.Off(), - * resolve: ({ target }) => target.from() - * } + * initial: (to) => to.Off().resolve(({ target }) => target.from()) * }).handle({ * Off: { * on: { @@ -9263,10 +9241,7 @@ export const prepare: < * const machine = Machine.make({ * states: States.states, * events: Machine.events(), - * initial: { - * target: (to) => to.Idle(), - * resolve: ({ target }) => target.from() - * } + * initial: (to) => to.Idle().resolve(({ target }) => target.from()) * }).handle({ Idle: {} }) * * const state = Effect.gen(function*() { @@ -9376,10 +9351,7 @@ export const start: < * const machine = Machine.make({ * states: States.states, * events: Machine.events(), - * initial: { - * target: (to) => to.Idle(), - * resolve: ({ target }) => target.from() - * } + * initial: (to) => to.Idle().resolve(({ target }) => target.from()) * }).handle({ Idle: {} }) * * const resumed = Effect.gen(function*() { diff --git a/src/internal/machine/machine.ts b/src/internal/machine/machine.ts index 756abd9..9382d92 100644 --- a/src/internal/machine/machine.ts +++ b/src/internal/machine/machine.ts @@ -176,8 +176,18 @@ type BranchesTransitionDescriptor = { type TransitionBuilderDescriptor = DirectTransitionDescriptor | BranchesTransitionDescriptor +const InitialBuilderDescriptorTypeId: unique symbol = Symbol("effect/Machine/InitialBuilderDescriptor") + +type InitialBuilderDescriptor = { + readonly [InitialBuilderDescriptorTypeId]: typeof InitialBuilderDescriptorTypeId + readonly selection: Topology.TargetSelection + readonly resolve: (context: any) => unknown +} + const plainTargetSelection = (selection: Topology.TargetSelection): Topology.TargetSelection => - Topology.makeTargetSelection(selection.kind, selection.path, selection.scope) + selection.kind === "none" + ? Topology.noneTargetSelection + : Topology.makeTargetSelection(selection.kind, selection.path, selection.scope) const transitionOptions = (options: unknown): { readonly reenter: boolean; readonly declinable: boolean } => { const configuration = typeof options === "object" && options !== null @@ -213,7 +223,49 @@ const decorateTransitionSelection = (selection: Topology.TargetSelection): Topol reenter: () => makeDirectTransitionDescriptor(selection, undefined, { reenter: true }) }) +const noneTransitionSelection = decorateTransitionSelection(Topology.noneTargetSelection) + +const makeInitialBuilderDescriptor = ( + selection: Topology.TargetSelection, + resolve: (context: any) => unknown +): InitialBuilderDescriptor => + Object.freeze({ + [InitialBuilderDescriptorTypeId]: InitialBuilderDescriptorTypeId, + selection: plainTargetSelection(selection), + resolve + }) + +const decorateInitialSelection = (selection: Topology.TargetSelection): Topology.TargetSelection => + Object.freeze({ + ...selection, + resolve: (resolve: (context: any) => unknown) => makeInitialBuilderDescriptor(selection, resolve) + }) + +const decorateInitialSelectorNode = (node: unknown): unknown => { + if (Topology.isTargetSelection(node)) return decorateInitialSelection(node) + if (typeof node === "function") { + const wrapped = ((...args: ReadonlyArray) => decorateInitialSelection(node(...args))) as + & ((...args: ReadonlyArray) => unknown) + & Record + for (const key of Object.keys(node)) { + wrapped[key] = decorateInitialSelectorNode((node as unknown as Record)[key]) + } + return Object.freeze(wrapped) + } + if (typeof node === "object" && node !== null) { + const wrapped: Record = {} + for (const key of Object.keys(node)) { + wrapped[key] = decorateInitialSelectorNode((node as Record)[key]) + } + return Object.freeze(wrapped) + } + return node +} + const decorateTransitionSelectorNode = (node: unknown): unknown => { + if (Topology.isTargetSelection(node)) { + return node === Topology.noneTargetSelection ? noneTransitionSelection : decorateTransitionSelection(node) + } if (typeof node === "function") { const wrapped = ((...args: ReadonlyArray) => decorateTransitionSelection(node(...args))) as & ((...args: ReadonlyArray) => unknown) @@ -301,6 +353,12 @@ const makeSelectionMethod = ( ): () => Topology.TargetSelection => () => Topology.makeTargetSelection(kind, path, scope) +const makeSelectionValue = ( + kind: Topology.TargetSelectionKind, + path: string | undefined, + scope: Topology.TargetSelectionScope +): Topology.TargetSelection => Topology.makeTargetSelection(kind, path, scope) + const addSelectionChildren = ( builder: Record, stateNodes: Machine.StateNodes, @@ -323,7 +381,7 @@ const makeSelectionNode = ( const method = makeSelectionMethod(kind, path, scope) as unknown as Record if (node.type !== "atomic" && node.type !== "final" && node.type !== "choice" && node.type !== "history") { Object.defineProperty(method, "initial", { - value: makeSelectionMethod("initial", path, scope), + value: makeSelectionValue("initial", path, scope), enumerable: true }) if (scope === "local" || scope === "branch") { @@ -341,7 +399,7 @@ const makeHistorySelectionTree = ( for (const node of stateNodes.byPath.values()) { if (node.parent !== parent) continue if (node.type === "history") { - builder[node.key] = makeSelectionMethod("history", node.path, "full") + builder[node.key] = makeSelectionValue("history", node.path, "full") } else if (node.type !== "choice") { const children = makeHistorySelectionTree(stateNodes, node.path) if (Object.keys(children).length > 0) builder[node.key] = children @@ -368,12 +426,12 @@ const makeTargetSelector = ( if (localScope !== undefined) { const localScopeNode = getTargetBuilderNode(stateNodes, localScope) if (localScopeNode.schema !== undefined) { - local.with = makeSelectionMethod("state", localScope, "local") + local.with = makeSelectionValue("state", localScope, "local") } addSelectionChildren(local, stateNodes, localScope, "local") } return { - none: makeSelectionMethod("none", undefined, "local"), + none: Topology.noneTargetSelection, local, branch, full, @@ -616,9 +674,12 @@ const captureTransition = ( path: string, trigger: PropertyKey ): unknown => { - const transition = typeof rawTransition === "function" - ? normalizeTransitionBuilder(rawTransition as (selector: unknown) => unknown, stateNodes, path) - : rawTransition + if (typeof rawTransition !== "function") { + throw new Error( + `Machine transition for state "${path}" on "${String(trigger)}" must be a target-first callback` + ) + } + const transition = normalizeTransitionBuilder(rawTransition as (selector: unknown) => unknown, stateNodes, path) if (typeof transition !== "object" || transition === null) { throw new Error(`Machine transition for state "${path}" on "${String(trigger)}" must be an object`) } @@ -1339,10 +1400,12 @@ const makeInitialSelector = (stateNodes: Machine.StateNodes): unknown => { const selector: Record = {} for (const node of stateNodes.byPath.values()) { if (node.parent === undefined && node.type !== "history" && node.type !== "choice") { - selector[node.key] = makeSelectionNode(stateNodes, node.path, "initial") + selector[node.key] = node.type === "atomic" || node.type === "final" + ? makeSelectionMethod("state", node.path, "initial") + : Object.freeze({ initial: makeSelectionValue("initial", node.path, "initial") }) } } - return selector + return Object.freeze(selector) } const getInitialSelectionBuilder = ( @@ -1361,15 +1424,37 @@ const getInitialSelectionBuilder = ( } const captureInitialBranch = ( - branch: unknown, - selector: unknown, + definition: unknown, + stateNodes: Machine.StateNodes, initialBuilder: Record -): CapturedBranch & { readonly builder: (...args: ReadonlyArray) => unknown } => { - const captured = captureDefinitionBranch(branch, selector, "", "initial") - if (captured.selection.kind !== "state" && captured.selection.kind !== "initial") { +): { + readonly selection: Topology.TargetSelection + readonly resolve?: (context: any) => unknown + readonly builder: (...args: ReadonlyArray) => unknown +} => { + if (typeof definition !== "function") { + throw new Error("Machine initial definition must be a target-first callback") + } + const result = definition(decorateInitialSelectorNode(makeInitialSelector(stateNodes))) + let selection: Topology.TargetSelection + let resolve: ((context: any) => unknown) | undefined + if (Topology.isTargetSelection(result)) { + selection = plainTargetSelection(result) + } else if (hasProperty(result, InitialBuilderDescriptorTypeId)) { + const descriptor = result as InitialBuilderDescriptor + selection = descriptor.selection + resolve = descriptor.resolve + } else { + throw new Error("Machine initial definition must select exactly one target") + } + if (selection.kind !== "state" && selection.kind !== "initial") { throw new Error("Machine initial target must select a top-level state or its declared initial entry") } - return { ...captured, builder: getInitialSelectionBuilder(initialBuilder, captured.selection) } + const captured = { + selection, + builder: getInitialSelectionBuilder(initialBuilder, selection) + } + return resolve === undefined ? Object.freeze(captured) : Object.freeze({ ...captured, resolve }) } const validateInitialSelection = (result: unknown, selection: Topology.TargetSelection): void => { @@ -1391,17 +1476,13 @@ const compileInitial = ( readonly initial: (input?: unknown) => unknown readonly definition: Machine.InitialDefinition } => { - if (typeof definition !== "object" || definition === null) { - throw new Error("Machine initial definition must be an object") - } - const selector = makeInitialSelector(stateNodes) const initialBuilder = makeSnapshotBuilder(states, { mode: "initial", prefix: "" }) as Record - const branch = captureInitialBranch(definition, selector, initialBuilder) + const branch = captureInitialBranch(definition, stateNodes, initialBuilder) return { initial: (input?: unknown) => { const result = branch.resolve === undefined - ? branch.builder() - : branch.resolve({ input, target: branch.builder }, undefined) + ? constructSelectedTarget(branch.builder) + : branch.resolve({ input, target: branch.builder }) validateInitialSelection(result, branch.selection) return result }, diff --git a/src/internal/machine/stateDefinition.ts b/src/internal/machine/stateDefinition.ts index 2496201..9f89de5 100644 --- a/src/internal/machine/stateDefinition.ts +++ b/src/internal/machine/stateDefinition.ts @@ -138,6 +138,22 @@ const assertPlainRecord: ( } } +const validateTargetSelectorChildKey = ( + boundary: StateDefinitionBoundary, + path: string, + states: unknown, + key: "initial" | "with", + message: string +): void => { + if (typeof states !== "object" || states === null || Array.isArray(states) || !hasOwn(states, key)) return + const child = (states as Readonly>)[key] + if ( + typeof child === "object" && child !== null && !Schema.isSchema(child) && + (child as Readonly>).type === "history" + ) return + fail(boundary, `${path}.${key}`, message) +} + const assertAllowedProperties = ( boundary: StateDefinitionBoundary, path: string, @@ -252,6 +268,22 @@ const validateStateTree = ( if (typeof node.initial !== "string") { fail(boundary, `${path}.initial`, "compound states must declare an initial child key") } + validateTargetSelectorChildKey( + boundary, + path, + node.states, + "initial", + "active and choice child states cannot use the reserved target selector key \"initial\"" + ) + if (valued) { + validateTargetSelectorChildKey( + boundary, + path, + node.states, + "with", + "schema-backed compound child states cannot use the reserved local target selector key \"with\"" + ) + } const initialKey = node.initial as string validateStateTree(boundary, node.states, path, true) if (!hasOwn(node.states as object, initialKey)) { @@ -270,6 +302,13 @@ const validateStateTree = ( if (!hasOwn(node, "states")) { fail(boundary, `${path}.states`, "parallel states must declare child regions") } + validateTargetSelectorChildKey( + boundary, + path, + node.states, + "initial", + "active and choice child states cannot use the reserved target selector key \"initial\"" + ) validateStateTree(boundary, node.states, path, true) } } diff --git a/src/internal/machine/topology.ts b/src/internal/machine/topology.ts index 88da383..2e9581d 100644 --- a/src/internal/machine/topology.ts +++ b/src/internal/machine/topology.ts @@ -106,6 +106,9 @@ export const makeTargetSelection = ( path }) +/** Source-independent definition-time selection for an explicitly targetless transition. */ +export const noneTargetSelection: TargetSelection = makeTargetSelection("none", undefined, "local") + export const isTargetSelection = (u: unknown): u is TargetSelection => hasProperty(u, TargetSelectionTypeId) export const makeSelectedBranch = ( diff --git a/src/internal/testing/machine/finiteModel.ts b/src/internal/testing/machine/finiteModel.ts index 98883ba..d726eb6 100644 --- a/src/internal/testing/machine/finiteModel.ts +++ b/src/internal/testing/machine/finiteModel.ts @@ -1316,7 +1316,7 @@ const selectHistoryTarget = (builder: Record, path: string): unknow const parts = path.split(".") let current: any = builder for (let index = 0; index < parts.length - 1; index++) current = current[parts[index]!] - return current[parts[parts.length - 1]!]() + return current[parts[parts.length - 1]!] } const selectableDefinitionTarget = ( @@ -1356,7 +1356,7 @@ const selectDefinitionTarget = ( const parts = selectable.split(".") for (const part of parts) current = current[part] if (typeof current === "function") return current() - return current.initial() + return current.initial } const resolveDefinitionTarget = ( @@ -1433,7 +1433,7 @@ const makeHandlers = ( if (transition.source !== path) continue const config = (to: Record) => { const selected = transition.target === undefined - ? to.none() + ? to.none : selectDefinitionTarget(to, path, transition.target, byPath) const resolve = transition.target === undefined ? () => undefined @@ -1513,13 +1513,12 @@ export const compileModel = (model: FiniteModel): Machine.Machine.Any => { const machine = Machine.make({ states: defined.states as any, events: Machine.events(...eventSchemas) as any, - initial: { - target: (to: Record) => { - const selected = to[initial.path] - return typeof selected === "function" ? selected() : selected.initial() - }, - resolve: ({ target }: { readonly target: any }) => + initial: (to: Record) => { + const selected = to[initial.path] + const selection = typeof selected === "function" ? selected() : selected.initial + return selection.resolve(({ target }: { readonly target: any }) => selectSnapshot({ [initial.path]: target }, initial.path, byPath, [initial.path], 0) as any + ) } } as any) const transitions = new Map(model.transitions.map((transition) => [ diff --git a/test/internal/machine/activities.test.ts b/test/internal/machine/activities.test.ts index 4725337..56ef5d8 100644 --- a/test/internal/machine/activities.test.ts +++ b/test/internal/machine/activities.test.ts @@ -18,10 +18,7 @@ const childMachine = Machine.make({ id: "document-worker", states: childStates.states, events: Machine.events(), - initial: { - target: (to) => to.ChildIdle(), - resolve: ({ target }) => target(new ChildIdle({})) - } + initial: (to) => to.ChildIdle().resolve(({ target }) => target(new ChildIdle({}))) }) const child = Machine.child("child", childMachine) @@ -32,10 +29,7 @@ const activityMachine = Machine.make({ id: "activity-inspection", states: activityStates.states, events: Machine.events(WorkSucceeded, WorkFailed, LoadTimedOut), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(new Loading({})) - } + initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({}))) }).handle({ Loading: { invoke: [ @@ -47,18 +41,18 @@ const activityMachine = Machine.make({ Machine.invoke({ id: "load-document", effect: () => Effect.fail("unavailable").pipe(Effect.as(1)), - onDone: { target: Machine.targetless }, - onFailure: { target: Machine.targetless } + onDone: (to) => to.none, + onFailure: (to) => to.none }), Machine.invoke({ id: "load-timeout", after: timerDuration, - onDone: { target: Machine.targetless } + onDone: (to) => to.none }), Machine.invoke({ id: "updates", stream: () => Stream.empty, - onDone: { target: Machine.targetless } + onDone: (to) => to.none }), Machine.invoke({ child }) ] @@ -182,16 +176,13 @@ describe("machine activity metadata", () => { const generated = Machine.make({ states: activityStates.states, events: Machine.events(LoadTimedOut), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(new Loading({})) - } + initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({}))) }).handle({ Loading: { invoke: Machine.invoke({ id, after: durationMillis, - onDone: { target: Machine.targetless } + onDone: (to) => to.none }) } }) diff --git a/test/internal/machine/protocol.test.ts b/test/internal/machine/protocol.test.ts index 584fee4..ba82b12 100644 --- a/test/internal/machine/protocol.test.ts +++ b/test/internal/machine/protocol.test.ts @@ -11,10 +11,8 @@ const InternalEvent = Schema.TaggedStruct("InternalEvent", { value: Schema.Strin describe("machine protocols", () => { it("rejects forged, misclassified, and overlapping event descriptors", () => { const states = Machine.states({ ProtocolIdle }) - const initial = { - target: (to: Machine.Machine.InitialSelector) => to.ProtocolIdle(), - resolve: () => ({ path: "ProtocolIdle" as const, value: new ProtocolIdle({}) }) - } + const initial = (to: Machine.Machine.InitialSelector) => + to.ProtocolIdle().resolve(() => ({ path: "ProtocolIdle" as const, value: new ProtocolIdle({}) })) assert.throws( () => Machine.make({ states: states.states, events: [PublicEvent] as any, initial }), @@ -43,10 +41,7 @@ describe("machine protocols", () => { states: states.states, events: Machine.events(PublicEvent), internalEvents: Machine.internalEvents(InternalEvent), - initial: { - target: (to) => to.ProtocolIdle(), - resolve: ({ target }) => target(new ProtocolIdle({})) - } + initial: (to) => to.ProtocolIdle().resolve(({ target }) => target(new ProtocolIdle({}))) }).handle({}) assert.strictEqual(Object.hasOwn(machine, "eventSchemas"), false) diff --git a/test/internal/machine/strategyDifferential.test.ts b/test/internal/machine/strategyDifferential.test.ts index 2f72021..caee2f8 100644 --- a/test/internal/machine/strategyDifferential.test.ts +++ b/test/internal/machine/strategyDifferential.test.ts @@ -35,17 +35,14 @@ const makeFlatMachine = () => { return Machine.make({ states: states.states, events: Machine.events(Noop, Increment, Reenter, Finish), - initial: { - target: (to) => to.Count(), - resolve: ({ target }) => target(new Count({ value: 0 })) - } + initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) }).handle({ Count: { on: { - Noop: { target: Machine.targetless }, + Noop: (to) => to.none, Increment: (to) => to.full.Count().resolve(({ state, target }) => target(new Count({ value: state.value + 1 }))), - Reenter: (to) => to.none().resolve(() => undefined, { reenter: true }), + Reenter: (to) => to.none.resolve(() => undefined, { reenter: true }), Finish: (to) => to.full.Done().resolve(({ state, target }) => target(new Done({ value: state.value }))) } }, @@ -67,18 +64,15 @@ describe("machine planner and runtime strategies", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Select), - initial: { - target: (to) => to.Count(), - resolve: ({ target }) => target(new Count({ value: 0 })) - } + initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) }).handle({ Count: { on: { Select: (to) => to.branches({ - negative: { target: to.none() }, - zero: { target: to.none() }, - positive: { target: to.none() } + negative: { target: to.none }, + zero: { target: to.none }, + positive: { target: to.none } }).resolve(({ event, select }) => event.value < 0 ? select.negative() @@ -112,10 +106,7 @@ describe("machine planner and runtime strategies", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Select), - initial: { - target: (to) => to.Count(), - resolve: ({ target }) => target(new Count({ value: 0 })) - } + initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) }).handle({ Count: { on: { @@ -182,14 +173,13 @@ describe("machine planner and runtime strategies", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Advance), - initial: { - target: (to) => to.Root.initial(), - resolve: ({ target }) => + initial: (to) => + to.Root.initial.resolve(({ target }) => target( new Root({}), (root) => root.Left(new Left({ value: 0 })).Right(new Right({ value: 0 })) ) - } + ) }).handle({ Root: { states: { @@ -234,14 +224,11 @@ describe("machine planner and runtime strategies", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Enter), - initial: { - target: (to) => to.Outside(), - resolve: ({ target }) => target(new Outside({})) - } + initial: (to) => to.Outside().resolve(({ target }) => target(new Outside({}))) }).handle({ Outside: { on: { - Enter: (to) => to.full.Opened.initial().resolve(({ target }) => target(new Opened({}))) + Enter: (to) => to.full.Opened.initial.resolve(({ target }) => target(new Opened({}))) } }, Opened: { @@ -317,10 +304,7 @@ describe("machine planner and runtime strategies", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) }).handle({ Idle: { always: (to) => to.full.Ready().resolve(({ target }) => target(new Ready({}))) @@ -343,10 +327,7 @@ describe("machine planner and runtime strategies", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Idle().resolve(({ target }) => target.from()) }).handle({ Idle: {} }) assert.strictEqual(ExecutionPlan.selectExecutionPlanForTesting(machine, "auto").strategy, "generic") @@ -379,10 +360,8 @@ describe("machine planner and runtime strategies", () => { states: states.states, events: Machine.events(), input: Input, - initial: { - target: (to) => to.Complete(), - resolve: ({ input: input, target }) => target(new Complete({ value: input.value })) - } + initial: (to) => + to.Complete().resolve(({ input: input, target }) => target(new Complete({ value: input.value }))) }).handle({ Complete: { output: ({ state }) => state.value } }) @@ -440,22 +419,17 @@ describe("machine planner and runtime strategies", () => { const definition = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Streaming(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Streaming().resolve(({ target }) => target.from()) }) const machine = definition.handle({ Streaming: { invoke: Machine.invoke({ id: "values", stream: () => Stream.fromIterable([1, 2, 3]), - onElement: { - target: Machine.targetless, - resolve: ({ element }) => { + onElement: (to) => + to.none.resolve(({ element }) => { seen.push(element) - } - }, + }), onDone: (to) => to.full.StreamDone().resolve(({ target }) => target(new StreamDone({ values: [...seen] }))) }) }, @@ -477,10 +451,7 @@ describe("machine planner and runtime strategies", () => { const definition = Machine.make({ states: states.states, events: Machine.events(Event), - initial: { - target: (to) => to.Count(), - resolve: ({ target }) => target(new Count({ value: 0 })) - } + initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) }) const events = definition.events const machine = definition.handle({ @@ -531,15 +502,12 @@ describe("machine planner and runtime strategies", () => { states: states.states, events: Events, emittedEvents: Emissions, - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) }).handle({ Idle: { on: { Publish: (to) => - to.none().resolve(({ parent, self }, enqueue) => { + to.none.resolve(({ parent, self }, enqueue) => { assert.strictEqual(parent, undefined) assert.ok(self.sessionId.startsWith("machine:")) enqueue.emit(Emissions.Published({ value } as never)) @@ -581,10 +549,7 @@ describe("machine planner and runtime strategies", () => { states: states.states, events: Machine.events(), emittedEvents: Emissions, - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) }).handle({ Idle: { entry: (_, enqueue) => { @@ -710,10 +675,7 @@ describe("machine planner and runtime strategies", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Load, Loaded), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) }).handle({ Idle: { on: { @@ -761,10 +723,7 @@ describe("machine planner and runtime strategies", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(new Loading({})) - } + initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({}))) }).handle({ Loading: { invoke: Machine.invoke({ @@ -805,10 +764,7 @@ describe("machine planner and runtime strategies", () => { const definition = Machine.make({ states: states.states, events: Machine.events(Reenter, Stale), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(new Loading({ epoch: 0 })) - } + initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ epoch: 0 }))) }) const machine = definition.handle({ Loading: { @@ -833,11 +789,11 @@ describe("machine planner and runtime strategies", () => { ) }) }, - onFailure: { target: Machine.targetless }, + onFailure: (to) => to.none, onSnapshot: (to) => to.branches({ stale: { title: "Worker is stale", target: to.full.Failed() }, - unchanged: { target: to.none() } + unchanged: { target: to.none } }).resolve(({ snapshot, select }) => snapshot.state === "stale" ? select.stale(new Failed({})) diff --git a/test/machine/ActivityLifecycleModel.test.ts b/test/machine/ActivityLifecycleModel.test.ts index be1a8bc..84a647a 100644 --- a/test/machine/ActivityLifecycleModel.test.ts +++ b/test/machine/ActivityLifecycleModel.test.ts @@ -73,10 +73,7 @@ describe("machine activity lifecycle model", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Enter, Leave, Restart), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) }).handle({ Idle: { on: { @@ -88,8 +85,8 @@ describe("machine activity lifecycle model", () => { id: "activity", address: Machine.childAddress("activity"), logic: probe.logic("active", { _tag: "Blocked" }), - onDone: { target: Machine.targetless }, - onFailure: { target: Machine.targetless } + onDone: (to) => to.none, + onFailure: (to) => to.none }), on: { Leave: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({}))), @@ -148,10 +145,7 @@ describe("machine activity lifecycle model", () => { states: states.states, events: Machine.events(), internalEvents: Machine.internalEvents(Completed), - initial: { - target: (to) => to.Active(), - resolve: ({ target }) => target(new Active({})) - } + initial: (to) => to.Active().resolve(({ target }) => target(new Active({}))) }).handle({ Active: { invoke: Machine.invoke({ @@ -159,7 +153,7 @@ describe("machine activity lifecycle model", () => { address: Machine.childAddress("immediate"), logic: probe.immediate("immediate", (epoch) => new Completed({ epoch })), onDone: (to) => to.full.Done().resolve(({ output, target }) => target(new Done({ epoch: output.epoch }))), - onFailure: { target: Machine.targetless } + onFailure: (to) => to.none }) }, Done: { @@ -187,10 +181,7 @@ describe("machine activity lifecycle model", () => { states: states.states, events: Machine.events(Restart, QueueBarrier), internalEvents: Machine.internalEvents(Completed), - initial: { - target: (to) => to.Active(), - resolve: ({ target }) => target(new EpochActive({ acknowledged: 0 })) - } + initial: (to) => to.Active().resolve(({ target }) => target(new EpochActive({ acknowledged: 0 }))) }).handle({ Active: { invoke: Machine.invoke({ @@ -200,8 +191,8 @@ describe("machine activity lifecycle model", () => { _tag: "StaleOnCancel", event: (epoch) => new Completed({ epoch }) }), - onDone: { target: Machine.targetless }, - onFailure: { target: Machine.targetless } + onDone: (to) => to.none, + onFailure: (to) => to.none }), on: { Restart: (to) => @@ -289,9 +280,8 @@ describe("machine activity lifecycle model", () => { } }, events: Machine.events(LeaveLeft), - initial: { - target: (to) => to.Root.initial(), - resolve: () => ({ + initial: (to) => + to.Root.initial.resolve(() => ({ path: "Root" as const, value: new Root({}), states: { @@ -306,8 +296,7 @@ describe("machine activity lifecycle model", () => { state: { path: "Root.right.active" as const, value: new RightActive({}) } } } - }) - } + })) }).handle({ Root: { states: { @@ -318,8 +307,8 @@ describe("machine activity lifecycle model", () => { id: "left-activity", address: Machine.childAddress("left-activity"), logic: probe.logic("left", { _tag: "Blocked" }), - onDone: { target: Machine.targetless }, - onFailure: { target: Machine.targetless } + onDone: (to) => to.none, + onFailure: (to) => to.none }), on: { LeaveLeft: (to) => to.local.idle().resolve(({ target }) => target(new LeftIdle({}))) @@ -334,8 +323,8 @@ describe("machine activity lifecycle model", () => { id: "right-activity", address: Machine.childAddress("right-activity"), logic: probe.logic("right", { _tag: "Blocked" }), - onDone: { target: Machine.targetless }, - onFailure: { target: Machine.targetless } + onDone: (to) => to.none, + onFailure: (to) => to.none }) } } @@ -375,10 +364,7 @@ describe("machine activity lifecycle model", () => { states: states.states, events: Machine.events(Leave), internalEvents: Machine.internalEvents(TimerFired), - initial: { - target: (to) => to.Active(), - resolve: ({ target }) => target(new Active({})) - } + initial: (to) => to.Active().resolve(({ target }) => target(new Active({}))) }).handle({ Idle: {}, Active: { @@ -387,8 +373,8 @@ describe("machine activity lifecycle model", () => { id: "timed-activity", address: Machine.childAddress("timed-activity"), logic: probe.logic("timed", { _tag: "Blocked" }), - onDone: { target: Machine.targetless }, - onFailure: { target: Machine.targetless } + onDone: (to) => to.none, + onFailure: (to) => to.none }), Machine.invoke({ id: "deadline", @@ -424,10 +410,7 @@ describe("machine activity lifecycle model", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Active(), - resolve: ({ target }) => target(new Active({})) - } + initial: (to) => to.Active().resolve(({ target }) => target(new Active({}))) }).handle({ Active: { invoke: [ @@ -435,9 +418,9 @@ describe("machine activity lifecycle model", () => { id: "failing", address: Machine.childAddress("failing"), logic: probe.logic("failing", { _tag: "Failure" }), - onDone: { target: Machine.targetless }, + onDone: (to) => to.none, onFailure: (to) => - to.none().resolve(({ error }) => { + to.none.resolve(({ error }) => { throw error }) }), @@ -445,8 +428,8 @@ describe("machine activity lifecycle model", () => { id: "sibling", address: Machine.childAddress("sibling"), logic: probe.logic("sibling", { _tag: "Blocked" }), - onDone: { target: Machine.targetless }, - onFailure: { target: Machine.targetless } + onDone: (to) => to.none, + onFailure: (to) => to.none }) ] } @@ -476,10 +459,7 @@ describe("machine activity lifecycle model", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Active(), - resolve: ({ target }) => target(new Active({})) - } + initial: (to) => to.Active().resolve(({ target }) => target(new Active({}))) }).handle({ Active: { invoke: [ @@ -487,15 +467,15 @@ describe("machine activity lifecycle model", () => { id: "first", address: Machine.childAddress("first"), logic: probe.logic("first", { _tag: "Blocked" }), - onDone: { target: Machine.targetless }, - onFailure: { target: Machine.targetless } + onDone: (to) => to.none, + onFailure: (to) => to.none }), Machine.invoke({ id: "second", address: Machine.childAddress("second"), logic: probe.logic("second", { _tag: "Blocked" }), - onDone: { target: Machine.targetless }, - onFailure: { target: Machine.targetless } + onDone: (to) => to.none, + onFailure: (to) => to.none }) ] } diff --git a/test/machine/Annotations.test.ts b/test/machine/Annotations.test.ts index 7d5f509..67373af 100644 --- a/test/machine/Annotations.test.ts +++ b/test/machine/Annotations.test.ts @@ -47,10 +47,8 @@ const States = Machine.states({ const machine = Machine.make({ states: States.states, events: Machine.events(), - initial: { - target: (to) => to.Workflow.initial(), - resolve: ({ target }) => target(new Workflow({}), (workflow) => workflow.Idle(new Idle({}))) - } + initial: (to) => + to.Workflow.initial.resolve(({ target }) => target(new Workflow({}), (workflow) => workflow.Idle(new Idle({})))) }) describe("Machine state annotations", () => { diff --git a/test/machine/AnnotationsVisualization.test.ts b/test/machine/AnnotationsVisualization.test.ts index 3c9a67e..f039296 100644 --- a/test/machine/AnnotationsVisualization.test.ts +++ b/test/machine/AnnotationsVisualization.test.ts @@ -34,10 +34,8 @@ const States = Machine.states({ const machine = Machine.make({ states: States.states, events: Machine.events(), - initial: { - target: (to) => to.Workflow.initial(), - resolve: ({ target }) => target(new Workflow({}), (workflow) => workflow.Idle(new Idle({}))) - } + initial: (to) => + to.Workflow.initial.resolve(({ target }) => target(new Workflow({}), (workflow) => workflow.Idle(new Idle({})))) }) const renderMachine = makeTextRenderer< diff --git a/test/machine/Choice.test.ts b/test/machine/Choice.test.ts index 6598256..53dcd3d 100644 --- a/test/machine/Choice.test.ts +++ b/test/machine/Choice.test.ts @@ -25,10 +25,7 @@ let branchFactoryCalls = 0 const machine = Machine.make({ states: States.states, events: Machine.events(Recheck), - initial: { - target: (to) => to.Flow.initial(), - resolve: ({ target }) => target(new Flow({ score: 80 }), (flow) => flow.Routing()) - } + initial: (to) => to.Flow.initial.resolve(({ target }) => target(new Flow({ score: 80 }), (flow) => flow.Routing())) }).handle({ Flow: { states: { @@ -58,7 +55,7 @@ const machine = Machine.make({ Approved: { on: { Recheck: (to) => - to.branch.Flow.initial().resolve(({ event, target }) => target(new Flow({ score: event.score }))) + to.branch.Flow.initial.resolve(({ event, target }) => target(new Flow({ score: event.score }))) } } } @@ -154,10 +151,8 @@ describe("Machine choice pseudo-states", () => { const chained = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Flow.initial(), - resolve: ({ target }) => target(new Flow({ score: 80 }), (flow) => flow.First()) - } + initial: (to) => + to.Flow.initial.resolve(({ target }) => target(new Flow({ score: 80 }), (flow) => flow.First())) }).handle({ Flow: { states: { @@ -204,10 +199,8 @@ describe("Machine choice pseudo-states", () => { id: "ChoiceLoopMachine", states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Flow.initial(), - resolve: ({ target }) => target(new Flow({ score: 80 }), (flow) => flow.First()) - } + initial: (to) => + to.Flow.initial.resolve(({ target }) => target(new Flow({ score: 80 }), (flow) => flow.First())) }).handle({ Flow: { states: { @@ -243,14 +236,13 @@ describe("Machine choice pseudo-states", () => { const alwaysMachine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Flow.initial(), - resolve: ({ target }) => + initial: (to) => + to.Flow.initial.resolve(({ target }) => target( new Flow({ score: 10 }), (flow) => flow.Approved(new Approved({})) ) - } + ) }).handle({ Flow: { states: { @@ -301,10 +293,8 @@ describe("Machine choice pseudo-states", () => { const completion = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Flow.initial(), - resolve: ({ target }) => target(new Flow({ score: 0 }), (flow) => flow.Done(new Done({}))) - } + initial: (to) => + to.Flow.initial.resolve(({ target }) => target(new Flow({ score: 0 }), (flow) => flow.Done(new Done({})))) }).handle({ Flow: { onDone: (to) => to.full.Flow().resolve(({ state, target }) => target(state, (flow) => flow.Routing())), @@ -361,9 +351,8 @@ describe("Machine choice pseudo-states", () => { const parallel = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Board.initial(), - resolve: ({ target }) => + initial: (to) => + to.Board.initial.resolve(({ target }) => target( new Board({}), (board) => @@ -371,7 +360,7 @@ describe("Machine choice pseudo-states", () => { .Left(new Left({}), (left) => left.Routing()) .Right(new Right({}), (right) => right.Routing()) ) - } + ) }).handle({ Board: { states: { @@ -424,10 +413,8 @@ describe("Machine choice pseudo-states", () => { const history = Machine.make({ states: states.states, events: Machine.events(Leave, Resume), - initial: { - target: (to) => to.Flow.initial(), - resolve: ({ target }) => target(new Flow({ score: 1 }), (flow) => flow.Active(new Active({}))) - } + initial: (to) => + to.Flow.initial.resolve(({ target }) => target(new Flow({ score: 1 }), (flow) => flow.Active(new Active({})))) }).handle({ Flow: { history: { @@ -442,7 +429,7 @@ describe("Machine choice pseudo-states", () => { } }, Routing: { - choice: (to) => to.history.Flow.Recent().resolve(({ target }) => target()) + choice: (to) => to.history.Flow.Recent.resolve(({ target }) => target()) } } }, @@ -481,10 +468,8 @@ describe("Machine choice pseudo-states", () => { const initialHistory = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Flow.initial(), - resolve: ({ target }) => target(new Flow({ score: 1 }), (flow) => flow.Routing()) - } + initial: (to) => + to.Flow.initial.resolve(({ target }) => target(new Flow({ score: 1 }), (flow) => flow.Routing())) }).handle({ Flow: { history: { @@ -498,7 +483,7 @@ describe("Machine choice pseudo-states", () => { }, states: { Routing: { - choice: (to) => to.history.Flow.Recent().resolve(({ target }) => target()) + choice: (to) => to.history.Flow.Recent.resolve(({ target }) => target()) } } } @@ -530,10 +515,7 @@ describe("Machine choice pseudo-states", () => { const historyChoice = Machine.make({ states: states.states, events: Machine.events(Resume), - initial: { - target: (to) => to.Outside(), - resolve: ({ target }) => target(new Outside({})) - } + initial: (to) => to.Outside().resolve(({ target }) => target(new Outside({}))) }).handle({ Flow: { history: { @@ -549,7 +531,7 @@ describe("Machine choice pseudo-states", () => { }, Outside: { on: { - FallbackChoiceResume: (to) => to.history.Flow.Recent().resolve(({ target }) => target()) + FallbackChoiceResume: (to) => to.history.Flow.Recent.resolve(({ target }) => target()) } } }) diff --git a/test/machine/DeepHandlers.test.ts b/test/machine/DeepHandlers.test.ts index c4b562e..8559db5 100644 --- a/test/machine/DeepHandlers.test.ts +++ b/test/machine/DeepHandlers.test.ts @@ -105,10 +105,7 @@ const initial = (() => { const machine = Machine.make({ states: States.states, events: Machine.events(Advance), - initial: { - target: (to) => to.n0.initial(), - resolve: () => initial - } + initial: (to) => to.n0.initial.resolve(() => initial) }).handle({ n0: { states: { diff --git a/test/machine/History.test.ts b/test/machine/History.test.ts index aa1920d..392b8d3 100644 --- a/test/machine/History.test.ts +++ b/test/machine/History.test.ts @@ -124,18 +124,15 @@ const makeCheckoutMachine = ( Machine.make({ states: CheckoutStates.states, events: Machine.events(Leave, ResumeShallow, ResumeDeep, GoShipping, EnterVerifying, ReenterHistory), - initial: (initial.path === "checkout" - ? { - target: (to: any) => to.checkout.initial(), - resolve: ({ target }: any) => - target(new Checkout({ orderId: "initial" }), (checkout: any) => - checkout.shipping(new Shipping({ address: "initial" }))) - } - : { - target: (to: any) => - to.support(), - resolve: () => initial - }) as any + initial: ((to: any) => + initial.path === "checkout" + ? to.checkout.initial.resolve(({ target }: any) => + target( + new Checkout({ orderId: "initial" }), + (checkout: any) => checkout.shipping(new Shipping({ address: "initial" })) + ) + ) + : to.support().resolve(() => initial)) as any }).handle({ checkout: { entry: () => { @@ -162,7 +159,7 @@ const makeCheckoutMachine = ( Leave: (to) => to.full.support().resolve(({ target }) => target(new Support({ ticket: "ticket-1" }))), GoShipping: (to) => to.local.shipping().resolve(({ event, target }) => target(new Shipping({ address: event.address }))), - ReenterHistory: (to) => to.history.checkout.exact().resolve(({ target }) => target(), { reenter: true }) + ReenterHistory: (to) => to.history.checkout.exact.resolve(({ target }) => target(), { reenter: true }) }, states: { shipping: { @@ -208,8 +205,8 @@ const makeCheckoutMachine = ( lifecycle?.push("exit:support") }, on: { - ResumeShallow: (to) => to.history.checkout.recent().resolve(({ target }) => target()), - ResumeDeep: (to) => to.history.checkout.exact().resolve(({ target }) => target()) + ResumeShallow: (to) => to.history.checkout.recent.resolve(({ target }) => target()), + ResumeDeep: (to) => to.history.checkout.exact.resolve(({ target }) => target()) } } }) @@ -304,14 +301,13 @@ const makeWorkspaceMachine = (initialized: Array) => Machine.make({ states: WorkspaceStates.states, events: Machine.events(LeaveWorkspace, ResumeWorkspaceShallow, ResumeWorkspaceDeep), - initial: { - target: (to) => to.workspace.initial(), - resolve: ({ target }) => + initial: (to) => + to.workspace.initial.resolve(({ target }) => target(new Workspace({ id: "initial" }), (workspace) => workspace .editor(new Editor({ documentId: "initial" }), (editor) => editor.writing(new Writing({ draft: "" }))) .sidebar(new Sidebar({ width: 0 }), (sidebar) => sidebar.files(new Files({ directory: "/" })))) - } + ) }).handle({ workspace: { history: { @@ -368,8 +364,8 @@ const makeWorkspaceMachine = (initialized: Array) => }, away: { on: { - ResumeWorkspaceShallow: (to) => to.history.workspace.recent().resolve(({ target }) => target()), - ResumeWorkspaceDeep: (to) => to.history.workspace.exact().resolve(({ target }) => target()) + ResumeWorkspaceShallow: (to) => to.history.workspace.recent.resolve(({ target }) => target()), + ResumeWorkspaceDeep: (to) => to.history.workspace.exact.resolve(({ target }) => target()) } } }) @@ -418,9 +414,8 @@ const nestedParallelSnapshot: Machine.Machine.Snapshot to.workspace.initial(), - resolve: ({ target }) => + initial: (to) => + to.workspace.initial.resolve(({ target }) => target( new Workspace({ id: "workspace-1" }), (workspace) => @@ -431,7 +426,7 @@ const nestedHistoryMachine = Machine.make({ ) .sidebar(new Search({ query: "untouched" })) ) - } + ) }).handle({ workspace: { states: { @@ -455,13 +450,13 @@ const nestedHistoryMachine = Machine.make({ preview: { on: { RestoreEditor: (to) => - to.history.workspace.editor.exact().resolve(({ target }) => target(), { reenter: true }), - DefaultEditor: (to) => to.history.workspace.editor.exact().resolve(({ target }) => target()) + to.history.workspace.editor.exact.resolve(({ target }) => target(), { reenter: true }), + DefaultEditor: (to) => to.history.workspace.editor.exact.resolve(({ target }) => target()) } }, writing: { on: { - DefaultEditor: (to) => to.history.workspace.editor.exact().resolve(({ target }) => target()) + DefaultEditor: (to) => to.history.workspace.editor.exact.resolve(({ target }) => target()) } } } diff --git a/test/machine/InitialEntry.test.ts b/test/machine/InitialEntry.test.ts index 4f4f20e..2df186b 100644 --- a/test/machine/InitialEntry.test.ts +++ b/test/machine/InitialEntry.test.ts @@ -41,15 +41,12 @@ const makeMachine = () => Machine.make({ states: States.states, events: Machine.events(Open, OpenInvalid), - initial: { - target: (to) => to.closed(), - resolve: ({ target }) => target(new Closed({})) - } + initial: (to) => to.closed().resolve(({ target }) => target(new Closed({}))) }).handle({ closed: { on: { - Open: (to) => to.full.opened.initial().resolve(({ target }) => target.from({ id: "team-1" })), - OpenInvalid: (to) => to.full.opened.initial().resolve(({ target }) => target.from({ id: "" })) + Open: (to) => to.full.opened.initial.resolve(({ target }) => target.from({ id: "team-1" })), + OpenInvalid: (to) => to.full.opened.initial.resolve(({ target }) => target.from({ id: "" })) } }, opened: { @@ -77,14 +74,11 @@ const makeParallelMachine = () => Machine.make({ states: ParallelStates.states, events: Machine.events(EnterDashboard), - initial: { - target: (to) => to.outside(), - resolve: ({ target }) => target(new Outside({})) - } + initial: (to) => to.outside().resolve(({ target }) => target(new Outside({}))) }).handle({ outside: { on: { - EnterDashboard: (to) => to.full.dashboard.initial().resolve(({ target }) => target(new Dashboard({}))) + EnterDashboard: (to) => to.full.dashboard.initial.resolve(({ target }) => target(new Dashboard({}))) } }, dashboard: { @@ -113,14 +107,11 @@ const makeChoiceMachine = () => Machine.make({ states: ChoiceStates.states, events: Machine.events(EnterFlow), - initial: { - target: (to) => to.outside(), - resolve: ({ target }) => target(new Outside({})) - } + initial: (to) => to.outside().resolve(({ target }) => target(new Outside({}))) }).handle({ outside: { on: { - EnterFlow: (to) => to.full.flow.initial().resolve(({ target }) => target(new Flow({}))) + EnterFlow: (to) => to.full.flow.initial.resolve(({ target }) => target(new Flow({}))) } }, flow: { @@ -144,14 +135,11 @@ const makeStructuralMachine = () => Machine.make({ states: StructuralStates.states, events: Machine.events(EnterFlow), - initial: { - target: (to) => to.outside(), - resolve: ({ target }) => target(new Outside({})) - } + initial: (to) => to.outside().resolve(({ target }) => target(new Outside({}))) }).handle({ outside: { on: { - EnterFlow: (to) => to.full.group.initial().resolve(({ target }) => target.from()) + EnterFlow: (to) => to.full.group.initial.resolve(({ target }) => target.from()) } } }) @@ -174,17 +162,14 @@ const makeNestedMachine = () => Machine.make({ states: NestedStates.states, events: Machine.events(OpenLocal, OpenBranch), - initial: { - target: (to) => to.root.initial(), - resolve: ({ target }) => target.from((root) => root.closed(new Closed({}))) - } + initial: (to) => to.root.initial.resolve(({ target }) => target.from((root) => root.closed(new Closed({})))) }).handle({ root: { states: { closed: { on: { - OpenLocal: (to) => to.local.opened.initial().resolve(({ target }) => target.from({ id: "local" })), - OpenBranch: (to) => to.branch.root.opened.initial().resolve(({ target }) => target.from({ id: "branch" })) + OpenLocal: (to) => to.local.opened.initial.resolve(({ target }) => target.from({ id: "local" })), + OpenBranch: (to) => to.branch.root.opened.initial.resolve(({ target }) => target.from({ id: "branch" })) } }, opened: { @@ -195,6 +180,47 @@ const makeNestedMachine = () => }) describe("declared initial entry", () => { + it.effect("captures the target-first selector once and evaluates its resolver only when planned", () => + Effect.gen(function*() { + let captures = 0 + let resolves = 0 + const definition = Machine.make({ + states: { closed: Closed }, + events: Machine.events(), + initial: (to) => { + captures++ + return to.closed().resolve(({ target }) => { + resolves++ + return target.from() + }) + } + }) + + assert.strictEqual(captures, 1) + assert.strictEqual(resolves, 0) + + const machine = definition.handle({ closed: {} }) + const first = yield* Machine.planInitial(machine) + const second = yield* Machine.planInitial(machine) + + assert.deepStrictEqual(first.state, { path: "closed", value: new Closed({}) }) + assert.deepStrictEqual(second.state, first.state) + assert.strictEqual(captures, 1) + assert.strictEqual(resolves, 2) + })) + + it.effect("default-constructs a bare initial destination", () => + Effect.gen(function*() { + const machine = Machine.make({ + states: { closed: Closed }, + events: Machine.events(), + initial: (to) => to.closed() + }).handle({ closed: {} }) + + const initial = yield* Machine.planInitial(machine) + assert.deepStrictEqual(initial.state, { path: "closed", value: new Closed({}) }) + })) + it.effect("enters a compound state's declared initial child and decodes builder inputs", () => Effect.gen(function*() { const machine = makeMachine() diff --git a/test/machine/Inspection.test.ts b/test/machine/Inspection.test.ts index 3b3f859..cce14d5 100644 --- a/test/machine/Inspection.test.ts +++ b/test/machine/Inspection.test.ts @@ -37,14 +37,13 @@ const States = Machine.states({ const machine = Machine.make({ states: States.states, events: Machine.events(), - initial: { - target: (to) => to.root.initial(), - resolve: ({ target }) => + initial: (to) => + to.root.initial.resolve(({ target }) => target(new Root({}), (root) => root .flow(new Flow({}), (flow) => flow.idle(new Idle({}))) .side(new Side({}))) - } + ) }) const ChoiceStates = Machine.states({ @@ -61,10 +60,7 @@ const ChoiceStates = Machine.states({ const choiceMachine = Machine.make({ states: ChoiceStates.states, events: Machine.events(), - initial: { - target: (to) => to.Flow.initial(), - resolve: ({ target }) => target(new ChoiceFlow({}), (flow) => flow.Routing()) - } + initial: (to) => to.Flow.initial.resolve(({ target }) => target(new ChoiceFlow({}), (flow) => flow.Routing())) }).handle({ Flow: { states: { diff --git a/test/machine/Invoke.test.ts b/test/machine/Invoke.test.ts index 8f43fad..f77cdee 100644 --- a/test/machine/Invoke.test.ts +++ b/test/machine/Invoke.test.ts @@ -29,10 +29,7 @@ describe("inline invoke", () => { const machine = Machine.make({ states: States.states, events: Machine.events(), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Loading().resolve(({ target }) => target.from()) }).handle({ Loading: { invoke: Machine.invoke({ @@ -58,22 +55,17 @@ describe("inline invoke", () => { const definition = Machine.make({ states: states.states, events: Machine.events(Add), - initial: { - target: (to) => to.Collecting(), - resolve: ({ target }) => target(new Collecting({ values: [] })) - } + initial: (to) => to.Collecting().resolve(({ target }) => target(new Collecting({ values: [] }))) }) const machine = definition.handle({ Collecting: { invoke: Machine.invoke({ id: "numbers", stream: () => Stream.fromIterable([1, 2, 3]), - onElement: { - target: Machine.targetless, - resolve: ({ element }, enqueue) => { + onElement: (to) => + to.none.resolve(({ element }, enqueue) => { enqueue.raise(new Add({ value: element })) - } - }, + }), onDone: (to) => to.full.Complete().resolve(({ state, target }) => target(new Complete({ value: state.values.join(",") }))) }), @@ -140,17 +132,14 @@ describe("inline invoke", () => { const definition = Machine.make({ states: States.states, events: Machine.events(), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Loading().resolve(({ target }) => target.from()) }) const machine = definition.handle({ Loading: { invoke: Machine.invoke({ id: "updates", stream: () => Stream.fail("offline"), - onDone: { target: Machine.targetless }, + onDone: (to) => to.none, onFailure: (to) => to.full.Failed().resolve(({ error, target }) => target(new Failed({ message: error }))) }) }, @@ -176,17 +165,14 @@ describe("inline invoke", () => { const definition = Machine.make({ states: States.states, events: Machine.events(), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Loading().resolve(({ target }) => target.from()) }) const machine = definition.handle({ Loading: { invoke: Machine.invoke({ id: "updates", stream: () => Stream.die(defect), - onDone: { target: Machine.targetless } + onDone: (to) => to.none }) }, Complete: {}, @@ -214,23 +200,18 @@ describe("inline invoke", () => { const definition = Machine.make({ states: States.states, events: Machine.events(FinishStream), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Loading().resolve(({ target }) => target.from()) }) const machine = definition.handle({ Loading: { invoke: Machine.invoke({ id: "updates", stream: () => source, - onElement: { - target: Machine.targetless, - resolve: ({ element }, enqueue) => { + onElement: (to) => + to.none.resolve(({ element }, enqueue) => { enqueue.raise(new FinishStream({ value: element })) - } - }, - onDone: { target: Machine.targetless } + }), + onDone: (to) => to.none }), on: { FinishStream: (to) => @@ -258,10 +239,7 @@ describe("inline invoke", () => { const machine = Machine.make({ states: States.states, events: Machine.events(), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Loading().resolve(({ target }) => target.from()) }).handle({ Loading: { invoke: Machine.invoke({ @@ -296,10 +274,7 @@ describe("inline invoke", () => { const machine = Machine.make({ states: States.states, events: Machine.events(), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Loading().resolve(({ target }) => target.from()) }).handle({ Loading: { invoke: Machine.invoke({ @@ -323,10 +298,7 @@ describe("inline invoke", () => { const machine = Machine.make({ states: States.states, events: Machine.events(Start), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Idle().resolve(({ target }) => target.from()) }).handle({ Idle: { on: { @@ -339,7 +311,7 @@ describe("inline invoke", () => { effect: (): Effect.Effect => { throw defect }, - onDone: { target: Machine.targetless } + onDone: (to) => to.none }) }, Complete: {}, @@ -366,10 +338,7 @@ describe("inline invoke", () => { const machine = Machine.make({ states: States.states, events: Machine.events(Start), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Idle().resolve(({ target }) => target.from()) }).handle({ Idle: { on: { diff --git a/test/machine/LiveInspection.test.ts b/test/machine/LiveInspection.test.ts index a1befc8..7b810e3 100644 --- a/test/machine/LiveInspection.test.ts +++ b/test/machine/LiveInspection.test.ts @@ -19,10 +19,7 @@ const machine = Machine.make({ states: states.states, events: Events, emittedEvents: Emissions, - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) }).handle({ Idle: { on: { @@ -105,12 +102,10 @@ describe("Machine live inspection", () => { const invalid = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Idle(), - resolve: () => { + initial: (to) => + to.Idle().resolve(() => { throw new Error("boom") - } - } + }) }).handle({ Idle: {} }) const prepared = yield* Machine.prepare(invalid) const collected = yield* prepared.inspection.pipe( @@ -133,10 +128,7 @@ describe("Machine live inspection", () => { id: "activity-root", states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) }).handle({ Idle: { invoke: Machine.invoke({ id: "worker", effect: () => Effect.never }) @@ -185,16 +177,13 @@ describe("Machine live inspection", () => { id: "stream-activity-root", states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) }).handle({ Idle: { invoke: Machine.invoke({ id: "updates", stream: () => Stream.never, - onDone: { target: Machine.targetless } + onDone: (to) => to.none }) } }) @@ -234,15 +223,12 @@ describe("Machine live inspection", () => { states: childStates.states, events: ChildEvents, parentEvents: ParentEvents, - initial: { - target: (to) => to.ChildIdle(), - resolve: ({ target }) => target(new ChildIdle({})) - } + initial: (to) => to.ChildIdle().resolve(({ target }) => target(new ChildIdle({}))) }).handle({ ChildIdle: { on: { Trigger: (to) => - to.none().resolve(({ parent }, enqueue) => { + to.none.resolve(({ parent }, enqueue) => { if (parent !== undefined) enqueue.sendTo(parent, ParentEvents.ChildReady()) return undefined }) @@ -258,10 +244,7 @@ describe("Machine live inspection", () => { id: "parent-machine", states: parentStates.states, events: Machine.events(ParentEvents), - initial: { - target: (to) => to.ParentIdle(), - resolve: ({ target }) => target(new ParentIdle({})) - } + initial: (to) => to.ParentIdle().resolve(({ target }) => target(new ParentIdle({}))) }).handle({ ParentIdle: { invoke: Machine.invoke({ child: Child }), diff --git a/test/machine/LocalTargetWith.test.ts b/test/machine/LocalTargetWith.test.ts index 7964aac..f11842b 100644 --- a/test/machine/LocalTargetWith.test.ts +++ b/test/machine/LocalTargetWith.test.ts @@ -27,15 +27,13 @@ describe("local compound target selection", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Events), - initial: { - target: (to) => to.search.initial(), - resolve: ({ target }) => target.from({ query: "" }, (search) => search.Idle.from()) - } + initial: (to) => + to.search.initial.resolve(({ target }) => target.from({ query: "" }, (search) => search.Idle.from())) }).handle({ search: { on: { UpdateQuery: (to) => - to.local.with().resolve( + to.local.with.resolve( ({ event, target }) => target.from({ query: event.query }, (search) => search.Updated.from()), { reenter: true } ) @@ -116,10 +114,10 @@ describe("local compound target selection", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.search.initial(), - resolve: ({ target }) => target.from({ query: "pending" }, (search) => search.Searching.from()) - } + initial: (to) => + to.search.initial.resolve(({ target }) => + target.from({ query: "pending" }, (search) => search.Searching.from()) + ) }).handle({ search: { states: { @@ -128,7 +126,7 @@ describe("local compound target selection", () => { id: "search", effect: () => Effect.succeed("resolved"), onDone: (to) => - to.local.with().resolve(({ output, target }) => + to.local.with.resolve(({ output, target }) => target.from({ query: output }, (search) => search.Updated.from()) ) }) @@ -178,10 +176,7 @@ describe("local compound target selection", () => { Machine.make({ states: states.states, events: Machine.events(Event), - initial: { - target: (to) => to.flow.initial(), - resolve: ({ target }) => target.from((flow) => flow.Idle.from()) - } + initial: (to) => to.flow.initial.resolve(({ target }) => target.from((flow) => flow.Idle.from())) }).handle({ flow: { states: { diff --git a/test/machine/Machine.test.ts b/test/machine/Machine.test.ts index 75a2267..51e9133 100644 --- a/test/machine/Machine.test.ts +++ b/test/machine/Machine.test.ts @@ -108,14 +108,9 @@ describe("Machine", () => { const definition = Machine.make({ states: states.states, events: Machine.events(Ping), - initial: { - target: (to) => to.Stable(), - resolve: ({ target }) => target(new Stable({})) - } + initial: (to) => to.Stable().resolve(({ target }) => target(new Stable({}))) }) - const transition = { target: Machine.targetless } - - const handlingPing = definition.handle({ Stable: { on: { Ping: transition } } }) + const handlingPing = definition.handle({ Stable: { on: { Ping: (to) => to.none } } }) const ignoringPing = definition.handle({ Stable: {} }) assert.isFalse("handle" in handlingPing) @@ -133,25 +128,26 @@ describe("Machine", () => { class Ping extends Schema.TaggedClass("Ping")("Ping", {}) {} const states = Machine.states({ Stable }) let captures = 0 + let resolves = 0 const machine = Machine.make({ states: states.states, events: Machine.events(Ping), - initial: { - target: (to) => to.Stable(), - resolve: ({ target }) => target(new Stable({})) - } + initial: (to) => to.Stable().resolve(({ target }) => target(new Stable({}))) }).handle({ Stable: { on: { Ping: (to) => { captures++ - return to.none() + return to.none.resolve(() => { + resolves++ + }) } } } }) assert.strictEqual(captures, 1) + assert.strictEqual(resolves, 0) assert.deepStrictEqual(Machine.transitionDefinitions(machine), [{ source: "Stable", @@ -167,6 +163,7 @@ describe("Machine", () => { const initial = yield* Machine.planInitial(machine) const planned = yield* Machine.plan(machine, initial.state, new Ping({})) + assert.strictEqual(resolves, 1) assert.strictEqual(planned.microsteps.length, 1) const step = planned.microsteps[0]! assert.isFalse(step.changed) @@ -182,10 +179,7 @@ describe("Machine", () => { const machine = Machine.make({ states: { Idle, Done }, events: Machine.events(Finish), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Idle().resolve(({ target }) => target.from()) }).handle({ Idle: { on: { Finish: (to) => to.full.Done() } }, Done: {} @@ -206,12 +200,9 @@ describe("Machine", () => { const machine = Machine.make({ states: { Stable }, events: Machine.events(Restart), - initial: { - target: (to) => to.Stable(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Stable().resolve(({ target }) => target.from()) }).handle({ - Stable: { on: { Restart: (to) => to.none().reenter() } } + Stable: { on: { Restart: (to) => to.none.reenter() } } }) assert.strictEqual(Machine.transitionDefinitions(machine)[0]?.reenter, true) @@ -233,10 +224,7 @@ describe("Machine", () => { const definition = Machine.make({ states: states.states, events: Machine.events(Ping), - initial: { - target: (to) => to.Stable(), - resolve: ({ target }) => target(new Stable({})) - } + initial: (to) => to.Stable().resolve(({ target }) => target(new Stable({}))) }) const machine = definition.handle({ Stable: { @@ -244,7 +232,7 @@ describe("Machine", () => { Ping: (to) => { captures++ const captured = { - unchanged: { target: to.none() }, + unchanged: { target: to.none }, refresh: { title: "Refresh stable state", target: to.full.Stable() } } declarations = captured @@ -297,10 +285,7 @@ describe("Machine", () => { Machine.make({ states: { Stable }, events: Machine.events(Ping), - initial: { - target: (to) => to.Stable(), - resolve: ({ target }) => target(new Stable({})) - } + initial: (to) => to.Stable().resolve(({ target }) => target(new Stable({}))) }) const handle = (branches: (to: any) => object) => () => makeDefinition().handle({ @@ -312,13 +297,13 @@ describe("Machine", () => { }) assert.throws(handle(() => ({})), /requires a branch/) - assert.throws(handle((to) => [{ target: to.none() }]), /requires a branch record/) - assert.throws(handle((to) => ({ "": { target: to.none() } })), /non-index string branch keys/) - assert.throws(handle((to) => ({ 0: { target: to.none() } })), /non-index string branch keys/) - assert.throws(handle((to) => ({ invalid: { title: "", target: to.none() } })), /non-empty string/) + assert.throws(handle((to) => [{ target: to.none }]), /requires a branch record/) + assert.throws(handle((to) => ({ "": { target: to.none } })), /non-index string branch keys/) + assert.throws(handle((to) => ({ 0: { target: to.none } })), /non-index string branch keys/) + assert.throws(handle((to) => ({ invalid: { title: "", target: to.none } })), /non-empty string/) assert.throws(handle(() => ({ invalid: { target: undefined } })), /must select exactly one target/) assert.throws( - handle((to) => ({ valid: { target: to.none() }, [Symbol("invalid")]: { target: to.none() } })), + handle((to) => ({ valid: { target: to.none }, [Symbol("invalid")]: { target: to.none } })), /cannot use symbol keys/ ) }) @@ -332,19 +317,16 @@ describe("Machine", () => { const machine = Machine.make({ states: { Stable }, events: Machine.events(Capture, Reuse), - initial: { - target: (to) => to.Stable(), - resolve: ({ target }) => target(new Stable({})) - } + initial: (to) => to.Stable().resolve(({ target }) => target(new Stable({}))) }).handle({ Stable: { on: { Capture: (to) => - to.branches({ unchanged: { target: to.none() } }).resolve(({ select }) => { + to.branches({ unchanged: { target: to.none } }).resolve(({ select }) => { captured = select.unchanged() return captured as any }), - Reuse: (to) => to.branches({ unchanged: { target: to.none() } }).resolve(() => captured as any) + Reuse: (to) => to.branches({ unchanged: { target: to.none } }).resolve(() => captured as any) } } }) @@ -520,10 +502,7 @@ describe("Machine", () => { states: states.states, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }) const planned = yield* Machine.planInitial(machine, { userId: "user-1" }) @@ -537,10 +516,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({ userId: "user-1" })) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) }) assert.strictEqual(Machine.isMachine(machine), true) @@ -554,10 +530,7 @@ describe("Machine", () => { const definition = Machine.make({ states: states.states, events: Machine.events(Convert), - initial: { - target: (to) => to.Submit(), - resolve: ({ target }) => target(new Submit({ value: "loaded" })) - } + initial: (to) => to.Submit().resolve(({ target }) => target(new Submit({ value: "loaded" }))) }) const machine = definition.handle({ Submit: { @@ -587,10 +560,7 @@ describe("Machine", () => { states: states.states, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -617,10 +587,7 @@ describe("Machine", () => { const machine = Machine.make({ states: defined.states, events: Machine.events(Submit), - initial: { - target: (to) => to.idle(), - resolve: ({ target }) => target(new Idle({ userId: "user-1" })) - } + initial: (to) => to.idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) }) const planned = yield* Machine.planInitial(machine) @@ -742,14 +709,13 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Authorize), - initial: { - target: (to) => to.payment.initial(), - resolve: ({ target }) => + initial: (to) => + to.payment.initial.resolve(({ target }) => target( payment, (payment) => payment.entering(entering) ) - } + ) }) const planned = yield* Machine.planInitial(machine) @@ -794,9 +760,8 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(ReserveInventory), - initial: { - target: (to) => to.fulfillment.initial(), - resolve: ({ target }) => + initial: (to) => + to.fulfillment.initial.resolve(({ target }) => target( fulfillment, (fulfillment) => @@ -810,7 +775,7 @@ describe("Machine", () => { (shipping) => shipping.quoting(quoting) ) ) - } + ) }) const planned = yield* Machine.planInitial(machine) @@ -865,17 +830,14 @@ describe("Machine", () => { states: states.states, events, internalEvents, - initial: { - target: (to) => to.Active(), - resolve: ({ target }) => target.from({ value: "initial" }) - } + initial: (to) => to.Active().resolve(({ target }) => target.from({ value: "initial" })) }) const machine = definition.handle({ Active: { on: { SetValue: (to) => to.full.Active().resolve(({ event, target }) => target.from({ value: event.value })), Reset: (to) => - to.none().resolve((_, enqueue) => { + to.none.resolve((_, enqueue) => { enqueue.raise(internalEvents.Loaded({ value: "loaded" })) return undefined }), @@ -940,16 +902,13 @@ describe("Machine", () => { id: "deferred-event-failure", states: states.states, events: Machine.events(Event), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Idle().resolve(({ target }) => target.from()) }) const events = definition.events const machine = definition.handle({ Idle: { on: { - Submit: { target: Machine.targetless } + Submit: (to) => to.none } } }) @@ -994,10 +953,7 @@ describe("Machine", () => { states: states.states, events: Machine.events(), internalEvents: Machine.internalEvents(InternalEvent), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Loading().resolve(({ target }) => target.from()) }) const machine = definition.handle({ Loading: { @@ -1042,22 +998,16 @@ describe("Machine", () => { const first = Machine.make({ states: states.states, events: Machine.events(FirstEvent), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Idle().resolve(({ target }) => target.from()) }) const second = Machine.make({ states: states.states, events: Machine.events(SecondEvent), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Idle().resolve(({ target }) => target.from()) }).handle({ Idle: { on: { - Submit: { target: Machine.targetless } + Submit: (to) => to.none } } }) @@ -1080,10 +1030,7 @@ describe("Machine", () => { id: "from-default", states: states.states, events: Machine.events(), - initial: { - target: (to) => to.idle(), - resolve: ({ target }) => target.from({ id: "idle-1" }) - } + initial: (to) => to.idle().resolve(({ target }) => target.from({ id: "idle-1" })) }) const planned = yield* Machine.planInitial(machine) @@ -1111,10 +1058,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Event), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Idle().resolve(({ target }) => target.from()) }).handle({ Idle: { on: { @@ -1149,10 +1093,7 @@ describe("Machine", () => { id: "from-default-only", states: states.states, events: Machine.events(), - initial: { - target: (to) => to.DefaultOnly(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.DefaultOnly().resolve(({ target }) => target.from()) }) const planned = yield* Machine.planInitial(machine) @@ -1209,10 +1150,7 @@ describe("Machine", () => { Event.cases.Full, Event.cases.Finish ), - initial: { - target: (to) => to.Flow.initial(), - resolve: ({ target }) => target.from((flow) => flow.Idle.from()) - } + initial: (to) => to.Flow.initial.resolve(({ target }) => target.from((flow) => flow.Idle.from())) }).handle({ Flow: { states: { @@ -1293,15 +1231,14 @@ describe("Machine", () => { id: "from-empty-parallel", states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Parallel.initial(), - resolve: ({ target }) => + initial: (to) => + to.Parallel.initial.resolve(({ target }) => target.from((parallel) => parallel .left.from((left) => left.LeftIdle.from()) .right.from((right) => right.RightIdle.from()) ) - } + ) }) const planned = yield* Machine.planInitial(machine) @@ -1324,10 +1261,7 @@ describe("Machine", () => { id: "from-empty-refinement", states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Blocked(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Blocked().resolve(({ target }) => target.from()) }) const error = yield* Effect.flip(Machine.planInitial(machine)) @@ -1343,10 +1277,7 @@ describe("Machine", () => { id: "from-refinement", states: states.states, events: Machine.events(), - initial: { - target: (to) => to.NonEmptyIdle(), - resolve: ({ target }) => target.from({ userId: "" }) - } + initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target.from({ userId: "" })) }) const error = yield* Effect.flip(Machine.planInitial(machine)) @@ -1362,10 +1293,7 @@ describe("Machine", () => { id: "from-transition-refinement", states: states.states, events: Machine.events(NonEmptySubmit), - initial: { - target: (to) => to.NonEmptyIdle(), - resolve: ({ target }) => target.from({ userId: "user-1" }) - } + initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target.from({ userId: "user-1" })) }).handle({ NonEmptyIdle: { on: { @@ -1417,10 +1345,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Submit), - initial: { - target: (to) => to.idle(), - resolve: ({ target }) => target.from({ userId: "user-1" }) - } + initial: (to) => to.idle().resolve(({ target }) => target.from({ userId: "user-1" })) }).handle({ idle: { on: { @@ -1482,14 +1407,13 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Submit), - initial: { - target: (to) => to.payment.initial(), - resolve: ({ target }) => + initial: (to) => + to.payment.initial.resolve(({ target }) => target.from( { id: "payment-1" }, (payment) => payment.entering.from({ amount: 1 }) ) - } + ) }).handle({ payment: { states: { @@ -1538,14 +1462,13 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Submit), - initial: { - target: (to) => to.workflow.initial(), - resolve: ({ target }) => + initial: (to) => + to.workflow.initial.resolve(({ target }) => target.from( { id: "workflow-1" }, (workflow) => workflow.idle.from({ userId: "user-1" }) ) - } + ) }).handle({ workflow: { states: { @@ -1590,10 +1513,8 @@ describe("Machine", () => { states: states.states, events: Machine.events(NonEmptySubmit), input: NonEmptyInput, - initial: { - target: (to) => to.NonEmptyIdle(), - resolve: ({ input: input, target }) => target(new NonEmptyIdle({ userId: input.userId })) - } + initial: (to) => + to.NonEmptyIdle().resolve(({ input: input, target }) => target(new NonEmptyIdle({ userId: input.userId }))) }) const error = yield* Effect.flip(Machine.planInitial(machine, { userId: "" as any })) @@ -1607,10 +1528,8 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(NonEmptySubmit), - initial: { - target: (to) => to.NonEmptyIdle(), - resolve: ({ target }) => target(unsafeTagged({ _tag: "NonEmptyIdle", userId: "" })) - } + initial: (to) => + to.NonEmptyIdle().resolve(({ target }) => target(unsafeTagged({ _tag: "NonEmptyIdle", userId: "" }))) }) const error = yield* Effect.flip(Machine.planInitial(machine)) @@ -1624,10 +1543,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(NonEmptySubmit), - initial: { - target: (to) => to.NonEmptyIdle(), - resolve: ({ target }) => target(new NonEmptyIdle({ userId: "user-1" })) - } + initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) }).handle({ NonEmptyIdle: { on: { @@ -1653,10 +1569,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(NonEmptySubmit), - initial: { - target: (to) => to.NonEmptyIdle(), - resolve: ({ target }) => target(new NonEmptyIdle({ userId: "user-1" })) - } + initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) }).handle({ NonEmptyIdle: { on: { @@ -1691,10 +1604,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(NonEmptySubmit), - initial: { - target: (to) => to.NonEmptyIdle(), - resolve: ({ target }) => target(new NonEmptyIdle({ userId: "user-1" })) - } + initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) }).handle({ NonEmptyIdle: { on: { @@ -1723,10 +1633,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(NonEmptySubmit), - initial: { - target: (to) => to.NonEmptyIdle(), - resolve: ({ target }) => target(new NonEmptyIdle({ userId: "user-1" })) - } + initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) }).handle({ NonEmptyIdle: { on: { @@ -1763,10 +1670,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(NonEmptySubmit), - initial: { - target: (to) => to.NonEmptyIdle(), - resolve: ({ target }) => target(new NonEmptyIdle({ userId: "user-1" })) - } + initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) }).handle({ NonEmptyIdle: { on: { @@ -1812,9 +1716,8 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.all.initial(), - resolve: ({ target }) => + initial: (to) => + to.all.initial.resolve(({ target }) => target( new ParallelRoot({ id: "all" }), (all) => @@ -1822,7 +1725,7 @@ describe("Machine", () => { .left(new ParallelLeftDone({ id: "left" })) .right(new ParallelRightDone({ id: "right" })) ) - } + ) }).handle({ all: { output: () => ({ summary: "" as any }) @@ -1840,10 +1743,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(NonEmptySubmit), - initial: { - target: (to) => to.NonEmptyIdle(), - resolve: ({ target }) => target(new NonEmptyIdle({ userId: "user-1" })) - } + initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) }) const error = yield* Effect.flip( @@ -1867,10 +1767,7 @@ describe("Machine", () => { id: "Counter", states: states.states, events: Machine.events(), - initial: { - target: (to) => to.count(), - resolve: ({ target }) => target(new EncodedCount({ count: 1 })) - } + initial: (to) => to.count().resolve(({ target }) => target(new EncodedCount({ count: 1 }))) }) const planned = yield* Machine.planInitial(machine) @@ -1917,9 +1814,8 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.fulfillment.initial(), - resolve: ({ target }) => + initial: (to) => + to.fulfillment.initial.resolve(({ target }) => target( new Fulfillment({ id: "fulfillment-1" }), (fulfillment) => @@ -1933,7 +1829,7 @@ describe("Machine", () => { (shipping) => shipping.quoting(new QuotingShipping({ postalCode: "12345" })) ) ) - } + ) }) const planned = yield* Machine.planInitial(machine) @@ -1969,9 +1865,8 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.all.initial(), - resolve: ({ target }) => + initial: (to) => + to.all.initial.resolve(({ target }) => target( new ParallelRoot({ id: "all" }), (all) => @@ -1979,7 +1874,7 @@ describe("Machine", () => { .left(new ParallelLeftDone({ id: "left" })) .right(new ParallelRightDone({ id: "right" })) ) - } + ) }).handle({ all: { states: { @@ -2016,9 +1911,8 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.all.initial(), - resolve: ({ target }) => + initial: (to) => + to.all.initial.resolve(({ target }) => target( new ParallelRoot({ id: "all" }), (all) => @@ -2026,7 +1920,7 @@ describe("Machine", () => { .left(new ParallelLeftDone({ id: "left" })) .right(new ParallelRightDone({ id: "right" })) ) - } + ) }).handle({ all: { states: { @@ -2049,10 +1943,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.NonEmptyIdle(), - resolve: ({ target }) => target(new NonEmptyIdle({ userId: "user-1" })) - } + initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) }) const error = yield* Machine.encodeSnapshot(machine, { @@ -2069,10 +1960,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.NonEmptyIdle(), - resolve: ({ target }) => target(new NonEmptyIdle({ userId: "user-1" })) - } + initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) }) const error = yield* Machine.encodeSnapshot(machine, { @@ -2090,10 +1978,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.NonEmptyIdle(), - resolve: ({ target }) => target(new NonEmptyIdle({ userId: "user-1" })) - } + initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) }) const error = yield* Machine.decodeSnapshot(machine, { @@ -2122,14 +2007,13 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.payment.initial(), - resolve: ({ target }) => + initial: (to) => + to.payment.initial.resolve(({ target }) => target( new Payment({ id: "payment-1" }), (payment) => payment.entering(new EnteringPayment({ amount: 1 })) ) - } + ) }) const error = yield* Machine.decodeSnapshot(machine, { @@ -2154,10 +2038,7 @@ describe("Machine", () => { }, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ idle: { on: { @@ -2190,10 +2071,7 @@ describe("Machine", () => { b: Duplicate }, events: Machine.events(Submit, Reset), - initial: { - target: (to) => to.a(), - resolve: ({ target }) => target(new Duplicate({ value: "a" })) - } + initial: (to) => to.a().resolve(({ target }) => target(new Duplicate({ value: "a" }))) }).handle({ a: { on: { @@ -2233,10 +2111,7 @@ describe("Machine", () => { b: Duplicate }, events: Machine.events(Submit), - initial: { - target: (to) => to.a(), - resolve: ({ target }) => target(new Duplicate({ value: "a" })) - } + initial: (to) => to.a().resolve(({ target }) => target(new Duplicate({ value: "a" }))) }).handle({ a: { on: { @@ -2268,10 +2143,7 @@ describe("Machine", () => { } }, events: Machine.events(Submit), - initial: { - target: (to) => to.idle(), - resolve: ({ target }) => target(new Idle({ userId: "user-1" })) - } + initial: (to) => to.idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) }).handle({ idle: { on: { @@ -2310,17 +2182,15 @@ describe("Machine", () => { failed: Failed }, events: Machine.events(Authorize), - initial: { - target: (to) => to.payment.initial(), - resolve: () => ({ + initial: (to) => + to.payment.initial.resolve(() => ({ path: "payment" as const, value: payment, state: { path: "payment.entering" as const, value: entering } - }) - } + })) }).handle({ payment: { on: { @@ -2369,17 +2239,15 @@ describe("Machine", () => { }, events: Machine.events(Authorize), emittedEvents: Machine.emittedEvents(Notice), - initial: { - target: (to) => to.payment.initial(), - resolve: () => ({ + initial: (to) => + to.payment.initial.resolve(() => ({ path: "payment" as const, value: payment, state: { path: "payment.entering" as const, value: entering } - }) - } + })) }).handle({ payment: { on: { @@ -2391,7 +2259,7 @@ describe("Machine", () => { Authorize: (to) => to.branches({ authorize: { target: to.local.authorized() }, - consume: { target: to.none() } + consume: { target: to.none } }).resolve(({ event, select, decline }, enqueue) => { if (event.code === "child") { return select.authorize(new AuthorizedPayment({ code: event.code })) @@ -2445,18 +2313,17 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.workflow.initial(), - resolve: ({ target }) => + initial: (to) => + to.workflow.initial.resolve(({ target }) => target(new Workflow({}), (workflow) => workflow.waiting(new Waiting({ ready: false }))) - } + ) }).handle({ workflow: { always: (to) => to.full.finished().resolve(({ target }) => target(new Finished({}))), states: { waiting: { always: (to) => - to.none().resolve(({ state, decline }) => state.ready ? undefined : decline(), { declinable: true }) + to.none.resolve(({ state, decline }) => state.ready ? undefined : decline(), { declinable: true }) } } } @@ -2475,14 +2342,11 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Ping), - initial: { - target: (to) => to.Stable(), - resolve: ({ target }) => target(new Stable({})) - } + initial: (to) => to.Stable().resolve(({ target }) => target(new Stable({}))) }).handle({ Stable: { on: { - Ping: (to) => to.none().resolve(({ decline }) => decline(), { declinable: true }) + Ping: (to) => to.none.resolve(({ decline }) => decline(), { declinable: true }) } } }) @@ -2512,10 +2376,10 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.workflow.initial(), - resolve: ({ target }) => target(new Workflow({}), (workflow) => workflow.complete(new Complete({}))) - } + initial: (to) => + to.workflow.initial.resolve(({ target }) => + target(new Workflow({}), (workflow) => workflow.complete(new Complete({}))) + ) }).handle({ workflow: { onDone: (to) => to.full.finished().resolve(({ decline }) => decline(), { declinable: true }), @@ -2552,10 +2416,10 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Ping), - initial: { - target: (to) => to.root.initial(), - resolve: ({ target }) => target(new Root({}), (root) => root.left(new Left({})).right(new Right({}))) - } + initial: (to) => + to.root.initial.resolve(({ target }) => + target(new Root({}), (root) => root.left(new Left({})).right(new Right({}))) + ) }).handle({ root: { on: { @@ -2568,13 +2432,13 @@ describe("Machine", () => { states: { left: { on: { - Ping: (to) => to.none().resolve(({ decline }) => decline(), { declinable: true }) + Ping: (to) => to.none.resolve(({ decline }) => decline(), { declinable: true }) } }, right: { on: { Ping: (to) => - to.none().resolve(({ event, decline }) => event.handleRight ? undefined : decline(), { + to.none.resolve(({ event, decline }) => event.handleRight ? undefined : decline(), { declinable: true }) } @@ -2616,17 +2480,15 @@ describe("Machine", () => { failed: Failed }, events: Machine.events(Authorize, Reset), - initial: { - target: (to) => to.payment.initial(), - resolve: () => ({ + initial: (to) => + to.payment.initial.resolve(() => ({ path: "payment" as const, value: payment, state: { path: "payment.entering" as const, value: entering } - }) - } + })) }).handle({ payment: { on: { @@ -2683,17 +2545,15 @@ describe("Machine", () => { } }, events: Machine.events(Reset), - initial: { - target: (to) => to.payment.initial(), - resolve: () => ({ + initial: (to) => + to.payment.initial.resolve(() => ({ path: "payment" as const, value: payment, state: { path: "payment.entering" as const, value: entering } - }) - } + })) }).handle({ payment: { on: { @@ -2746,10 +2606,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Submit), - initial: { - target: (to) => to.idle(), - resolve: ({ target }) => target(new Idle({ userId: "user-1" })) - } + initial: (to) => to.idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) }).handle({ idle: { on: { @@ -2843,14 +2700,13 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Submit), - initial: { - target: (to) => to.workflow.initial(), - resolve: ({ target }) => + initial: (to) => + to.workflow.initial.resolve(({ target }) => target( workflow, (workflow) => workflow.idle(new Idle({ userId: "user-1" })) ) - } + ) }).handle({ workflow: { states: { @@ -2963,10 +2819,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Submit), - initial: { - target: (to) => to.app.initial(), - resolve: () => initial - } + initial: (to) => to.app.initial.resolve(() => initial) }).handle({ app: { states: { @@ -3053,9 +2906,8 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(ReserveInventory), - initial: { - target: (to) => to.fulfillment.initial(), - resolve: ({ target }) => + initial: (to) => + to.fulfillment.initial.resolve(({ target }) => target( fulfillment, (fulfillment) => @@ -3069,7 +2921,7 @@ describe("Machine", () => { (shipping) => shipping.quoting(quoting) ) ) - } + ) }).handle({ fulfillment: { states: { @@ -3145,9 +2997,8 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(ReserveInventory), - initial: { - target: (to) => to.fulfillment.initial(), - resolve: ({ target }) => + initial: (to) => + to.fulfillment.initial.resolve(({ target }) => target( fulfillment, (fulfillment) => @@ -3161,7 +3012,7 @@ describe("Machine", () => { (shipping) => shipping.quoting(quoting) ) ) - } + ) }).handle({ fulfillment: { states: { @@ -3242,9 +3093,8 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(ReserveInventory), - initial: { - target: (to) => to.fulfillment.initial(), - resolve: ({ target }) => + initial: (to) => + to.fulfillment.initial.resolve(({ target }) => target( fulfillment, (fulfillment) => @@ -3258,7 +3108,7 @@ describe("Machine", () => { (shipping) => shipping.quoting(quoting) ) ) - } + ) }).handle({ fulfillment: { states: { @@ -3340,9 +3190,8 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(ReserveInventory), - initial: { - target: (to) => to.fulfillment.initial(), - resolve: ({ target }) => + initial: (to) => + to.fulfillment.initial.resolve(({ target }) => target( fulfillment, (fulfillment) => @@ -3356,7 +3205,7 @@ describe("Machine", () => { (shipping) => shipping.quoting(quoting) ) ) - } + ) }).handle({ fulfillment: { states: { @@ -3439,9 +3288,8 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(ReserveInventory), - initial: { - target: (to) => to.payment.initial(), - resolve: ({ target }) => + initial: (to) => + to.payment.initial.resolve(({ target }) => target( payment, (payment) => @@ -3450,7 +3298,7 @@ describe("Machine", () => { (inventory) => inventory.checking(new CheckingInventory({ sku: "sku-1" })) ) ) - } + ) }).handle({ payment: { states: { @@ -3509,17 +3357,15 @@ describe("Machine", () => { } }, events: Machine.events(Authorize, Reset), - initial: { - target: (to) => to.payment.initial(), - resolve: () => ({ + initial: (to) => + to.payment.initial.resolve(() => ({ path: "payment" as const, value: payment, state: { path: "payment.entering" as const, value: new EnteringPayment({ amount: 100 }) } - }) - } + })) }).handle({ payment: { on: { @@ -3572,17 +3418,15 @@ describe("Machine", () => { } }, events: Machine.events(Reset), - initial: { - target: (to) => to.payment.initial(), - resolve: () => ({ + initial: (to) => + to.payment.initial.resolve(() => ({ path: "payment" as const, value: payment, state: { path: "payment.authorized" as const, value: authorized } - }) - } + })) }).handle({ payment: { states: { @@ -3624,17 +3468,15 @@ describe("Machine", () => { } }, events: Machine.events(Authorize, Reset), - initial: { - target: (to) => to.payment.initial(), - resolve: () => ({ + initial: (to) => + to.payment.initial.resolve(() => ({ path: "payment" as const, value: payment, state: { path: "payment.entering" as const, value: new EnteringPayment({ amount: 100 }) } - }) - } + })) }).handle({ payment: { on: { @@ -3708,9 +3550,8 @@ describe("Machine", () => { failed: Failed }, events: Machine.events(ReserveInventory, Reset), - initial: { - target: (to) => to.checkout.initial(), - resolve: () => ({ + initial: (to) => + to.checkout.initial.resolve(() => ({ path: "checkout" as const, value: checkout, state: { @@ -3721,8 +3562,7 @@ describe("Machine", () => { value: new CheckingInventory({ sku: "sku-1" }) } } - }) - } + })) }).handle({ checkout: { on: { @@ -3803,9 +3643,8 @@ describe("Machine", () => { } }, events: Machine.events(ReserveInventory), - initial: { - target: (to) => to.fulfillment.initial(), - resolve: () => ({ + initial: (to) => + to.fulfillment.initial.resolve(() => ({ path: "fulfillment" as const, value: fulfillment, states: { @@ -3826,8 +3665,7 @@ describe("Machine", () => { } } } - }) - } + })) }).handle({ fulfillment: { states: { @@ -3923,9 +3761,8 @@ describe("Machine", () => { } }, events: Machine.events(ReserveInventory), - initial: { - target: (to) => to.fulfillment.initial(), - resolve: () => ({ + initial: (to) => + to.fulfillment.initial.resolve(() => ({ path: "fulfillment" as const, value: fulfillment, states: { @@ -3946,8 +3783,7 @@ describe("Machine", () => { } } } - }) - } + })) }).handle({ fulfillment: { output: ({ outputs }) => ({ @@ -4070,9 +3906,8 @@ describe("Machine", () => { } }, events: Machine.events(ReserveInventory, Resolve), - initial: { - target: (to) => to.fulfillment.initial(), - resolve: () => ({ + initial: (to) => + to.fulfillment.initial.resolve(() => ({ path: "fulfillment" as const, value: fulfillment, states: { @@ -4093,8 +3928,7 @@ describe("Machine", () => { } } } - }) - } + })) }).handle({ fulfillment: { output: ({ outputs }) => outputs, @@ -4199,9 +4033,8 @@ describe("Machine", () => { } }, events: Machine.events(ReserveInventory), - initial: { - target: (to) => to.fulfillment.initial(), - resolve: () => ({ + initial: (to) => + to.fulfillment.initial.resolve(() => ({ path: "fulfillment" as const, value: fulfillment, states: { @@ -4222,8 +4055,7 @@ describe("Machine", () => { } } } - }) - } + })) }).handle({ fulfillment: { states: { @@ -4311,9 +4143,8 @@ describe("Machine", () => { } }, events: Machine.events(ReserveInventory, Resolve), - initial: { - target: (to) => to.fulfillment.initial(), - resolve: () => ({ + initial: (to) => + to.fulfillment.initial.resolve(() => ({ path: "fulfillment" as const, value: fulfillment, states: { @@ -4334,8 +4165,7 @@ describe("Machine", () => { } } } - }) - } + })) }).handle({ fulfillment: { states: { @@ -4399,10 +4229,7 @@ describe("Machine", () => { const machine = Machine.make({ states: { Idle }, events: Machine.events(Submit), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({ userId: "user-1" })) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) }) const actor = yield* Machine.start(machine) @@ -4416,10 +4243,7 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -4447,10 +4271,7 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit, Reset), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -4479,10 +4300,7 @@ describe("Machine", () => { }, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -4504,10 +4322,7 @@ describe("Machine", () => { states: { Idle, Success: SuccessOutput }, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -4537,10 +4352,7 @@ describe("Machine", () => { states: { Idle, Success: SuccessOutput }, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -4567,10 +4379,7 @@ describe("Machine", () => { const machine = Machine.make({ states: { Success: SuccessOutput }, events: Machine.events(Submit), - initial: { - target: (to) => to.Success(), - resolve: ({ target }) => target(new Success({ requestId: "request-1" })) - } + initial: (to) => to.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))) }).handle({ Success: { output: ({ state }) => { @@ -4604,10 +4413,7 @@ describe("Machine", () => { const machine = Machine.make({ states: { Success: SuccessOutput }, events: Machine.events(Submit), - initial: { - target: (to) => to.Success(), - resolve: ({ target }) => target(new Success({ requestId: "request-1" })) - } + initial: (to) => to.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))) }).handle({ Success: { output: ({ state }) => { @@ -4637,10 +4443,7 @@ describe("Machine", () => { }, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -4674,10 +4477,7 @@ describe("Machine", () => { }, events: Machine.events(Submit, Reset), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -4709,10 +4509,7 @@ describe("Machine", () => { const machine = Machine.make({ states: { Idle, Loading }, events: Machine.events(Submit), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({ userId: "user-1" })) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) }).handle({ Idle: { on: { @@ -4740,10 +4537,7 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -4774,10 +4568,7 @@ describe("Machine", () => { }, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Success: {} }) @@ -4796,14 +4587,11 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - Submit: { target: Machine.targetless } + Submit: (to) => to.none } } }) @@ -4822,10 +4610,7 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -4869,10 +4654,7 @@ describe("Machine", () => { states: { Idle, Success: SuccessOutput }, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -4907,10 +4689,7 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit, Reset), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -4934,10 +4713,7 @@ describe("Machine", () => { states: { Idle, Loading, Success: SuccessOutput }, events: Machine.events(Submit, RequestSucceeded), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }) const machine = definition.handle({ Idle: { @@ -4980,10 +4756,7 @@ describe("Machine", () => { states: { Idle, Loading, Success: SuccessOutput }, events: Machine.events(Submit, RequestSucceeded), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }) const machine = definition.handle({ Idle: { @@ -5026,20 +4799,14 @@ describe("Machine", () => { const childMachine = Machine.make({ states: childStates.states, events: Machine.events(), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({ userId: "child" })) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "child" }))) }) const Child = Machine.child("shared-child", childMachine) const parentStates = Machine.states({ Loading }) const parentMachine = Machine.make({ states: parentStates.states, events: Machine.events(), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "parent" })) - } + initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "parent" }))) }).handle({ Loading: { invoke: Machine.invoke({ child: Child }) @@ -5083,20 +4850,14 @@ describe("Machine", () => { const childMachine = Machine.make({ states: childStates.states, events: Machine.events(), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({ userId: "child" })) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "child" }))) }).handle({ Idle: {} }) const Child = Machine.child("owned-child", childMachine) const parentStates = Machine.states({ Loading }) const parentMachine = Machine.make({ states: parentStates.states, events: Machine.events(), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "parent" })) - } + initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "parent" }))) }).handle({ Loading: { invoke: Machine.invoke({ child: Child }) } }) @@ -5123,23 +4884,18 @@ describe("Machine", () => { states: childStates.states, events: Machine.events(), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input, target }) => { + initial: (to) => + to.Idle().resolve(({ input, target }) => { starts += 1 return target(new Idle({ userId: input.userId })) - } - } + }) }).handle({ Idle: {} }) const Child = Machine.child("input-child", childMachine) const parentStates = Machine.states({ Loading }) const parentMachine = Machine.make({ states: parentStates.states, events: Machine.events(), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "parent" })) - } + initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "parent" }))) }).handle({ Loading: { invoke: Machine.invoke({ child: Child, input: { userId: "configured" } }) @@ -5174,10 +4930,7 @@ describe("Machine", () => { const childMachine = Machine.make({ states: childStates.states, events: Machine.events(), - initial: { - target: (to) => to.Success(), - resolve: ({ target }) => target(new Success({ requestId: "child-output" })) - } + initial: (to) => to.Success().resolve(({ target }) => target(new Success({ requestId: "child-output" }))) }).handle({ Success: { output: ({ state }) => state.requestId } }) @@ -5189,10 +4942,7 @@ describe("Machine", () => { const parentMachine = Machine.make({ states: parentStates.states, events: Machine.events(ChildFinished), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "parent" })) - } + initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "parent" }))) }).handle({ Loading: { invoke: Machine.invoke({ @@ -5216,10 +4966,7 @@ describe("Machine", () => { states: states.states, events: Machine.events(), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: {} }) @@ -5248,20 +4995,14 @@ describe("Machine", () => { const child = Machine.make({ states: childStates.states, events: Machine.events(), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({ userId: "child" })) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "child" }))) }) const Child = Machine.child("child-machine", child) const parentStates = Machine.states({ Loading }) const parent = Machine.make({ states: parentStates.states, events: Machine.events(), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - } + initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) }).handle({ Loading: { invoke: [ @@ -5290,10 +5031,7 @@ describe("Machine", () => { const parent = Machine.make({ states: parentStates.states, events: Machine.events(), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - } + initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) }).handle({ Loading: { invoke: [ @@ -5326,10 +5064,7 @@ describe("Machine", () => { states: { Idle, Loading, Failed: FailedOutput }, events: Machine.events(Submit, RequestFailed), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -5371,10 +5106,7 @@ describe("Machine", () => { states: { Idle, Loading, Success: SuccessOutput }, events: Machine.events(Submit), internalEvents: Machine.internalEvents(RequestSucceeded), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({ userId: "user-1" })) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) }).handle({ Idle: { on: { @@ -5406,10 +5138,7 @@ describe("Machine", () => { const machine = Machine.make({ states: { Loading, Success: SuccessOutput }, events: Machine.events(RequestSucceeded), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - } + initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) }).handle({ Loading: { invoke: Machine.invoke({ @@ -5425,7 +5154,7 @@ describe("Machine", () => { Effect.andThen(Effect.never) ) }), - onFailure: { target: Machine.targetless } + onFailure: (to) => to.none }), on: { RequestSucceeded: (to) => @@ -5449,10 +5178,7 @@ describe("Machine", () => { const machine = Machine.make({ states: { Idle, Loading, Success: SuccessOutput }, events: Machine.events(Resolve, RequestSucceeded), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - } + initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) }).handle({ Idle: { on: { @@ -5474,7 +5200,7 @@ describe("Machine", () => { Effect.onInterrupt(() => sendTo(parent, new RequestSucceeded({ value: "stale" }))) ) }), - onFailure: { target: Machine.targetless } + onFailure: (to) => to.none }), on: { Resolve: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({ userId: "resolved" }))) @@ -5508,10 +5234,7 @@ describe("Machine", () => { states: { Loading, Failed: FailedOutput }, events: Machine.events(), internalEvents: Machine.internalEvents(RequestSucceeded, RequestFailed), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - } + initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) }).handle({ Loading: { invoke: Machine.invoke({ @@ -5540,10 +5263,7 @@ describe("Machine", () => { states: { Loading, Success: SuccessOutput }, events: Machine.events(), internalEvents: Machine.internalEvents(RequestSucceeded), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - } + initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) }).handle({ Loading: { invoke: Machine.invoke({ @@ -5574,10 +5294,7 @@ describe("Machine", () => { states: { Loading, Success: SuccessOutput }, events: Machine.events(), internalEvents: Machine.internalEvents(RequestSucceeded), - initial: { - target: (to) => to.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - } + initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) }).handle({ Loading: { invoke: Machine.invoke({ @@ -5604,10 +5321,7 @@ describe("Machine", () => { states: { Idle, Loading, Success: SuccessOutput }, events: Machine.events(Submit, RequestProgress), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }) const onSnapshot: Machine.Machine.InvokeTransition< Machine.Machine.States, @@ -5671,10 +5385,7 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -5712,10 +5423,7 @@ describe("Machine", () => { states: { Idle, Loading, Success: SuccessOutput }, events: Machine.events(Submit, RequestProgress), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }) const onSnapshot: Machine.Machine.InvokeTransition< Machine.Machine.States, @@ -5736,7 +5444,7 @@ describe("Machine", () => { > = (to) => to.branches({ ready: { title: "Request is ready", target: to.full.Success() }, - unchanged: { target: to.none() } + unchanged: { target: to.none } }).resolve(({ snapshot, select }) => snapshot.state === "ready" ? select.ready(new Success({ requestId: snapshot.state })) @@ -5800,10 +5508,7 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -5818,7 +5523,7 @@ describe("Machine", () => { initial: "pending", run: () => Effect.void }), - onDone: { target: Machine.targetless } + onDone: (to) => to.none }) } }) @@ -5858,10 +5563,7 @@ describe("Machine", () => { states: { Idle, Loading, Success: SuccessOutput }, events: Machine.events(Submit, Resolve, RequestSucceeded), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -5945,17 +5647,15 @@ describe("Machine", () => { } }, events: Machine.events(Authorize), - initial: { - target: (to) => to.payment.initial(), - resolve: () => ({ + initial: (to) => + to.payment.initial.resolve(() => ({ path: "payment" as const, value: payment, state: { path: "payment.entering" as const, value: entering } - }) - } + })) }).handle({ payment: { invoke: Machine.invoke({ @@ -6069,9 +5769,8 @@ describe("Machine", () => { } }, events: Machine.events(ReserveInventory), - initial: { - target: (to) => to.fulfillment.initial(), - resolve: () => ({ + initial: (to) => + to.fulfillment.initial.resolve(() => ({ path: "fulfillment" as const, value: fulfillment, states: { @@ -6092,8 +5791,7 @@ describe("Machine", () => { } } } - }) - } + })) }).handle({ fulfillment: { invoke: Machine.invoke({ @@ -6161,12 +5859,10 @@ describe("Machine", () => { const machine = Machine.make({ states: { Idle }, events: Machine.events(), - initial: { - target: (to) => to.Idle(), - resolve: () => { + initial: (to) => + to.Idle().resolve(() => { throw defect - } - } + }) }) const planningError = yield* Effect.flip(Machine.planInitial(machine)) @@ -6201,10 +5897,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.payment.initial(), - resolve: () => invalidInitialState as any - } + initial: (to) => to.payment.initial.resolve(() => invalidInitialState as any) }) const planningError = yield* Effect.flip(Machine.planInitial(machine)) @@ -6223,10 +5916,7 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit), input: Input, - initial: { - target: (to) => to.Idle(), - resolve: ({ input: input, target }) => target(new Idle({ userId: input.userId })) - } + initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) }).handle({ Idle: { always: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))), @@ -6255,10 +5945,7 @@ describe("Machine", () => { id: "InitialLoopMachine", states: { Idle, Loading }, events: Machine.events(), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({ userId: "user-1" })) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) }).handle({ Idle: { always: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) @@ -6298,10 +5985,7 @@ describe("Machine", () => { id: "CompletionLoopMachine", states: states.states, events: Machine.events(Submit), - initial: { - target: (to) => to.idle(), - resolve: ({ target }) => target(new Idle({ userId: "user-1" })) - } + initial: (to) => to.idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) }).handle({ idle: { on: { @@ -6369,14 +6053,13 @@ describe("Machine", () => { Machine.make({ states: ParallelCounterStates.states, events: Machine.events(AdvanceCounters), - initial: { - target: (to) => to.running.initial(), - resolve: ({ target }) => + initial: (to) => + to.running.initial.resolve(({ target }) => target( new CounterRunning({}), (running) => running.left(new LeftCounter({ value: 0 })).right(new RightCounter({ value: 0 })) ) - } + ) }).handle({ running: { states: { @@ -6405,14 +6088,11 @@ describe("Machine", () => { return Machine.make({ states: states.states, events: Machine.events(ConcurrentPing), - initial: { - target: (to) => to.ConcurrentIdle(), - resolve: ({ target }) => target(new ConcurrentIdle({})) - } + initial: (to) => to.ConcurrentIdle().resolve(({ target }) => target(new ConcurrentIdle({}))) }).handle({ ConcurrentIdle: { on: { - ConcurrentPing: { target: Machine.targetless } + ConcurrentPing: (to) => to.none } } }) diff --git a/test/machine/MachineReferences.test.ts b/test/machine/MachineReferences.test.ts index 13674d4..7c0e8a4 100644 --- a/test/machine/MachineReferences.test.ts +++ b/test/machine/MachineReferences.test.ts @@ -19,13 +19,11 @@ describe("machine reference event channels", () => { states: states.states, events: Machine.events(), emittedEvents: Emissions, - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => { + initial: (to) => + to.Idle().resolve(({ target }) => { initializations += 1 return target(new Idle({})) - } - } + }) }).handle({ Idle: { entry: (_, enqueue) => { @@ -73,10 +71,7 @@ describe("machine reference event channels", () => { states: states.states, events: Machine.events(), emittedEvents: Emissions, - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) }).handle({ Idle: { entry: (_, enqueue) => { @@ -115,15 +110,12 @@ describe("machine reference event channels", () => { states: states.states, events: Events, emittedEvents: Emissions, - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) }).handle({ Idle: { on: { Publish: (to) => - to.none().resolve(({ event }, enqueue) => { + to.none.resolve(({ event }, enqueue) => { enqueue.emit(Emissions.Published({ value: event.value })) return undefined }) @@ -169,15 +161,12 @@ describe("machine reference event channels", () => { states: states.states, events: Events, emittedEvents: Emissions, - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) }).handle({ Idle: { on: { Publish: (to) => - to.none().resolve((_, enqueue) => { + to.none.resolve((_, enqueue) => { enqueue.emit(Emissions.Published({ value: "invalid" } as never)) return undefined }) @@ -226,10 +215,7 @@ describe("machine reference event channels", () => { events: ChildEvents, parentEvents: ParentEvents, emittedEvents: ChildEmissions, - initial: { - target: (to) => to.Waiting(), - resolve: ({ target }) => target(new Waiting({})) - } + initial: (to) => to.Waiting().resolve(({ target }) => target(new Waiting({}))) }).handle({ Waiting: { on: { @@ -267,10 +253,7 @@ describe("machine reference event channels", () => { const parentMachine = Machine.make({ states: parentStates.states, events: Machine.events(ParentEvents, Notice), - initial: { - target: (to) => to.Awaiting(), - resolve: ({ target }) => target(new Awaiting({})) - } + initial: (to) => to.Awaiting().resolve(({ target }) => target(new Awaiting({}))) }).handle({ Awaiting: { invoke: Machine.invoke({ child: Child }), @@ -309,18 +292,15 @@ describe("machine reference event channels", () => { states: childStates.states, events: Machine.events(), parentEvents: ParentEvents, - initial: { - target: (to) => to.ChildIdle(), - resolve: ({ target }) => target(new ChildIdle({})) - } + initial: (to) => to.ChildIdle().resolve(({ target }) => target(new ChildIdle({}))) }) const childMachine = childDefinition.handle({ ChildIdle: { invoke: Machine.invoke({ id: "notify-ready", effect: ({ parent }) => parent === undefined ? Effect.void : parent.send(ParentEvents.ChildReady()), - onDone: { target: Machine.targetless }, - onFailure: { target: Machine.targetless } + onDone: (to) => to.none, + onFailure: (to) => to.none }) } }) @@ -332,15 +312,12 @@ describe("machine reference event channels", () => { const parentMachine = Machine.make({ states: parentStates.states, events: ParentEvents, - initial: { - target: (to) => to.ParentWaiting(), - resolve: ({ target }) => target(new ParentWaiting({})) - } + initial: (to) => to.ParentWaiting().resolve(({ target }) => target(new ParentWaiting({}))) }).handle({ ParentWaiting: { invoke: Machine.invoke({ child: Child, - onFailure: { target: Machine.targetless } + onFailure: (to) => to.none }), on: { ChildReady: (to) => to.full.ParentDone().resolve(({ target }) => target(new ParentDone({}))) diff --git a/test/machine/PublicPrototype.test.ts b/test/machine/PublicPrototype.test.ts index 87d5c20..b0e36aa 100644 --- a/test/machine/PublicPrototype.test.ts +++ b/test/machine/PublicPrototype.test.ts @@ -10,10 +10,7 @@ it("uses the public pipeable and inspectable prototypes", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Start), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle()) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle())) }) assert.strictEqual(machine.pipe((value) => value), machine) diff --git a/test/machine/Resume.test.ts b/test/machine/Resume.test.ts index 2713c9d..0a9b987 100644 --- a/test/machine/Resume.test.ts +++ b/test/machine/Resume.test.ts @@ -70,10 +70,7 @@ describe("Machine.resume", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Inactive(), - resolve: ({ target }) => target(new Inactive({})) - } + initial: (to) => to.Inactive().resolve(({ target }) => target(new Inactive({}))) }).handle({ Root: { invoke: Machine.invoke({ @@ -187,7 +184,7 @@ describe("Machine.resume", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Advance), - initial: { target: (to) => to.Root.initial(), resolve: () => initial } + initial: (to) => to.Root.initial.resolve(() => initial) }) .handle({ Root: { @@ -250,10 +247,7 @@ describe("Machine.resume", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Finish), - initial: { - target: (to) => to.Count(), - resolve: ({ target }) => target(new Count({ value: 0 })) - } + initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) }).handle({ Count: { on: { @@ -296,7 +290,7 @@ describe("Machine.resume", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Ping), - initial: { target: (to) => to.Flow.initial(), resolve: () => logical } + initial: (to) => to.Flow.initial.resolve(() => logical) }) .handle({ Flow: { @@ -322,10 +316,7 @@ describe("Machine.resume", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Ping), - initial: { - target: (to) => to.A(), - resolve: ({ target }) => target(new A({})) - } + initial: (to) => to.A().resolve(({ target }) => target(new A({}))) }).handle({ A: { always: (to) => to.full.B().resolve(({ target }) => target(new B({}))) @@ -351,10 +342,7 @@ describe("Machine.resume", () => { states: states.states, events: Machine.events(Cancel), internalEvents: Machine.internalEvents(Timeout), - initial: { - target: (to) => to.Cancelled(), - resolve: ({ target }) => target(new Cancelled({})) - } + initial: (to) => to.Cancelled().resolve(({ target }) => target(new Cancelled({}))) }).handle({ Waiting: { invoke: Machine.invoke({ @@ -397,10 +385,7 @@ describe("Machine.resume", () => { states: states.states, events: Machine.events(), internalEvents: Machine.internalEvents(LoadedEvent), - initial: { - target: (to) => to.Loaded(), - resolve: ({ target }) => target(new Loaded({ value: "initial" })) - } + initial: (to) => to.Loaded().resolve(({ target }) => target(new Loaded({ value: "initial" }))) }).handle({ Loading: { invoke: Machine.invoke({ @@ -435,10 +420,7 @@ describe("Machine.resume", () => { states: states.states, events: Machine.events(), internalEvents: Machine.internalEvents(FailedEvent), - initial: { - target: (to) => to.Failed(), - resolve: ({ target }) => target(new Failed({ message: "initial" })) - } + initial: (to) => to.Failed().resolve(({ target }) => target(new Failed({ message: "initial" }))) }).handle({ Loading: { invoke: Machine.invoke({ @@ -477,10 +459,7 @@ describe("Machine.resume", () => { const child = Machine.make({ states: childStates.states, events: Machine.events(ChildFinish), - initial: { - target: (to) => to.ChildIdle(), - resolve: ({ target }) => target(new ChildIdle({ value: 1 })) - } + initial: (to) => to.ChildIdle().resolve(({ target }) => target(new ChildIdle({ value: 1 }))) }).handle({ ChildIdle: { on: { @@ -495,10 +474,7 @@ describe("Machine.resume", () => { const machine = Machine.make({ states: states.states, events: Machine.events(ChildOutput), - initial: { - target: (to) => to.ChildOutput(), - resolve: ({ target }) => target(new ChildOutput({ value: 0 })) - } + initial: (to) => to.ChildOutput().resolve(({ target }) => target(new ChildOutput({ value: 0 }))) }).handle({ Parent: { invoke: Machine.invoke({ @@ -559,7 +535,7 @@ describe("Machine.resume", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { target: (to) => to.Root.initial(), resolve: () => valid } + initial: (to) => to.Root.initial.resolve(() => valid) }) const forged: ReadonlyArray = [ { path: "Missing" as const, value: {} }, @@ -598,10 +574,7 @@ describe("Machine.resume", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Add), - initial: { - target: (to) => to.Count(), - resolve: ({ target }) => target(new Count({ value: 0 })) - } + initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) }).handle({ Count: { on: { diff --git a/test/machine/RuntimeDifferential.test.ts b/test/machine/RuntimeDifferential.test.ts index 3044cbb..db9296d 100644 --- a/test/machine/RuntimeDifferential.test.ts +++ b/test/machine/RuntimeDifferential.test.ts @@ -54,15 +54,12 @@ describe("pure planning and managed runtime differential", () => { states: states.states, events: Machine.events(Cascade, Ignore, Finish), internalEvents: Machine.internalEvents(Increment), - initial: { - target: (to) => to.Count(), - resolve: ({ target }) => target(new Count({ value: 0 })) - } + initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) }).handle({ Count: { on: { Cascade: (to) => - to.none().resolve((_, enqueue) => { + to.none.resolve((_, enqueue) => { enqueue.raise(new Increment({})) return undefined }), @@ -145,14 +142,13 @@ describe("pure planning and managed runtime differential", () => { states: states.states, events: Machine.events(Advance, Inspect, Finish), internalEvents: Machine.internalEvents(Bump), - initial: { - target: (to) => to.Running.initial(), - resolve: ({ target }) => + initial: (to) => + to.Running.initial.resolve(({ target }) => target( new Running({}), (running) => running.Left(new Left({ value: 0 })).Right(new Right({ value: 0 })) ) - } + ) }).handle({ Running: { on: { @@ -187,7 +183,7 @@ describe("pure planning and managed runtime differential", () => { target(new Right({ value: state.value + 100 })) ), Inspect: (to) => - to.none().resolve((context) => { + to.none.resolve((context) => { const { state, containingState, ancestors, snapshot } = context if (snapshot.path !== "Running") throw new Error("expected Running snapshot") const expectedKeys = [ @@ -454,10 +450,7 @@ describe("pure planning and managed runtime differential", () => { events: Machine.events(Begin), internalEvents: Machine.internalEvents(RaisedOne, RaisedTwo), emittedEvents: Machine.emittedEvents(Notice), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) }).handle({ Idle: { entry: (_, enqueue) => { @@ -482,7 +475,7 @@ describe("pure planning and managed runtime differential", () => { }, on: { RaisedOne: (to) => - to.none().resolve((_, enqueue) => { + to.none.resolve((_, enqueue) => { record("raised:one") enqueue.emit(new Notice({ label: "raised-one" })) return undefined @@ -570,10 +563,7 @@ describe("pure planning and managed runtime differential", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Ignore, Go), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) }).handle({ Idle: { on: { diff --git a/test/machine/Scheduling.test.ts b/test/machine/Scheduling.test.ts index da4ea84..41c76ce 100644 --- a/test/machine/Scheduling.test.ts +++ b/test/machine/Scheduling.test.ts @@ -21,10 +21,7 @@ describe("machine scheduling", () => { states: states.states, events: Machine.events(StartBurst), internalEvents: Machine.internalEvents(Burst), - initial: { - target: (to) => to.SchedulingActive(), - resolve: ({ target }) => target(new SchedulingActive({ count: 0 })) - } + initial: (to) => to.SchedulingActive().resolve(({ target }) => target(new SchedulingActive({ count: 0 }))) }).handle({ SchedulingActive: { on: { diff --git a/test/machine/SnapshotCodecAdversarial.test.ts b/test/machine/SnapshotCodecAdversarial.test.ts index 2fe3d0b..270d350 100644 --- a/test/machine/SnapshotCodecAdversarial.test.ts +++ b/test/machine/SnapshotCodecAdversarial.test.ts @@ -54,7 +54,7 @@ const topologyMachine = Machine.make({ id: "codec-topology", states: TopologyStates.states, events: Machine.events(), - initial: { target: (to) => to.Root.initial(), resolve: () => topologyActive() } + initial: (to) => to.Root.initial.resolve(() => topologyActive()) }) const topologyActive = () => ({ @@ -134,10 +134,7 @@ const historyMachine = Machine.make({ id: "codec-history", states: HistoryStates.states, events: Machine.events(), - initial: { - target: (to) => to.Outside(), - resolve: ({ target }) => target(new Outside({})) - } + initial: (to) => to.Outside().resolve(({ target }) => target(new Outside({}))) }) const historySnapshot = () => @@ -242,10 +239,7 @@ describe("snapshot codec adversarial boundaries", () => { id: "codec-automatic-original", states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Before(), - resolve: ({ target }) => target(new Before({})) - } + initial: (to) => to.Before().resolve(({ target }) => target(new Before({}))) }).handle({ Before: { always: (to) => to.full.Boundary().resolve(({ target }) => target(new Boundary({}))) @@ -257,10 +251,7 @@ describe("snapshot codec adversarial boundaries", () => { id: "codec-automatic-changed", states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Before(), - resolve: ({ target }) => target(new Before({})) - } + initial: (to) => to.Before().resolve(({ target }) => target(new Before({}))) }).handle({ Before: {}, Boundary: { diff --git a/test/machine/SnapshotContext.test.ts b/test/machine/SnapshotContext.test.ts index 921661f..a7eac43 100644 --- a/test/machine/SnapshotContext.test.ts +++ b/test/machine/SnapshotContext.test.ts @@ -48,10 +48,8 @@ const initial = { } } -const initialDefinition = { - target: (to: Machine.Machine.InitialSelector) => to.System.initial(), - resolve: () => initial -} +const initialDefinition = (to: Machine.Machine.InitialSelector) => + to.System.initial.resolve(() => initial) describe("Machine transition snapshot context", () => { it.effect("lets an effectful event handler inspect a sibling region", () => @@ -71,7 +69,7 @@ describe("Machine transition snapshot context", () => { BufferReady: (to) => to.branches({ online: { title: "Network is online", target: to.local.Playing() }, - unchanged: { target: to.none() } + unchanged: { target: to.none } }).resolve(({ snapshot, select }) => { captured = snapshot return States.matches(snapshot, "System.Network.Online") @@ -164,7 +162,7 @@ describe("Machine transition snapshot context", () => { always: (to) => to.branches({ online: { title: "Network is online", target: to.local.Playing() }, - unchanged: { target: to.none() } + unchanged: { target: to.none } }).resolve(({ snapshot, select }) => { captured = snapshot return States.matches(snapshot, "System.Network.Online") @@ -217,9 +215,8 @@ describe("Machine transition snapshot context", () => { const machine = Machine.make({ states: completionStates.states, events: Machine.events(), - initial: { - target: (to) => to.System.initial(), - resolve: ({ target }) => + initial: (to) => + to.System.initial.resolve(({ target }) => target( new System({}), (system) => @@ -227,7 +224,7 @@ describe("Machine transition snapshot context", () => { .Work(new Work({}), (work) => work.Finished(new Finished({}))) .Monitor(new Monitor({}), (monitor) => monitor.Active(new Active({}))) ) - } + ) }).handle({ System: { states: { diff --git a/test/machine/StateDefinition.test.ts b/test/machine/StateDefinition.test.ts index 96bcd9c..b9487b7 100644 --- a/test/machine/StateDefinition.test.ts +++ b/test/machine/StateDefinition.test.ts @@ -105,10 +105,7 @@ describe("exact state-definition runtime validation", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Idle().resolve(({ target }) => target.from()) }) const nodes = Machine.stateNodes(machine) @@ -131,10 +128,7 @@ describe("exact state-definition runtime validation", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Idle(), - resolve: ({ target }) => target(new Idle({})) - } + initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) }) assert.strictEqual(Machine.stateNodes(machine)[0]?.path, "Idle") @@ -149,10 +143,7 @@ describe("exact state-definition runtime validation", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: { - target: (to) => to.Opaque(), - resolve: ({ target }) => target({ _tag: "OpaqueState", value: 1 }) - } + initial: (to) => to.Opaque().resolve(({ target }) => target({ _tag: "OpaqueState", value: 1 })) }) assert.strictEqual(Machine.stateNodes(machine)[0]?.path, "Opaque") @@ -287,6 +278,34 @@ describe("exact state-definition runtime validation", () => { ) }) + it("rejects child keys reserved by definition-time target selectors", () => { + expectDefinitionError( + () => + Machine.states({ + Root: { + initial: "initial", + states: { initial: Idle } + } + } as any), + "Machine.states", + "Root.initial", + "reserved target selector key" + ) + expectDefinitionError( + () => + Machine.states({ + Root: { + schema: Root, + initial: "with", + states: { with: Idle } + } + } as any), + "Machine.states", + "Root.with", + "reserved local target selector key" + ) + }) + it("rejects unknown or non-string pseudo-state annotations", () => { expectDefinitionError( () => diff --git a/test/machine/StructuralStates.test.ts b/test/machine/StructuralStates.test.ts index 9bf9596..bc8e86a 100644 --- a/test/machine/StructuralStates.test.ts +++ b/test/machine/StructuralStates.test.ts @@ -73,15 +73,14 @@ const makeMachine = () => Machine.make({ states: States.states, events: Machine.events(SourceSelected, Loaded, Play, Mute), - initial: { - target: (to) => to.player.initial(), - resolve: ({ target }) => + initial: (to) => + to.player.initial.resolve(({ target }) => target.from((player) => player .transport.from((transport) => transport.Empty.from()) .settings.from((settings) => settings.Audible.from({ volume: 1 })) ) - } + ) }).handle({ player: { states: { @@ -166,10 +165,8 @@ const historyFallback = () => ({ const historyMachine = Machine.make({ states: HistoryStates.states, events: Machine.events(Edit, Leave, ResumeShallow, ResumeDeep), - initial: { - target: (to) => to.flow.initial(), - resolve: ({ target }) => target.from((flow) => flow.section.from((section) => section.Idle.from())) - } + initial: (to) => + to.flow.initial.resolve(({ target }) => target.from((flow) => flow.section.from((section) => section.Idle.from()))) }).handle({ flow: { history: { @@ -193,8 +190,8 @@ const historyMachine = Machine.make({ }, away: { on: { - ResumeShallow: (to) => to.history.flow.recent().resolve(({ target }) => target()), - ResumeDeep: (to) => to.history.flow.exact().resolve(({ target }) => target()) + ResumeShallow: (to) => to.history.flow.recent.resolve(({ target }) => target()), + ResumeDeep: (to) => to.history.flow.exact.resolve(({ target }) => target()) } } }) @@ -330,10 +327,7 @@ describe("structural active states", () => { const machine = Machine.make({ states: FinalStates.states, events: Machine.events(), - initial: { - target: (to) => to.Done(), - resolve: ({ target }) => target.from() - } + initial: (to) => to.Done().resolve(({ target }) => target.from()) }).handle({ Done: { output: ({ state }) => { diff --git a/test/machine/Totality.test.ts b/test/machine/Totality.test.ts index 879c8b4..5817f4e 100644 --- a/test/machine/Totality.test.ts +++ b/test/machine/Totality.test.ts @@ -257,10 +257,7 @@ describe("machine operation totality", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Finish), - initial: { - target: (to) => to.Value(), - resolve: ({ target }) => target({ _tag: "Value", amount: 42 }) - } + initial: (to) => to.Value().resolve(({ target }) => target({ _tag: "Value", amount: 42 })) }).handle({ Value: { on: { diff --git a/test/machine/Visualization.test.ts b/test/machine/Visualization.test.ts index 982bc9f..9c16b06 100644 --- a/test/machine/Visualization.test.ts +++ b/test/machine/Visualization.test.ts @@ -83,7 +83,7 @@ const machineDefinition = Machine.make({ id: "inspection-example", states: States.states, events: Machine.events(Start, Disconnect, Refresh), - initial: { target: (to) => to.application.initial(), resolve: () => initial } + initial: (to) => to.application.initial.resolve(() => initial) }) const makeMachine = (unsafeStart = false) => @@ -108,7 +108,7 @@ const makeMachine = (unsafeStart = false) => to.local.running().resolve(({ target }) => target(new Running({}), (running) => running.editing(new Editing({}))) ), - Refresh: { target: Machine.targetless } + Refresh: (to) => to.none } }, running: { @@ -153,10 +153,7 @@ const lifecycleDefinition = Machine.make({ id: "lifecycle-inspection", states: LifecycleStates.states, events: Machine.events(), - initial: { - target: (to) => to.idle(), - resolve: ({ target }) => target(new Idle({})) - } + initial: (to) => to.idle().resolve(({ target }) => target(new Idle({}))) }) const makeLifecycleMachine = (unsafe: "always" | "done" | undefined = undefined) => @@ -190,12 +187,10 @@ describe("Machine structural visualization", () => { states: States.states, events: Machine.events(), input: Schema.String, - initial: { - target: (to) => to.application.initial(), - resolve: () => { + initial: (to) => + to.application.initial.resolve(() => { throw new Error("initial resolver unexpectedly executed during inspection") - } - } + }) }) assert.deepStrictEqual(Machine.initialDefinition(inspectOnly), { @@ -272,17 +267,14 @@ describe("Machine structural visualization", () => { const metadataMachine = Machine.make({ states: { idle: Idle }, events: Machine.events(Refresh), - initial: { - target: (to) => to.idle(), - resolve: () => ({ path: "idle" as const, value: new Idle({}) }) - } + initial: (to) => to.idle().resolve(() => ({ path: "idle" as const, value: new Idle({}) })) }).handle({ idle: { on: { - Refresh: (to) => to.none().resolve(() => undefined, { reenter: true }) + Refresh: (to) => to.none.resolve(() => undefined, { reenter: true }) }, - always: { target: Machine.targetless }, - onDone: { target: Machine.targetless } + always: (to) => to.none, + onDone: (to) => to.none } }) diff --git a/test/testing/Coverage.test.ts b/test/testing/Coverage.test.ts index abf42de..4663d9b 100644 --- a/test/testing/Coverage.test.ts +++ b/test/testing/Coverage.test.ts @@ -20,10 +20,7 @@ const CounterStates = Machine.states({ count: Count, done: Done }) const counterMachine = Machine.make({ states: CounterStates.states, events: Machine.events(Add, Finish), - initial: { - target: (to) => to.count(), - resolve: ({ target }) => target(new Count({ value: 0 })) - } + initial: (to) => to.count().resolve(({ target }) => target(new Count({ value: 0 }))) }).handle({ count: { on: { @@ -47,26 +44,20 @@ const opaqueMachine = Machine.make({ states: OpaqueStates.states, events: Machine.events(), input: Schema.Any, - initial: { - target: (to) => to.opaque(), - resolve: ({ input: payload, target }) => target(new Opaque({ payload })) - } + initial: (to) => to.opaque().resolve(({ input: payload, target }) => target(new Opaque({ payload }))) }) const StartupStates = Machine.states({ count: Count }) const startupMachine = Machine.make({ states: StartupStates.states, events: Machine.events(Add), - initial: { - target: (to) => to.count(), - resolve: ({ target }) => target(new Count({ value: 0 })) - } + initial: (to) => to.count().resolve(({ target }) => target(new Count({ value: 0 }))) }).handle({ count: { always: (to) => to.branches({ zero: { title: "Count is zero", target: to.full.count() }, - unchanged: { target: to.none() } + unchanged: { target: to.none } }).resolve(({ state, select }) => state.value === 0 ? select.zero(new Count({ value: 1 })) @@ -86,18 +77,15 @@ class Select extends Schema.TaggedClass