From effd4589df560a9caa206840a94174ef1bfb38a1 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Wed, 19 Aug 2026 11:08:42 +0200 Subject: [PATCH] Replace transition wrapper with fluent handlers --- .changeset/fluent-transition-handlers.md | 22 + README.md | 116 +- api-reference.config.json | 3 +- docs/agent-guide.md | 153 +-- examples/platformer/src/machine.ts | 205 ++- .../src/examples/media-player/machine.ts | 274 ++-- .../src/examples/microwave/machine.ts | 37 +- .../src/examples/traffic-light/machine.ts | 41 +- .../src/examples/turnstile/machine.ts | 10 +- .../src/examples/worker-tabs/machine.ts | 43 +- examples/pokemon/src/machine.ts | 32 +- examples/pokemon/src/machines/replace.ts | 26 +- examples/pokemon/src/machines/selection.ts | 55 +- perf/runtime/effect-machine-compatibility.mjs | 24 +- perf/types/adapter-readiness-control.ts | 5 +- perf/types/composition.ts | 7 +- perf/types/definition-variants.ts | 25 +- perf/types/dynamic-invoke.ts | 16 +- perf/types/exact-channels.ts | 13 +- perf/types/handle.ts | 11 +- perf/types/named-branches.ts | 10 +- scripts/fixtures/consumer/consumer.ts | 18 +- scripts/fixtures/consumer/deep-bound.ts | 45 +- scripts/invoke-autocomplete.test.mjs | 109 +- ...runtime-performance-compatibility.test.mjs | 38 +- scripts/type-performance.mjs | 6 +- src/Machine.ts | 1101 ++++++----------- src/internal/machine/machine.ts | 145 ++- src/internal/testing/machine/finiteModel.ts | 24 +- src/testing/MachineTest.ts | 13 +- test/internal/machine/activities.test.ts | 20 +- .../machine/strategyDifferential.test.ts | 151 +-- test/machine/ActivityLifecycleModel.test.ts | 157 +-- test/machine/Choice.test.ts | 103 +- test/machine/DeepHandlers.test.ts | 7 +- test/machine/History.test.ts | 66 +- test/machine/InitialEntry.test.ts | 40 +- test/machine/Inspection.test.ts | 2 +- test/machine/Invoke.test.ts | 62 +- test/machine/LiveInspection.test.ts | 21 +- test/machine/LocalTargetWith.test.ts | 34 +- test/machine/Machine.test.ts | 879 +++++-------- test/machine/MachineReferences.test.ts | 59 +- test/machine/Resume.test.ts | 66 +- test/machine/RuntimeDifferential.test.ts | 88 +- test/machine/Scheduling.test.ts | 16 +- test/machine/SnapshotCodecAdversarial.test.ts | 10 +- test/machine/SnapshotContext.test.ts | 44 +- test/machine/StructuralStates.test.ts | 52 +- test/machine/Totality.test.ts | 5 +- test/machine/Visualization.test.ts | 45 +- test/testing/Coverage.test.ts | 52 +- test/testing/Exploration.test.ts | 25 +- test/testing/Invariant.test.ts | 16 +- test/testing/MachineTest.test.ts | 28 +- test/testing/Probe.test.ts | 47 +- test/testing/ReferenceModel.test.ts | 20 +- test/testing/Runtime.test.ts | 55 +- test/testing/Verification.test.ts | 91 +- test/unstable/cluster/ClusterMachine.test.ts | 43 +- test/unstable/reactivity/AtomMachine.test.ts | 35 +- typetest/machine/Activities.tst.ts | 10 +- typetest/machine/Choice.tst.ts | 41 +- typetest/machine/EventByTag.tst.ts | 16 +- typetest/machine/EventConstructors.tst.ts | 16 +- typetest/machine/History.tst.ts | 26 +- typetest/machine/InitialEntry.tst.ts | 70 +- typetest/machine/Inspection.tst.ts | 15 +- typetest/machine/Machine.tst.ts | 357 +++--- typetest/machine/MachineReferences.tst.ts | 33 +- typetest/machine/Readiness.tst.ts | 5 +- typetest/machine/Resume.tst.ts | 5 +- typetest/machine/SnapshotContext.tst.ts | 32 +- typetest/machine/StructuralStates.tst.ts | 28 +- typetest/testing/Coverage.tst.ts | 5 +- typetest/testing/Exploration.tst.ts | 10 +- typetest/testing/Invariant.tst.ts | 10 +- typetest/testing/MachineTest.tst.ts | 15 +- typetest/testing/Probe.tst.ts | 10 +- .../unstable/cluster/ClusterMachine.tst.ts | 11 +- .../unstable/reactivity/AtomMachine.tst.ts | 10 +- 81 files changed, 2160 insertions(+), 3531 deletions(-) create mode 100644 .changeset/fluent-transition-handlers.md diff --git a/.changeset/fluent-transition-handlers.md b/.changeset/fluent-transition-handlers.md new file mode 100644 index 0000000..db38231 --- /dev/null +++ b/.changeset/fluent-transition-handlers.md @@ -0,0 +1,22 @@ +--- +"@typeonce/effect-machine": minor +--- + +Replace `Machine.transition(...)` with fluent transition selectors supplied directly to inline handlers. Select a target and optionally attach its resolver, reentry, or named branches without an intermediate wrapper: + +```ts +Start: ; +;((to) => to.full.Running().resolve(({ event, target }) => target.from({ count: event.count }))) + +Route: ; +;((to) => + to.branches({ + running: { target: to.full.Running() }, + done: { target: to.full.Done() }, + unchanged: { target: to.none() } + }).resolve(({ event, select }) => event.cached ? select.done.from() : select.running.from())) +``` + +Use `.reenter()` for resolver-free reentry, or pass literal `declinable: true` to `.resolve(...)` when the resolver must receive `decline()`. Bare targets are accepted only when their schemas support default construction. + +Remove the machine-definition `.invoke(...)` method. Use `Machine.invoke(...)` in every state; it now retains the owning state, event, parent-event, output, error, element, snapshot, and service inference directly inside `handle(...)`. diff --git a/README.md b/README.md index 7b813f5..2d9fff4 100644 --- a/README.md +++ b/README.md @@ -75,22 +75,13 @@ const CounterDefinition = Machine.make({ const Counter = CounterDefinition.handle({ Idle: { on: { - Start: Machine.transition({ - target: (to) => to.full.Running(), - resolve: ({ target }) => target.from({ count: 0 }) - }) + Start: (to) => to.full.Running().resolve(({ target }) => target.from({ count: 0 })) } }, Running: { on: { - Increment: Machine.transition({ - target: (to) => to.full.Running(), - resolve: ({ state, target }) => target.from({ count: state.count + 1 }) - }), - Stop: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ target }) => target.from() - }) + Increment: (to) => to.full.Running().resolve(({ state, target }) => target.from({ count: state.count + 1 })), + Stop: (to) => to.full.Idle().resolve(({ target }) => target.from()) } } }) @@ -153,13 +144,13 @@ When sibling states share fields, remove the source discriminator and pass the remaining fields through the target schema: ```ts -Submit: Machine.transition({ - target: (to) => to.local.Saving(), - resolve: ({ state, target }) => { - const { _tag: _, ...fields } = state - return target.from({ ...fields, attempt: 1 }) - } -}) +const handlers = { + Submit: (to) => + to.local.Saving().resolve(({ state, target }) => { + const { _tag: _, ...fields } = state + return target.from({ ...fields, attempt: 1 }) + }) +} ``` Omit `schema` when a state represents control flow but owns no data: @@ -339,15 +330,13 @@ const child = Machine.make({ }).handle({ Working: { on: { - Finish: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ parent, target }, enqueue) => { + Finish: (to) => + to.full.Done().resolve(({ parent, target }, enqueue) => { if (parent !== undefined) { enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" })) } return target.from() - } - }) + }) } }, Done: {} @@ -383,25 +372,31 @@ paths. `parent` always means the owning machine target. | `target.full` | Replacing or selecting a complete root | Nothing implicit for a newly selected root | | `target.history` | Restoring a declared history node | The remembered configuration or its typed default | -Every required transition handler returns either a concrete target or -`target.none()`. An absent handler ignores the trigger; `target.none()` handles +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 it and retains queued commands, raised events, and emitted events without -selecting a destination. Declared `targets` constrain only concrete -destinations, so `target.none()` is always permitted. Builders describe the +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; use `{ reenter: true, transition }` when the source must restart. With -`target.none()`, reentry restarts the source while retaining its configuration. +change; call `.reenter()` for resolver-free reentry or pass `{ reenter: true }` +to `.resolve(...)` when the source must restart. With `to.none()`, reentry +restarts the source while retaining its configuration. 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: ```ts -Submit: Machine.transition({ - declinable: true, - target: (to) => to.local.Saving(), - resolve: ({ event, target, decline }) => accepts(event) ? target.from({ draft: event.draft }) : decline() -}) +const handlers = { + Submit: (to) => + to.local.Saving().resolve( + ({ event, target, decline }) => accepts(event) ? target.from({ draft: event.draft }) : decline(), + { declinable: true } + ) +} ``` Declining discards work enqueued by that resolver. Event and eventless dispatch @@ -444,14 +439,8 @@ Loading: { invoke: Machine.invoke({ id: "save-document", effect: () => saveDocument, - onDone: Machine.transition({ - target: (to) => to.full.Saved(), - resolve: ({ output, target }) => target.from({ id: output.id }) - }), - onFailure: Machine.transition({ - target: (to) => to.full.Failed(), - resolve: ({ error, target }) => target.from({ message: String(error) }) - }) + onDone: (to) => to.full.Saved().resolve(({ output, target }) => target.from({ id: output.id })), + onFailure: (to) => to.full.Failed().resolve(({ error, target }) => target.from({ message: String(error) })) }) } @@ -459,10 +448,7 @@ Waiting: { invoke: Machine.invoke({ id: "save-timeout", after: "3 seconds", - onDone: Machine.transition({ - target: (to) => to.full.Failed(), - resolve: ({ target }) => target.from({ message: "Timed out" }) - }) + onDone: (to) => to.full.Failed().resolve(({ target }) => target.from({ message: "Timed out" })) }) } ``` @@ -478,14 +464,8 @@ for state-dependent Effects: invoke: Machine.invoke({ id: "load-document", effect: ({ state }) => loadDocument(state.documentId), - onDone: Machine.transition({ - target: (to) => to.full.Ready(), - resolve: ({ output, target }) => target.from({ document: output }) - }), - onFailure: Machine.transition({ - target: (to) => to.full.Failed(), - resolve: ({ error, target }) => target.from({ message: error.message }) - }) + onDone: (to) => to.full.Ready().resolve(({ output, target }) => target.from({ document: output })), + onFailure: (to) => to.full.Failed().resolve(({ error, target }) => target.from({ message: error.message })) }) ``` @@ -504,17 +484,15 @@ invoke: Machine.invoke({ } }, onDone: { target: Machine.targetless }, - onFailure: Machine.transition({ - target: (to) => to.full.Failed(), - resolve: ({ error, target }) => target.from({ error }) - }) + 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 -`Machine.transition(...)` for transitions that select state or reenter. +the same fluent `to` builder to select a target for transitions that change +state or reenter. Inside `.handle(...)`, `Machine.invoke(...)` receives the owning machine's public input and `parentEvents` protocols contextually. Its source and lifecycle @@ -532,27 +510,23 @@ const machine = Machine.make({ invoke: Machine.invoke({ id: "notify-parent", effect: () => saveDocument, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: ({ parent, self }, enqueue) => { + onDone: (to) => + 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: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + }), + onFailure: (to) => to.none() }) } }) ``` -The machine-bound `definition.invoke(...)` form remains equivalent when a -definition is already named; it is not required for `self` or `parent` typing. +The standard `Machine.invoke(...)` form retains exact `self` and `parent` +typing even when the definition is named separately; no intermediate +definition method is required. A direct `invoke: { ... }` object is also supported when its lifecycle handlers do not need source-derived context. Reuse one exported diff --git a/api-reference.config.json b/api-reference.config.json index 4184585..658a7c6 100644 --- a/api-reference.config.json +++ b/api-reference.config.json @@ -37,8 +37,7 @@ "resume", "state", "start", - "states", - "transition" + "states" ] }, { diff --git a/docs/agent-guide.md b/docs/agent-guide.md index d808d7e..c0f7b6d 100644 --- a/docs/agent-guide.md +++ b/docs/agent-guide.md @@ -128,8 +128,8 @@ its extra control is required: logic, and `child` for a complete child statechart. `Machine.invoke({...})` preserves owner state and source channels across sibling lifecycle handlers. Inside `.handle(...)`, `self` and `parent` - use the owning definition's exact public input and `parentEvents` protocols. - The bound `definition.invoke({...})` form is equivalent, not required. + use the owning definition's exact public input and `parentEvents` protocols; + no intermediate definition method is required. - Use `Machine.child(id, machine)` for a complete statechart descriptor and `Machine.childAddress(id)` for a low-level process address. A logic invocation is addressable only when `Machine.invoke` receives that @@ -244,13 +244,11 @@ in snapshots. They do not have a state value: ```ts Idle: { on: { - Start: Machine.transition({ - target: (to) => to.full.Form.initial(), - resolve: ({ state, target }) => { + Start: (to) => + to.full.Form.initial().resolve(({ state, target }) => { // state: undefined return target.from((form) => form.Editing.from()) - } - }) + }) } } @@ -351,10 +349,7 @@ with `.initial`. This is available on top-level state methods under `target.branch`; atomic and final state methods do not expose it: ```ts -Open: Machine.transition({ - target: (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 selected state's own value is passed directly to `initial(value)` or @@ -431,10 +426,7 @@ checkout: { Target it without a value: ```ts -Resume: Machine.transition({ - target: (to) => to.history.checkout.exact(), - resolve: ({ target }) => target() -}) +Resume: (to) => to.history.checkout.exact().resolve(({ target }) => target()) ``` Deep history restores the complete remembered subtree and its decoded values. @@ -497,11 +489,16 @@ sets. A `target.full` result with the same active paths can update values withou exiting shared states. To force the source to exit and enter again: ```ts -Refresh: Machine.transition({ - target: (to) => to.full.Ready(), - reenter: true, - resolve: ({ state, target }) => target.from({ value: state.value }) -}) +Refresh: (to) => + to.full.Ready().resolve(({ state, target }) => target.from({ value: state.value }), { reenter: true }) +``` + +When no resolver is needed, use the selected target directly and append +`.reenter()` only when restart semantics are intentional: + +```ts +Finish: (to) => to.full.Done() +Restart: (to) => to.none().reenter() ``` Do not use `target.full` merely because it is easiest to discover. Prefer the @@ -578,16 +575,14 @@ Event, `always`, and `onDone` transition contexts include a fully typed microstep, before any selected transition is applied: ```ts -BufferReady: Machine.transition({ - branches: (to) => ({ +BufferReady: (to) => + to.branches({ online: { target: to.local.Playing() }, unchanged: { target: to.none() } - }), - resolve: ({ snapshot, select }) => + }).resolve(({ snapshot, select }) => States.matches(snapshot, "Player.Network.Online") ? select.online.from() : select.unchanged() -}) ``` Use the existing `States.matches`, `States.get`, `States.getWithParents`, and @@ -622,13 +617,11 @@ When sibling state payloads share fields, destructure away the source discriminator and construct the destination through its target builder: ```ts -Submit: Machine.transition({ - target: (to) => to.local.Saving(), - resolve: ({ state, target }) => { +Submit: (to) => + to.local.Saving().resolve(({ state, target }) => { const { _tag: _, ...fields } = state return target.from({ ...fields, attempt: 1 }) - } -}) + }) ``` The target schema remains responsible for defaults, transforms, refinements, @@ -641,21 +634,19 @@ A transition declares every possible branch and resolves the selected target synchronously: ```ts -Submit: Machine.transition({ - branches: (to) => ({ +Submit: (to) => + to.branches({ valid: { target: to.local.Saving() }, invalid: { target: to.none() } - }), - resolve: ({ state, select }) => state.valid + }).resolve(({ state, select }) => state.valid ? select.valid.from({ draft: state.draft }) : select.invalid() -}) ``` Every installed event, `always`, `onDone`, choice, and invoke lifecycle handler -must use `Machine.transition`. A direct transition declares one `target`. A -branching transition declares every possible target in a named `branches` -record, then uses ordinary TypeScript control flow in `resolve` to return one +receives a bound `to` selector. A direct transition selects one target and calls +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 @@ -666,17 +657,14 @@ not enabled. The flag adds a typed `decline()` capability to that resolver and permits its opaque result: ```ts -Submit: Machine.transition({ - declinable: true, - branches: (to) => ({ +Submit: (to) => + to.branches({ accepted: { target: to.local.Saving() }, consumed: { target: to.none() } - }), - resolve: ({ event, select, decline }) => { + }).resolve(({ event, select, decline }) => { if (!belongsToThisState(event)) return decline() return event.consume ? select.consumed() : select.accepted.from() - } -}) + }, { declinable: true }) ``` Declining discards that resolver's enqueue buffer and resumes hierarchical @@ -701,13 +689,11 @@ enters again while its logical configuration is retained. Closed statechart and machine operations use `enqueue`: ```ts -Submit: Machine.transition({ - target: (to) => to.local.Saving(), - resolve: ({ target }, enqueue) => { +Submit: (to) => + to.local.Saving().resolve(({ target }, enqueue) => { enqueue.emit(Emissions.SaveRequested()) return target.from() - } -}) + }) ``` Declare emission constructors separately from machine inputs: @@ -801,15 +787,13 @@ const child = Machine.make({ }).handle({ Working: { on: { - Finish: Machine.transition({ - target: (to) => to.none(), - resolve: ({ parent }, enqueue) => { + Finish: (to) => + to.none().resolve(({ parent }, enqueue) => { if (parent !== undefined) { enqueue.sendTo(parent, ParentEvents.ChildFinished()) } return undefined - } - }) + }) } } }) @@ -943,14 +927,9 @@ receive the typed Effect channels and can transition directly: invoke: Machine.invoke({ id: "save", effect: () => SaveService.save(draft), - onDone: Machine.transition({ - target: (to) => to.full.Saved(), - resolve: ({ output, target }) => target.from({ entry: output }) - }), - onFailure: Machine.transition({ - target: (to) => to.full.SaveFailed(), - resolve: ({ error, target }) => target.from({ message: error.message }) - }) + onDone: (to) => to.full.Saved().resolve(({ output, target }) => target.from({ entry: output })), + onFailure: (to) => + to.full.SaveFailed().resolve(({ error, target }) => target.from({ message: error.message })) }) ``` @@ -980,10 +959,7 @@ invoke: Machine.invoke({ } }, onDone: { target: Machine.targetless }, - onFailure: Machine.transition({ - target: (to) => to.full.Disconnected(), - resolve: ({ error, target }) => target.from({ error }) - }) + onFailure: (to) => to.full.Disconnected().resolve(({ error, target }) => target.from({ error })) }) ``` @@ -994,8 +970,8 @@ 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`. Keep `Machine.transition(...)` for full state selection, -named branches, or reentry. +must return `undefined`. Use the same fluent `to` selector for full state +selection, named branches, and reentry. When a source function reads `state`, `containingState`, `ancestors`, or the entry `event`, `Machine.invoke` infers that owner context and the returned Effect's output, @@ -1005,14 +981,8 @@ error, and service channels together. No return annotation is needed: invoke: Machine.invoke({ id: "load", effect: ({ state }) => LoadService.load(state.userId), - onDone: Machine.transition({ - target: (to) => to.full.Loaded(), - resolve: ({ output, target }) => target.from({ user: output }) - }), - onFailure: Machine.transition({ - target: (to) => to.full.LoadFailed(), - resolve: ({ error, target }) => target.from({ error }) - }) + onDone: (to) => to.full.Loaded().resolve(({ output, target }) => target.from({ user: output })), + onFailure: (to) => to.full.LoadFailed().resolve(({ error, target }) => target.from({ error })) }) ``` @@ -1031,28 +1001,23 @@ const machine = Machine.make({ invoke: Machine.invoke({ id: "notify-parent", effect: () => saveDocument, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: ({ parent, self }, enqueue) => { + onDone: (to) => + 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: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + }), + onFailure: (to) => to.none() }) } }) ``` -The machine-bound `definition.invoke(...)` form remains equivalent when the -definition is already named. A direct `invoke: { ... }` object remains available -when lifecycle handlers do not need source-derived context. +The standard `Machine.invoke(...)` form retains the owning machine protocols +even when the definition is named separately. A direct `invoke: { ... }` object +remains available when lifecycle handlers do not need source-derived context. A cancellable timer uses the same object: @@ -1060,10 +1025,7 @@ A cancellable timer uses the same object: invoke: Machine.invoke({ id: "clear-status", after: "3 seconds", - onDone: Machine.transition({ - target: (to) => to.full.Clear(), - resolve: ({ target }) => target() - }) + onDone: (to) => to.full.Clear().resolve(({ target }) => target()) }) ``` @@ -1090,10 +1052,7 @@ Invoke it from its owning state: invoke: Machine.invoke({ child: Editor, input: editorInput, - onDone: Machine.transition({ - target: (to) => to.full.EditorDone(), - resolve: ({ output, target }) => target.from({ output }) - }) + onDone: (to) => to.full.EditorDone().resolve(({ output, target }) => target.from({ output })) }) ``` diff --git a/examples/platformer/src/machine.ts b/examples/platformer/src/machine.ts index be20a20..6f3a22b 100644 --- a/examples/platformer/src/machine.ts +++ b/examples/platformer/src/machine.ts @@ -135,9 +135,8 @@ export const CharacterMachine = Machine.make({ }).handle({ Character: { on: { - Reset: Machine.transition({ - target: (to) => to.full.Character(), - resolve: ({ target }) => + Reset: (to) => + to.full.Character().resolve(({ target }) => target.from((character) => character .locomotion.from((locomotion) => @@ -146,7 +145,7 @@ export const CharacterMachine = Machine.make({ .facing.from((facing) => facing.Right.from()) .contact.from((contact) => contact.NoWall.from()) ) - }) + ) }, states: { locomotion: { @@ -168,17 +167,16 @@ export const CharacterMachine = Machine.make({ } }, on: { - Pause: Machine.transition({ - target: (to) => to.branch.Character.locomotion.Paused(), - resolve: ({ event, target }) => target.from({ pausedAt: event.at }) - }) + Pause: (to) => + to.branch.Character.locomotion.Paused().resolve(({ event, target }) => + target.from({ pausedAt: event.at }) + ) }, states: { Grounded: { on: { - JumpPressed: Machine.transition({ - target: (to) => to.full.Character(), - resolve: ({ event, target }) => + JumpPressed: (to) => + to.full.Character().resolve(({ event, target }) => target.from((character) => character .locomotion.from((locomotion) => @@ -200,98 +198,85 @@ export const CharacterMachine = Machine.make({ : contact.NoWall.from() ) ) - }) + ) }, states: { Standing: { on: { - Move: Machine.transition({ - branches: (to) => ({ + Move: (to) => + to.branches({ moving: { target: to.local.Running() }, unchanged: { target: to.none() } - }), - resolve: ({ event, select }) => + }).resolve(({ event, select }) => event.axis === 0 ? select.unchanged() : select.moving.from({ startedAt: event.at }) - }), - DownPressed: Machine.transition({ - target: (to) => to.local.Ducking(), - resolve: ({ event, target }) => target.from({ startedAt: event.at }) - }) + ), + DownPressed: (to) => + to.local.Ducking().resolve(({ event, target }) => target.from({ startedAt: event.at })) } }, Running: { on: { - Move: Machine.transition({ - branches: (to) => ({ + Move: (to) => + to.branches({ stopped: { target: to.local.Standing() }, unchanged: { target: to.none() } - }), - resolve: ({ event, select }) => + }).resolve(({ event, select }) => event.axis === 0 ? select.stopped.from() : select.unchanged() - }), - DownPressed: Machine.transition({ - target: (to) => to.local.Ducking(), - resolve: ({ event, target }) => target.from({ startedAt: event.at }) - }) + ), + DownPressed: (to) => + to.local.Ducking().resolve(({ event, target }) => target.from({ startedAt: event.at })) } }, Ducking: { on: { - DownReleased: Machine.transition({ - branches: (to) => ({ + DownReleased: (to) => + to.branches({ stopped: { target: to.local.Standing() }, running: { target: to.local.Running() } - }), - resolve: ({ event, select }) => + }).resolve(({ event, select }) => event.axis === 0 ? select.stopped.from() : select.running.from({ startedAt: event.at }) - }) + ) } }, Landing: { invoke: Machine.invoke({ id: "landing-settle", after: "140 millis", - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: (_, enqueue) => { + onDone: (to) => + to.none().resolve((_, enqueue) => { enqueue.raise(InternalEvents.LandingSettled()) return undefined - } - }) + }) }), on: { - LandingSettled: Machine.transition({ - branches: (to) => ({ + LandingSettled: (to) => + to.branches({ stopped: { target: to.local.Standing() }, running: { target: to.local.Running() } - }), - resolve: ({ state, select }) => + }).resolve(({ state, select }) => state.resumeAxis === 0 ? select.stopped.from() : select.running.from({ startedAt: state.landedAt + 140 }) - }), - Move: Machine.transition({ - target: (to) => to.local.Landing(), - resolve: ({ event, state, target }) => { + ), + Move: (to) => + to.local.Landing().resolve(({ event, state, target }) => { const { _tag: _, ...fields } = state return target.from({ ...fields, resumeAxis: event.axis }) - } - }) + }) } } } }, Airborne: { on: { - JumpPressed: Machine.transition({ - target: (to) => to.none(), - resolve: ({ event }, enqueue) => { + JumpPressed: (to) => + to.none().resolve(({ event }, enqueue) => { const push = awayFrom(event.wall) enqueue.raise( push === 0 @@ -299,11 +284,9 @@ export const CharacterMachine = Machine.make({ : InternalEvents.WallJump({ at: event.at, push }) ) return undefined - } - }), - Landed: Machine.transition({ - target: (to) => to.branch.Character.locomotion.Playing.Grounded(), - resolve: ({ event, target }) => + }), + Landed: (to) => + to.branch.Character.locomotion.Playing.Grounded().resolve(({ event, target }) => target.from((grounded) => grounded.Landing.from({ impact: event.impact, @@ -311,40 +294,33 @@ export const CharacterMachine = Machine.make({ landedAt: event.at }) ) - }) + ) }, states: { motion: { on: { - DoubleJump: Machine.transition({ - target: (to) => to.local.Jumping(), - resolve: ({ event, target }) => target.from({ startedAt: event.at, push: 0, kind: "Double" }) - }), - WallJump: Machine.transition({ - target: (to) => to.local.Jumping(), - resolve: ({ event, target }) => + DoubleJump: (to) => + to.local.Jumping().resolve(({ event, target }) => + target.from({ startedAt: event.at, push: 0, kind: "Double" }) + ), + WallJump: (to) => + to.local.Jumping().resolve(({ event, target }) => target.from({ startedAt: event.at, push: event.push, kind: "Wall" }) - }) + ) }, states: { Jumping: { on: { - ApexReached: Machine.transition({ - target: (to) => to.local.Falling(), - resolve: ({ event, target }) => target.from({ apexY: event.y }) - }), - DownPressed: Machine.transition({ - target: (to) => to.local.Diving(), - resolve: ({ event, target }) => target.from({ startedAt: event.at }) - }) + ApexReached: (to) => + to.local.Falling().resolve(({ event, target }) => target.from({ apexY: event.y })), + DownPressed: (to) => + to.local.Diving().resolve(({ event, target }) => target.from({ startedAt: event.at })) } }, Falling: { on: { - DownPressed: Machine.transition({ - target: (to) => to.local.Diving(), - resolve: ({ event, target }) => target.from({ startedAt: event.at }) - }) + DownPressed: (to) => + to.local.Diving().resolve(({ event, target }) => target.from({ startedAt: event.at })) } }, Diving: {} @@ -352,21 +328,15 @@ export const CharacterMachine = Machine.make({ }, airJump: { on: { - WallJump: Machine.transition({ - target: (to) => to.local.AirJumpWallLock(), - resolve: ({ target }) => target.from(), - reenter: true - }) + WallJump: (to) => + to.local.AirJumpWallLock().resolve(({ target }) => target.from(), { reenter: true }) }, states: { AirJumpGroundLock: { invoke: Machine.invoke({ id: "ground-air-jump-unlock", after: "120 millis", - onDone: Machine.transition({ - target: (to) => to.local.AirJumpReady(), - resolve: ({ target }) => target.from() - }) + onDone: (to) => to.local.AirJumpReady().resolve(({ target }) => target.from()) }), on: {} }, @@ -374,22 +344,17 @@ export const CharacterMachine = Machine.make({ invoke: Machine.invoke({ id: "wall-air-jump-unlock", after: "240 millis", - onDone: Machine.transition({ - target: (to) => to.local.AirJumpReady(), - resolve: ({ target }) => target.from() - }) + onDone: (to) => to.local.AirJumpReady().resolve(({ target }) => target.from()) }), on: {} }, AirJumpReady: { on: { - TryAirJump: Machine.transition({ - target: (to) => to.local.AirJumpSpent(), - resolve: ({ event, target }, enqueue) => { + TryAirJump: (to) => + to.local.AirJumpSpent().resolve(({ event, target }, enqueue) => { enqueue.raise(InternalEvents.DoubleJump({ at: event.at })) return target.from() - } - }) + }) } }, AirJumpSpent: {} @@ -401,10 +366,7 @@ export const CharacterMachine = Machine.make({ }, Paused: { on: { - Resume: Machine.transition({ - target: (to) => to.history.Character.locomotion.Playing.resume(), - resolve: ({ target }) => target() - }) + Resume: (to) => to.history.Character.locomotion.Playing.resume().resolve(({ target }) => target()) } } } @@ -413,45 +375,44 @@ export const CharacterMachine = Machine.make({ states: { Left: { on: { - Move: Machine.transition({ - branches: (to) => ({ right: { target: to.local.Right() }, unchanged: { target: to.none() } }), - resolve: ({ event, select }) => event.axis === 1 ? select.right.from() : select.unchanged() - }), - WallJump: Machine.transition({ - branches: (to) => ({ right: { target: to.local.Right() }, unchanged: { target: to.none() } }), - resolve: ({ event, select }) => event.push === 1 ? select.right.from() : select.unchanged() - }) + Move: (to) => + 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(( + { event, select } + ) => event.push === 1 ? select.right.from() : select.unchanged()) } }, Right: { on: { - Move: Machine.transition({ - branches: (to) => ({ left: { target: to.local.Left() }, unchanged: { target: to.none() } }), - resolve: ({ event, select }) => event.axis === -1 ? select.left.from() : select.unchanged() - }), - WallJump: Machine.transition({ - branches: (to) => ({ left: { target: to.local.Left() }, unchanged: { target: to.none() } }), - resolve: ({ event, select }) => event.push === -1 ? select.left.from() : select.unchanged() - }) + Move: (to) => + 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(( + { event, select } + ) => event.push === -1 ? select.left.from() : select.unchanged()) } } } }, contact: { on: { - WallContact: Machine.transition({ - branches: (to) => ({ + WallContact: (to) => + to.branches({ leftWall: { title: "Left wall", target: to.local.LeftWall() }, rightWall: { title: "Right wall", target: to.local.RightWall() }, noWall: { title: "No wall", target: to.local.NoWall() } - }), - resolve: ({ event, select }) => + }).resolve(({ event, select }) => event.wall === -1 ? select.leftWall.from() : event.wall === 1 ? select.rightWall.from() : select.noWall.from() - }) + ) }, states: { NoWall: {}, diff --git a/examples/playground/src/examples/media-player/machine.ts b/examples/playground/src/examples/media-player/machine.ts index 0fb441b..299bba4 100644 --- a/examples/playground/src/examples/media-player/machine.ts +++ b/examples/playground/src/examples/media-player/machine.ts @@ -10,105 +10,79 @@ export const MediaPlayerMachine = MediaPlayerDefinition.handle({ states: { transport: { on: { - SourceSelected: Machine.transition({ - target: (to) => to.local.Loading(), - resolve: ({ event, target }) => target.from({ url: event.url }), - reenter: true - }), + SourceSelected: (to) => + to.local.Loading().resolve(({ event, target }) => target.from({ url: event.url }), { reenter: true }), - MediaFailed: Machine.transition({ - target: (to) => to.local.Failed(), - resolve: ({ event, target }) => target.from({ message: event.message }) - }), + MediaFailed: (to) => + to.local.Failed().resolve(({ event, target }) => target.from({ message: event.message })), - OperationFailed: Machine.transition({ - target: (to) => to.local.Failed(), - resolve: ({ event, target }) => target.from({ message: event.message }) - }) + OperationFailed: (to) => + to.local.Failed().resolve(({ event, target }) => target.from({ message: event.message })) }, states: { Empty: {}, Loading: { - invoke: MediaPlayerDefinition.invoke({ + invoke: Machine.invoke({ id: "load-audio", effect: ({ state }) => loadAudio(state.url), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: (_, enqueue) => { + onDone: (to) => + to.none().resolve((_, enqueue) => { enqueue.raise(MediaPlayerInternalEvents.LoadSucceeded()) return undefined - } - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: ({ error }, enqueue) => { + }), + onFailure: (to) => + to.none().resolve(({ error }, enqueue) => { enqueue.raise(MediaPlayerInternalEvents.OperationFailed({ message: error.message })) return undefined - } - }) + }) }), on: { - LoadSucceeded: Machine.transition({ - target: (to) => to.local.Ready(), - resolve: ({ target }) => target.from((ready) => ready.Paused.from(initialPlaybackData)) - }) + LoadSucceeded: (to) => + to.local.Ready().resolve(({ target }) => target.from((ready) => ready.Paused.from(initialPlaybackData))) } }, Ready: { states: { Paused: { - invoke: MediaPlayerDefinition.invoke({ + invoke: Machine.invoke({ id: "pause-audio", effect: () => pauseAudio, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: ({ error }, enqueue) => { + onDone: { target: Machine.targetless }, + onFailure: (to) => + to.none().resolve(({ error }, enqueue) => { enqueue.raise(MediaPlayerInternalEvents.OperationFailed({ message: error.message })) return undefined - } - }) + }) }), on: { - PlayRequested: Machine.transition({ - target: (to) => to.local.Playing(), - resolve: ({ state, target }) => + PlayRequested: (to) => + to.local.Playing().resolve(({ state, target }) => target.from({ ...updatePlaybackData(state, {}), loudness: null }) - }), + ), - RestartRequested: Machine.transition({ - target: (to) => to.local.Restarting(), - resolve: ({ state, target }) => target.from(updatePlaybackData(state, {})) - }) + RestartRequested: (to) => + to.local.Restarting().resolve(({ state, target }) => target.from(updatePlaybackData(state, {}))) } }, Playing: { invoke: [ - MediaPlayerDefinition.invoke({ + Machine.invoke({ id: "play-audio", effect: () => playAudio, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: ({ error }, enqueue) => { + onDone: { target: Machine.targetless }, + onFailure: (to) => + to.none().resolve(({ error }, enqueue) => { enqueue.raise(MediaPlayerInternalEvents.OperationFailed({ message: error.message })) return undefined - } - }) + }) }), - MediaPlayerDefinition.invoke({ + Machine.invoke({ id: "analyze-audio", stream: () => analyzeAudio, onElement: { @@ -127,39 +101,30 @@ export const MediaPlayerMachine = MediaPlayerDefinition.handle({ }) ], on: { - PauseRequested: Machine.transition({ - target: (to) => to.local.Paused(), - resolve: ({ state, target }) => target.from(updatePlaybackData(state, {})) - }), + PauseRequested: (to) => + to.local.Paused().resolve(({ state, target }) => target.from(updatePlaybackData(state, {}))), - RestartRequested: Machine.transition({ - target: (to) => to.local.Restarting(), - resolve: ({ state, target }) => target.from(updatePlaybackData(state, {})) - }), + RestartRequested: (to) => + to.local.Restarting().resolve(({ state, target }) => target.from(updatePlaybackData(state, {}))), - MediaWaiting: Machine.transition({ - target: (to) => to.local.Buffering(), - resolve: ({ state, target }) => target.from(updatePlaybackData(state, {})) - }), + MediaWaiting: (to) => + to.local.Buffering().resolve(({ state, target }) => target.from(updatePlaybackData(state, {}))), - PlaybackEnded: Machine.transition({ - target: (to) => to.local.Ended(), - resolve: ({ event, state, target }) => + PlaybackEnded: (to) => + to.local.Ended().resolve(({ event, state, target }) => target.from(updatePlaybackData(state, { currentTime: event.currentTime })) - }), + ), - TimeUpdated: Machine.transition({ - target: (to) => to.local.Playing(), - resolve: ({ event, state, target }) => + TimeUpdated: (to) => + to.local.Playing().resolve(({ event, state, target }) => target.from({ currentTime: event.currentTime, loudness: state.loudness }) - }), + ), - LoudnessMeasured: Machine.transition({ - target: (to) => to.local.Playing(), - resolve: ({ event, state, target }) => + LoudnessMeasured: (to) => + to.local.Playing().resolve(({ event, state, target }) => target.from({ currentTime: state.currentTime, loudness: { @@ -168,106 +133,85 @@ export const MediaPlayerMachine = MediaPlayerDefinition.handle({ decibels: event.decibels } }) - }) + ) } }, Buffering: { on: { - MediaCanPlay: Machine.transition({ - target: (to) => to.local.Playing(), - resolve: ({ state, target }) => + MediaCanPlay: (to) => + to.local.Playing().resolve(({ state, target }) => target.from({ ...updatePlaybackData(state, {}), loudness: null }) - }), + ), - PauseRequested: Machine.transition({ - target: (to) => to.local.Paused(), - resolve: ({ state, target }) => target.from(updatePlaybackData(state, {})) - }), + PauseRequested: (to) => + to.local.Paused().resolve(({ state, target }) => target.from(updatePlaybackData(state, {}))), - RestartRequested: Machine.transition({ - target: (to) => to.local.Restarting(), - resolve: ({ state, target }) => target.from(updatePlaybackData(state, {})) - }), + RestartRequested: (to) => + to.local.Restarting().resolve(({ state, target }) => target.from(updatePlaybackData(state, {}))), - PlaybackEnded: Machine.transition({ - target: (to) => to.local.Ended(), - resolve: ({ event, state, target }) => + PlaybackEnded: (to) => + to.local.Ended().resolve(({ event, state, target }) => target.from(updatePlaybackData(state, { currentTime: event.currentTime })) - }), + ), - TimeUpdated: Machine.transition({ - target: (to) => to.local.Buffering(), - resolve: ({ event, state, target }) => + TimeUpdated: (to) => + to.local.Buffering().resolve(({ event, state, target }) => target.from(updatePlaybackData(state, { currentTime: event.currentTime })) - }) + ) } }, Restarting: { - invoke: MediaPlayerDefinition.invoke({ + invoke: Machine.invoke({ id: "restart-audio", effect: () => restartAudio, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: (_, enqueue) => { + onDone: (to) => + to.none().resolve((_, enqueue) => { enqueue.raise(MediaPlayerInternalEvents.RestartSucceeded()) return undefined - } - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: ({ error }, enqueue) => { + }), + onFailure: (to) => + to.none().resolve(({ error }, enqueue) => { enqueue.raise(MediaPlayerInternalEvents.OperationFailed({ message: error.message })) return undefined - } - }) + }) }), on: { - RestartSucceeded: Machine.transition({ - target: (to) => to.local.Playing(), - resolve: ({ target }) => target.from({ currentTime: 0, loudness: null }) - }), + RestartSucceeded: (to) => + to.local.Playing().resolve(({ target }) => target.from({ currentTime: 0, loudness: null })), - TimeUpdated: Machine.transition({ - target: (to) => to.local.Restarting(), - resolve: ({ event, state, target }) => + TimeUpdated: (to) => + to.local.Restarting().resolve(({ event, state, target }) => target.from(updatePlaybackData(state, { currentTime: event.currentTime })) - }) + ) } }, Ended: { on: { - PlayRequested: Machine.transition({ - target: (to) => to.local.Restarting(), - resolve: ({ state, target }) => target.from(updatePlaybackData(state, {})) - }), + PlayRequested: (to) => + to.local.Restarting().resolve(({ state, target }) => target.from(updatePlaybackData(state, {}))), - RestartRequested: Machine.transition({ - target: (to) => to.local.Restarting(), - resolve: ({ state, target }) => target.from(updatePlaybackData(state, {})) - }) + RestartRequested: (to) => + to.local.Restarting().resolve(({ state, target }) => target.from(updatePlaybackData(state, {}))) } } } }, Failed: { - invoke: MediaPlayerDefinition.invoke({ + invoke: Machine.invoke({ id: "report-error", effect: ({ state }) => Effect.gen(function*() { const mediaPlayer = yield* MediaPlayer yield* mediaPlayer.reportError(state.message) }), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }) } } @@ -276,84 +220,64 @@ export const MediaPlayerMachine = MediaPlayerDefinition.handle({ settings: { states: { Audible: { - invoke: MediaPlayerDefinition.invoke({ + invoke: Machine.invoke({ id: "apply-audio-settings", effect: ({ state }) => applyAudioSettings(state, false), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }), on: { - VolumeChanged: Machine.transition({ - target: (to) => to.local.Audible(), - resolve: ({ event, state, target }) => + VolumeChanged: (to) => + to.local.Audible().resolve(({ event, state, target }) => target.from({ volume: event.volume, playbackRate: state.playbackRate - }), - reenter: true - }), + }), { reenter: true }), - PlaybackRateChanged: Machine.transition({ - target: (to) => to.local.Audible(), - resolve: ({ event, state, target }) => + PlaybackRateChanged: (to) => + to.local.Audible().resolve(({ event, state, target }) => target.from({ volume: state.volume, playbackRate: event.playbackRate - }), - reenter: true - }), + }), { reenter: true }), - MuteRequested: Machine.transition({ - target: (to) => to.local.Muted(), - resolve: ({ state, target }) => + MuteRequested: (to) => + to.local.Muted().resolve(({ state, target }) => target.from({ volume: state.volume, playbackRate: state.playbackRate }) - }) + ) } }, Muted: { - invoke: MediaPlayerDefinition.invoke({ + invoke: Machine.invoke({ id: "apply-audio-settings", effect: ({ state }) => applyAudioSettings(state, true), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }), on: { - VolumeChanged: Machine.transition({ - target: (to) => to.local.Muted(), - resolve: ({ event, state, target }) => + VolumeChanged: (to) => + to.local.Muted().resolve(({ event, state, target }) => target.from({ volume: event.volume, playbackRate: state.playbackRate - }), - reenter: true - }), + }), { reenter: true }), - PlaybackRateChanged: Machine.transition({ - target: (to) => to.local.Muted(), - resolve: ({ event, state, target }) => + PlaybackRateChanged: (to) => + to.local.Muted().resolve(({ event, state, target }) => target.from({ volume: state.volume, playbackRate: event.playbackRate - }), - reenter: true - }), + }), { reenter: true }), - UnmuteRequested: Machine.transition({ - target: (to) => to.local.Audible(), - resolve: ({ state, target }) => + UnmuteRequested: (to) => + to.local.Audible().resolve(({ state, target }) => target.from({ volume: state.volume, playbackRate: state.playbackRate }) - }) + ) } } } diff --git a/examples/playground/src/examples/microwave/machine.ts b/examples/playground/src/examples/microwave/machine.ts index 982529c..5ed19d3 100644 --- a/examples/playground/src/examples/microwave/machine.ts +++ b/examples/playground/src/examples/microwave/machine.ts @@ -55,36 +55,29 @@ export const MicrowaveMachine = Machine.make({ states: { Idle: { on: { - PowerPressed: Machine.transition({ - branches: (to) => ({ + PowerPressed: (to) => + to.branches({ doorClosed: { title: "Door closed", target: to.local.Cooking() }, unchanged: { target: to.none() } - }), - resolve: ({ snapshot, select }) => + }).resolve(({ snapshot, select }) => MicrowaveStates.matches(snapshot, "Oven.door.Closed") ? select.doorClosed.from({ elapsedSeconds: 0 }) : select.unchanged() - }) + ) } }, Cooking: { invoke: Machine.invoke({ id: "cooking-second", after: "1 second", - onDone: Machine.transition({ - target: (to) => to.local.Cooking(), - resolve: ({ state, target }) => target.from({ elapsedSeconds: state.elapsedSeconds + 1 }) - }) + onDone: (to) => + to.local.Cooking().resolve(({ state, target }) => + target.from({ elapsedSeconds: state.elapsedSeconds + 1 }) + ) }), on: { - PowerPressed: Machine.transition({ - target: (to) => to.local.Idle(), - resolve: ({ target }) => target.from() - }), - DoorOpened: Machine.transition({ - target: (to) => to.local.Idle(), - resolve: ({ target }) => target.from() - }) + PowerPressed: (to) => to.local.Idle().resolve(({ target }) => target.from()), + DoorOpened: (to) => to.local.Idle().resolve(({ target }) => target.from()) } } } @@ -93,18 +86,12 @@ export const MicrowaveMachine = Machine.make({ states: { Closed: { on: { - DoorOpened: Machine.transition({ - target: (to) => to.local.Open(), - resolve: ({ target }) => target.from() - }) + DoorOpened: (to) => to.local.Open().resolve(({ target }) => target.from()) } }, Open: { on: { - DoorClosed: Machine.transition({ - target: (to) => to.local.Closed(), - resolve: ({ target }) => target.from() - }) + DoorClosed: (to) => to.local.Closed().resolve(({ target }) => target.from()) } } } diff --git a/examples/playground/src/examples/traffic-light/machine.ts b/examples/playground/src/examples/traffic-light/machine.ts index 322df01..83b9702 100644 --- a/examples/playground/src/examples/traffic-light/machine.ts +++ b/examples/playground/src/examples/traffic-light/machine.ts @@ -34,65 +34,40 @@ export const TrafficLightMachine = Machine.make({ invoke: Machine.invoke({ id: "red-timer", after: trafficLightDurations.Red, - onDone: Machine.transition({ - target: (to) => to.full.RedYellow(), - resolve: ({ target }) => target.from() - }) + onDone: (to) => to.full.RedYellow().resolve(({ target }) => target.from()) }), on: { - Reset: Machine.transition({ - target: (to) => to.full.Red(), - resolve: ({ target }) => target.from(), - reenter: true - }) + Reset: (to) => to.full.Red().resolve(({ target }) => target.from(), { reenter: true }) } }, RedYellow: { invoke: Machine.invoke({ id: "red-yellow-timer", after: trafficLightDurations.RedYellow, - onDone: Machine.transition({ - target: (to) => to.full.Green(), - resolve: ({ target }) => target.from() - }) + onDone: (to) => to.full.Green().resolve(({ target }) => target.from()) }), on: { - Reset: Machine.transition({ - target: (to) => to.full.Red(), - resolve: ({ target }) => target.from() - }) + Reset: (to) => to.full.Red().resolve(({ target }) => target.from()) } }, Green: { invoke: Machine.invoke({ id: "green-timer", after: trafficLightDurations.Green, - onDone: Machine.transition({ - target: (to) => to.full.Yellow(), - resolve: ({ target }) => target.from() - }) + onDone: (to) => to.full.Yellow().resolve(({ target }) => target.from()) }), on: { - Reset: Machine.transition({ - target: (to) => to.full.Red(), - resolve: ({ target }) => target.from() - }) + Reset: (to) => to.full.Red().resolve(({ target }) => target.from()) } }, Yellow: { invoke: Machine.invoke({ id: "yellow-timer", after: trafficLightDurations.Yellow, - onDone: Machine.transition({ - target: (to) => to.full.Red(), - resolve: ({ target }) => target.from() - }) + onDone: (to) => to.full.Red().resolve(({ target }) => target.from()) }), on: { - Reset: Machine.transition({ - target: (to) => to.full.Red(), - resolve: ({ target }) => target.from() - }) + Reset: (to) => to.full.Red().resolve(({ target }) => target.from()) } } }) diff --git a/examples/playground/src/examples/turnstile/machine.ts b/examples/playground/src/examples/turnstile/machine.ts index 60dc4da..f79b968 100644 --- a/examples/playground/src/examples/turnstile/machine.ts +++ b/examples/playground/src/examples/turnstile/machine.ts @@ -24,18 +24,12 @@ export const TurnstileMachine = Machine.make({ }).handle({ Locked: { on: { - CoinInserted: Machine.transition({ - target: (to) => to.full.Unlocked(), - resolve: ({ target }) => target.from() - }) + CoinInserted: (to) => to.full.Unlocked().resolve(({ target }) => target.from()) } }, Unlocked: { on: { - GatePushed: Machine.transition({ - target: (to) => to.full.Locked(), - resolve: ({ target }) => target.from() - }) + GatePushed: (to) => to.full.Locked().resolve(({ target }) => target.from()) } } }) diff --git a/examples/playground/src/examples/worker-tabs/machine.ts b/examples/playground/src/examples/worker-tabs/machine.ts index ac8e7b7..01b0c34 100644 --- a/examples/playground/src/examples/worker-tabs/machine.ts +++ b/examples/playground/src/examples/worker-tabs/machine.ts @@ -35,44 +35,31 @@ export const SharedMachine = Machine.make({ }).handle({ Idle: { on: { - Started: Machine.transition({ - target: (to) => to.full.Active(), - resolve: ({ state, target }) => target.from({ count: state.count }) - }), - Reset: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ target }) => target.from({ count: 0 }) - }), - Synchronized: Machine.transition({ - branches: (to) => ({ active: { target: to.full.Active() }, idle: { target: to.full.Idle() } }), - resolve: ({ event, select }) => + Started: (to) => to.full.Active().resolve(({ state, target }) => target.from({ count: state.count })), + Reset: (to) => to.full.Idle().resolve(({ target }) => target.from({ count: 0 })), + Synchronized: (to) => + to.branches({ active: { target: to.full.Active() }, idle: { target: to.full.Idle() } }).resolve(( + { event, select } + ) => event.active ? select.active.from({ count: event.count }) : select.idle.from({ count: event.count }) - }) + ) } }, Active: { on: { - Incremented: Machine.transition({ - target: (to) => to.full.Active(), - resolve: ({ state, target }) => target.from({ count: state.count + 1 }) - }), - Reset: Machine.transition({ - target: (to) => to.full.Active(), - resolve: ({ target }) => target.from({ count: 0 }) - }), - Stopped: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ state, target }) => target.from({ count: state.count }) - }), - Synchronized: Machine.transition({ - branches: (to) => ({ active: { target: to.full.Active() }, idle: { target: to.full.Idle() } }), - resolve: ({ event, select }) => + Incremented: (to) => to.full.Active().resolve(({ state, target }) => target.from({ count: state.count + 1 })), + Reset: (to) => to.full.Active().resolve(({ target }) => target.from({ count: 0 })), + Stopped: (to) => to.full.Idle().resolve(({ state, target }) => target.from({ count: state.count })), + Synchronized: (to) => + to.branches({ active: { target: to.full.Active() }, idle: { target: to.full.Idle() } }).resolve(( + { event, select } + ) => event.active ? select.active.from({ count: event.count }) : select.idle.from({ count: event.count }) - }) + ) } } }) diff --git a/examples/pokemon/src/machine.ts b/examples/pokemon/src/machine.ts index cb38e39..7535ff7 100644 --- a/examples/pokemon/src/machine.ts +++ b/examples/pokemon/src/machine.ts @@ -31,45 +31,29 @@ const machine = Machine.make({ const service = yield* PokemonService return yield* service.getRandomTeam() }), - onDone: Machine.transition({ - target: (to) => to.full.ActiveTeam(), - resolve: ({ output, target }) => target.from({ team: output }) - }), - onFailure: Machine.transition({ - target: (to) => to.full.Failed(), - resolve: ({ target }) => target.from() - }) + onDone: (to) => to.full.ActiveTeam().resolve(({ output, target }) => target.from({ team: output })), + onFailure: (to) => to.full.Failed().resolve(({ target }) => target.from()) }) }, ActiveTeam: { invoke: [ Machine.invoke({ child: SelectionChild, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onFailure: Machine.transition({ - target: (to) => to.full.Failed(), - resolve: ({ target }) => target.from() - }) + onDone: { target: Machine.targetless }, + onFailure: (to) => to.full.Failed().resolve(({ target }) => target.from()) }), Machine.invoke({ child: ReplaceChild, - onFailure: Machine.transition({ - target: (to) => to.full.Failed(), - resolve: ({ target }) => target.from() - }) + onFailure: (to) => to.full.Failed().resolve(({ target }) => target.from()) }) ], on: { - ReplaceInTeam: Machine.transition({ - target: (to) => to.full.ActiveTeam(), - resolve: ({ event, target, state }) => + ReplaceInTeam: (to) => + to.full.ActiveTeam().resolve(({ event, target, state }) => target.from({ team: state.team.map((pokemon) => (pokemon.id === event.id ? event.pokemon : pokemon)) }) - }) + ) } }, Failed: {} diff --git a/examples/pokemon/src/machines/replace.ts b/examples/pokemon/src/machines/replace.ts index d13a07c..5658e12 100644 --- a/examples/pokemon/src/machines/replace.ts +++ b/examples/pokemon/src/machines/replace.ts @@ -43,38 +43,28 @@ export const ReplaceMachine = Machine.make({ }).handle({ Idle: { on: { - ReplacePokemon: Machine.transition({ - target: (to) => to.full.Replacing(), - resolve: ({ event, target }) => target.from({ id: event.id }) - }) + ReplacePokemon: (to) => to.full.Replacing().resolve(({ event, target }) => target.from({ id: event.id })) } }, Replacing: { invoke: Machine.invoke({ id: "replaceWithRandom", effect: () => replaceWithRandom, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: ({ output }, enqueue) => { + onDone: (to) => + to.none().resolve(({ output }, enqueue) => { enqueue.raise(ReplaceInternalEvents.Replaced({ pokemon: output.pokemon })) return undefined - } - }), - onFailure: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ target }) => target.from() - }) + }), + onFailure: (to) => to.full.Idle().resolve(({ target }) => target.from()) }), on: { - Replaced: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ event, parent, state, target }, enqueue) => { + Replaced: (to) => + to.full.Idle().resolve(({ event, parent, state, target }, enqueue) => { if (parent !== undefined) { enqueue.sendTo(parent, TeamEvents.ReplaceInTeam({ id: state.id, pokemon: event.pokemon })) } return target.from() - } - }) + }) } } }) diff --git a/examples/pokemon/src/machines/selection.ts b/examples/pokemon/src/machines/selection.ts index e06f85b..d520fb5 100644 --- a/examples/pokemon/src/machines/selection.ts +++ b/examples/pokemon/src/machines/selection.ts @@ -87,19 +87,17 @@ export const SelectionMachine = Machine.make({ states: { search: { on: { - UpdateSearchText: Machine.transition({ - target: (to) => to.local.with(), - resolve: ({ event, target }) => - target.from({ searchText: event.value }, (search) => search.Searching.from()), - reenter: true - }) + UpdateSearchText: (to) => + to.local.with().resolve( + ({ event, target }) => target.from({ searchText: event.value }, (search) => search.Searching.from()), + { reenter: true } + ) }, states: { WithPokemon: { on: { - ReplacePokemon: Machine.transition({ - target: (to) => to.full.form(), - resolve: ({ event, parent, state, target }, enqueue) => { + ReplacePokemon: (to) => + to.full.form().resolve(({ event, parent, state, target }, enqueue) => { if (parent !== undefined) { enqueue.sendTo(parent, TeamEvents.ReplaceInTeam({ id: event.id, pokemon: state.pokemon })) } @@ -108,38 +106,31 @@ export const SelectionMachine = Machine.make({ .search.from({ searchText: "" }, (search) => search.NoPokemon.from()) .selection.from((selection) => selection.Unselected.from()) ) - } - }) + }) } }, Searching: { invoke: Machine.invoke({ id: "search", effect: ({ ancestors }) => searchPokemon(ancestors["form.search"].searchText), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: ({ output }, enqueue) => { + onDone: (to) => + to.none().resolve(({ output }, enqueue) => { enqueue.raise(output) return undefined - } - }), - onFailure: Machine.transition({ - target: (to) => to.local.NoPokemon(), - resolve: ({ target }) => target.from() - }) + }), + onFailure: (to) => to.local.NoPokemon().resolve(({ target }) => target.from()) }), on: { - SearchResult: Machine.transition({ - branches: (to) => ({ + SearchResult: (to) => + to.branches({ found: { target: to.local.WithPokemon() }, notFound: { title: "Not found", target: to.local.NoPokemon() } - }), - resolve: ({ event, select }) => + }).resolve(({ event, select }) => Option.match(event.result, { onNone: () => select.notFound.from(), onSome: (pokemon) => select.found.from({ pokemon }) }) - }) + ) } } } @@ -148,24 +139,20 @@ export const SelectionMachine = Machine.make({ states: { Unselected: { on: { - SelectPokemon: Machine.transition({ - target: (to) => to.local.Selected(), - resolve: ({ event, target }) => target.from({ id: event.id }) - }) + SelectPokemon: (to) => to.local.Selected().resolve(({ event, target }) => target.from({ id: event.id })) } }, Selected: { on: { - SelectPokemon: Machine.transition({ - branches: (to) => ({ + SelectPokemon: (to) => + to.branches({ alreadySelected: { title: "Already selected", target: to.local.Unselected() }, selected: { target: to.local.Selected() } - }), - resolve: ({ event, select, state }) => + }).resolve(({ event, select, state }) => state.id === event.id ? select.alreadySelected.from() : select.selected.from({ id: event.id }) - }) + ) } } } diff --git a/perf/runtime/effect-machine-compatibility.mjs b/perf/runtime/effect-machine-compatibility.mjs index 7188f79..4225fb3 100644 --- a/perf/runtime/effect-machine-compatibility.mjs +++ b/perf/runtime/effect-machine-compatibility.mjs @@ -9,18 +9,34 @@ 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 targetless = ({ target }) => typeof target.none === "function" ? target.none() : undefined + const fluentTransition = (definition) => (to) => { + const selection = definition.target(to) + if (definition.resolve !== undefined) { + return selection.resolve(definition.resolve, { + ...(definition.reenter === true ? { reenter: true } : {}), + ...(definition.declinable === true ? { declinable: true } : {}) + }) + } + return definition.reenter === true ? selection.reenter() : selection + } + 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 ? definition : legacy, - transition: (definition, legacy) => hasStaticTransitions ? Machine.transition(definition) : legacy, - targetless: hasStaticTransitions - ? Machine.transition({ target: (to) => to.none(), resolve: () => undefined }) + initial: (definition, legacy) => hasStaticTransitions || hasFluentTransitions ? definition : legacy, + transition: (definition, legacy) => hasStaticTransitions + ? Machine.transition(definition) + : hasFluentTransitions + ? fluentTransition(definition) + : legacy, + targetless: hasStaticTransitions || hasFluentTransitions + ? { target: Machine.targetless } : targetless, invokeChild: typeof Machine.invokeMachine === "function" ? ({ onSnapshot, onFailure, ...config }) => { diff --git a/perf/types/adapter-readiness-control.ts b/perf/types/adapter-readiness-control.ts index 6b85dbc..aa937b9 100644 --- a/perf/types/adapter-readiness-control.ts +++ b/perf/types/adapter-readiness-control.ts @@ -43,10 +43,7 @@ export const machine = Machine.make({ states: { Idle: {}, Route: { - choice: Machine.transition({ - target: (to) => to.full.Ready(), - resolve: ({ target }) => target(Ready.make({})) - }) + choice: (to) => to.full.Ready().resolve(({ target }) => target(Ready.make({}))) } } }, diff --git a/perf/types/composition.ts b/perf/types/composition.ts index c824846..e0cb96f 100644 --- a/perf/types/composition.ts +++ b/perf/types/composition.ts @@ -67,9 +67,8 @@ const handled = machine.handle({ } }, Route: { - choice: Machine.transition({ - target: (to) => to.full.App(), - resolve: ({ target }) => + choice: (to) => + to.full.App().resolve(({ target }) => target(App.make({}), (app) => app.Workspace( Workspace.make({}), @@ -78,7 +77,7 @@ const handled = machine.handle({ .Editor(Editor.make({}), (editor) => editor.Editing(Editing.make({}))) .Sync(Sync.make({}), (sync) => sync.Idle(SyncIdle.make({}))) )) - }) + ) } } } diff --git a/perf/types/definition-variants.ts b/perf/types/definition-variants.ts index 8c38a6d..4c8b981 100644 --- a/perf/types/definition-variants.ts +++ b/perf/types/definition-variants.ts @@ -16,25 +16,16 @@ const complete = machine.handle({ }, states: { Route: { - choice: Machine.transition({ - target: (to) => to.local.Idle(), - resolve: ({ target }) => target(Idle.make({})) - }) + choice: (to) => to.local.Idle().resolve(({ target }) => target(Idle.make({}))) }, Idle: { on: { - Start: Machine.transition({ - target: (to) => to.local.Running(), - resolve: ({ target }) => target(Running.make({})) - }) + Start: (to) => to.local.Running().resolve(({ target }) => target(Running.make({}))) } }, Running: { on: { - Finish: Machine.transition({ - target: (to) => to.local.Done(), - resolve: ({ event, target }) => target(Done.make({ value: event.value })) - }) + Finish: (to) => to.local.Done().resolve(({ event, target }) => target(Done.make({ value: event.value }))) } }, Done: { @@ -49,10 +40,7 @@ const idleOnly = machine.handle({ states: { Idle: { on: { - Start: Machine.transition({ - target: (to) => to.local.Running(), - resolve: ({ target }) => target(Running.make({})) - }) + Start: (to) => to.local.Running().resolve(({ target }) => target(Running.make({}))) } } } @@ -64,10 +52,7 @@ const runningOnly = machine.handle({ states: { Running: { on: { - Finish: Machine.transition({ - target: (to) => to.local.Done(), - resolve: ({ event, target }) => target(Done.make({ value: event.value })) - }) + Finish: (to) => to.local.Done().resolve(({ event, target }) => target(Done.make({ value: event.value }))) } } } diff --git a/perf/types/dynamic-invoke.ts b/perf/types/dynamic-invoke.ts index a4ae34e..886358f 100644 --- a/perf/types/dynamic-invoke.ts +++ b/perf/types/dynamic-invoke.ts @@ -11,22 +11,18 @@ const invoked = machine.handle({ invoke: Machine.invoke({ id: "load-user", effect: ({ state }) => loadUser(state.userId), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: ({ output }) => { + onDone: (to) => + to.none().resolve(({ output }) => { const user: User = output void user return undefined - } - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: ({ error }) => { + }), + onFailure: (to) => + to.none().resolve(({ error }) => { const loadError: LoadError = error void loadError return undefined - } - }) + }) }) } }) diff --git a/perf/types/exact-channels.ts b/perf/types/exact-channels.ts index a5b47f7..d533eac 100644 --- a/perf/types/exact-channels.ts +++ b/perf/types/exact-channels.ts @@ -12,17 +12,12 @@ const complete = machine.handle({ Idle: { entry: () => {}, on: { - Start: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ event, target }, enqueue) => { + Start: (to) => + to.full.Done().resolve(({ event, target }, enqueue) => { enqueue.emit(Notice.make({ value: event.value })) return target(Done.make({ value: event.value })) - } - }), - Loaded: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ event, target }) => target(Done.make({ value: event.value })) - }) + }), + Loaded: (to) => to.full.Done().resolve(({ event, target }) => target(Done.make({ value: event.value }))) } }, Done: { diff --git a/perf/types/handle.ts b/perf/types/handle.ts index 5e6058b..ba5d4fb 100644 --- a/perf/types/handle.ts +++ b/perf/types/handle.ts @@ -24,18 +24,13 @@ const machine = Machine.make({ }).handle({ Idle: { on: { - Start: Machine.transition({ - target: (to) => to.full.Running(), - resolve: ({ target }) => target(State.cases.Running.make({})) - }) + Start: (to) => to.full.Running().resolve(({ target }) => target(State.cases.Running.make({}))) } }, Running: { on: { - Finish: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ event, target }) => target(State.cases.Done.make({ value: event.value })) - }) + Finish: (to) => + to.full.Done().resolve(({ event, target }) => target(State.cases.Done.make({ value: event.value }))) } }, Done: {} diff --git a/perf/types/named-branches.ts b/perf/types/named-branches.ts index 542304c..79bcc09 100644 --- a/perf/types/named-branches.ts +++ b/perf/types/named-branches.ts @@ -4,8 +4,8 @@ import { machine, State } from "./named-branches-control.js" const handled = machine.handle({ Idle: { on: { - Route: Machine.transition({ - branches: (to) => ({ + Route: (to) => + to.branches({ length1: { target: to.full.Text() }, length2: { target: to.full.Count() }, length3: { target: to.full.Text() }, @@ -17,8 +17,7 @@ const handled = machine.handle({ length9: { target: to.full.Count() }, length10: { target: to.full.Idle() }, unchanged: { target: to.none() } - }), - resolve: ({ event, select }) => { + }).resolve(({ event, select }) => { const value = event.value switch (value.length) { case 1: @@ -44,8 +43,7 @@ const handled = machine.handle({ default: return select.unchanged() } - } - }) + }) } }, Text: {}, diff --git a/scripts/fixtures/consumer/consumer.ts b/scripts/fixtures/consumer/consumer.ts index 04b6ed2..0c5e30d 100644 --- a/scripts/fixtures/consumer/consumer.ts +++ b/scripts/fixtures/consumer/consumer.ts @@ -34,23 +34,19 @@ const machine = Machine.make({ }).handle({ Idle: { on: { - Start: Machine.transition({ - branches: (to) => ({ + Start: (to) => + to.branches({ cached: { target: to.full.Loading() }, measured: { target: to.none() }, named: { target: to.full.Done() }, confirmed: { target: to.full.Idle() } - }), - resolve: ({ select }) => select.cached(State.cases.Loading.make({})) - }) + }).resolve(({ select }) => select.cached(State.cases.Loading.make({}))) } }, Loading: { on: { - Loaded: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ event, target }) => target(State.cases.Done.make({ value: event.value })) - }) + Loaded: (to) => + to.full.Done().resolve(({ event, target }) => target(State.cases.Done.make({ value: event.value }))) } }, Done: {} @@ -67,12 +63,12 @@ const cluster = ClusterMachine.make("ConsumerEntity", machine, { const invoked = Machine.invoke({ id: "fixture-load", effect: () => Effect.succeed("ready"), - onDone: Machine.transition({ target: (to) => to.none(), resolve: () => undefined }) + onDone: { target: Machine.targetless } }) const delayed = Machine.invoke({ id: "fixture-delay", after: "1 second", - onDone: Machine.transition({ target: (to) => to.none(), resolve: () => undefined }) + onDone: { target: Machine.targetless } }) 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 9c3c492..2950d33 100644 --- a/scripts/fixtures/consumer/deep-bound.ts +++ b/scripts/fixtures/consumer/deep-bound.ts @@ -102,22 +102,23 @@ const definition = Machine.make({ }) const machine = definition.handle({ Idle: { - invoke: definition.invoke({ + invoke: Machine.invoke({ id: "deep-inline-invoke", effect: () => Effect.asVoid(ExternalService), - onDone: Machine.transition({ target: (to) => to.none(), resolve: () => undefined }) + onDone: { target: Machine.targetless } }), on: { - Begin: Machine.transition({ - target: (to) => to.full.Ready(), - resolve: ({ target }) => + Begin: (to) => + to.full.Ready().resolve(({ target }) => target( State.cases.Ready.make({}), (ready) => - ready.Editor(State.cases.Editor.make({}), (editor) => - editor.Editing(State.cases.Editing.make({ value: "ready" }))) + ready.Editor( + State.cases.Editor.make({}), + (editor) => editor.Editing(State.cases.Editing.make({ value: "ready" })) + ) ) - }) + ) } }, Ready: { @@ -126,31 +127,27 @@ const machine = definition.handle({ states: { Editing: { on: { - Save: Machine.transition({ - target: (to) => to.local.Saving(), - resolve: ({ event, target }) => target(State.cases.Saving.make({ value: event.value })) - }), - Loaded: Machine.transition({ target: (to) => to.none(), resolve: () => undefined }) + Save: (to) => + to.local.Saving().resolve(({ event, target }) => + target(State.cases.Saving.make({ value: event.value })) + ), + Loaded: { target: Machine.targetless } } }, Saving: { - invoke: definition.invoke({ + invoke: Machine.invoke({ child: Child, input: ({ state }) => ({ value: state.value }), - onDone: Machine.transition({ target: (to) => to.none(), resolve: () => undefined }) + onDone: { target: Machine.targetless } }), on: { - ChildNotice: Machine.transition({ - target: (to) => to.local.Saving(), - resolve: ({ event, target }, enqueue) => { + ChildNotice: (to) => + to.local.Saving().resolve(({ event, target }, enqueue) => { enqueue.emit(Emissions.Notice({ value: event.value })) return target(State.cases.Saving.make({ value: event.value })) - } - }), - ChildCompleted: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ event, target }) => target(State.cases.Done.make({ value: event.value })) - }) + }), + ChildCompleted: (to) => + to.full.Done().resolve(({ event, target }) => target(State.cases.Done.make({ value: event.value }))) } } } diff --git a/scripts/invoke-autocomplete.test.mjs b/scripts/invoke-autocomplete.test.mjs index 8c36970..939d8bd 100644 --- a/scripts/invoke-autocomplete.test.mjs +++ b/scripts/invoke-autocomplete.test.mjs @@ -21,15 +21,14 @@ definition.handle({ Loading: { invoke: Machine.invoke({ id: "load", - effect: () => Effect.fail("offline").pipe(Effect.as(1)), - onDone: Machine.transition({ - target: (to) => to.full./*done-target*/Done(), - resolve: ({ /*done-context*/ ...context }) => context.target.from() - }), - onFailure: Machine.transition({ - target: (to) => to.full.Failed(), - resolve: ({ /*failure-context*/ ...context }) => context.target.from() - }) + effect: ({ /*invoke-source-context*/ ...context }) => + Effect.fail("offline").pipe(Effect.as(context.state._tag)), + onDone: (to) => + to.full./*done-target*/Done()./*selected-operations*/resolve(({ /*done-context*/ ...context }) => + context.target./*done-exact-target*/from()), + onFailure: (to) => + to.full.Failed().resolve(({ /*failure-context*/ ...context }) => + context.target.from()) }) }, Done: {}, @@ -41,11 +40,9 @@ definition.handle({ invoke: Machine.invoke({ id: "updates", stream: () => Stream.make(1), - onElement: { - target: Machine.targetless, - resolve: ({ /*element-context*/ ...context }) => undefined - }, - onDone: { target: Machine.targetless } + onElement: (to) => + to.none().resolve(({ /*element-context*/ ...context }) => undefined), + onDone: (to) => to.none() }) }, Done: {}, @@ -66,42 +63,52 @@ definition.handle({ definition.handle({ Loading: { - always: Machine.transition({ - /*transition-properties*/ - }) + always: (to) => to./*transition-selector*/none() } }) definition.handle({ Loading: { - always: { - target: Machine.targetless, - resolve: ({ /*targetless-context*/ }) => undefined - } + always: (to) => + to.none().resolve(({ /*targetless-context*/ ...context }) => undefined) } }) definition.handle({ Loading: { - always: Machine.transition({ - target: (to) => to./*target-scopes*/full.Done(), - resolve: ({ /*transition-context*/ ...context }) => context.target.from() - }) + always: (to) => + to./*target-scopes*/full.Done().resolve(({ /*transition-context*/ ...context }) => + context.target./*transition-exact-target*/from()) } }) definition.handle({ Loading: { - always: Machine.transition({ - branches: (to) => ({ + always: (to) => + to.branches({ ready: { title: "ready", target: to./*branch-target-scopes*/full.Done() }, unchanged: { target: to.none() } - }), - resolve: ({ /*branch-resolve-context*/ ...context }) => context.select.ready.from() - }) + }).resolve(({ /*branch-resolve-context*/ ...context }) => + context.select./*branch-select-keys*/ready.from()) + } +}) + +definition.handle({ + Loading: { + always: (to) => + to.none().resolve(({ /*required-context*/ ...context }) => undefined) + } +}) + +definition.handle({ + Loading: { + always: (to) => + to.none().resolve(({ /*declinable-context*/ ...context }) => context.decline(), { + declinable: true + }) } }) @@ -140,6 +147,14 @@ const completions = (marker) => { } test("contextually completes Effect invocation factories while authoring", () => { + const sourceContext = completions("invoke-source-context") + assert.equal(sourceContext.has("state"), true) + assert.equal(sourceContext.has("ancestors"), true) + assert.equal(sourceContext.has("event"), true) + assert.equal(sourceContext.has("snapshot"), false) + assert.equal(sourceContext.has("self"), true) + assert.equal(sourceContext.has("parent"), true) + const done = completions("done-context") assert.equal(done.has("output"), true) assert.equal(done.has("state"), true) @@ -149,6 +164,11 @@ test("contextually completes Effect invocation factories while authoring", () => assert.equal(doneTarget.has("Done"), true) assert.equal(doneTarget.has("Failed"), true) + const exactTarget = completions("done-exact-target") + assert.equal(exactTarget.has("from"), true) + assert.equal(exactTarget.has("full"), false) + assert.equal(exactTarget.has("Done"), false) + const failure = completions("failure-context") assert.equal(failure.has("error"), true) assert.equal(failure.has("state"), true) @@ -167,11 +187,10 @@ test("contextually completes Stream element handlers while authoring", () => { }) test("contextually completes transition definitions while authoring", () => { - const properties = completions("transition-properties") - assert.equal(properties.has("target"), true) - assert.equal(properties.has("resolve"), true) - assert.equal(properties.has("branches"), true) - assert.equal(properties.has("reenter"), true) + const selector = completions("transition-selector") + assert.equal(selector.has("none"), true) + assert.equal(selector.has("branches"), true) + assert.equal(selector.has("full"), true) const scopes = completions("target-scopes") assert.equal(scopes.has("none"), true) @@ -186,6 +205,11 @@ test("contextually completes transition definitions while authoring", () => { assert.equal(context.has("snapshot"), true) assert.equal(context.has("target"), true) + const exactTarget = completions("transition-exact-target") + assert.equal(exactTarget.has("from"), true) + assert.equal(exactTarget.has("full"), false) + assert.equal(exactTarget.has("Done"), false) + const targetless = completions("targetless-context") assert.equal(targetless.has("state"), true) assert.equal(targetless.has("target"), false) @@ -202,4 +226,19 @@ test("contextually completes transition definitions while authoring", () => { assert.equal(resolve.has("select"), true) assert.equal(resolve.has("target"), false) + const select = completions("branch-select-keys") + assert.equal(select.has("ready"), true) + assert.equal(select.has("unchanged"), true) + assert.equal(select.has("Done"), false) + + const required = completions("required-context") + assert.equal(required.has("decline"), false) + + const declinable = completions("declinable-context") + assert.equal(declinable.has("decline"), true) + + const selected = completions("selected-operations") + assert.equal(selected.has("resolve"), true) + assert.equal(selected.has("reenter"), true) + }) diff --git a/scripts/runtime-performance-compatibility.test.mjs b/scripts/runtime-performance-compatibility.test.mjs index 840bdce..15fed92 100644 --- a/scripts/runtime-performance-compatibility.test.mjs +++ b/scripts/runtime-performance-compatibility.test.mjs @@ -11,10 +11,12 @@ test("adapts the state-definition constructor across the public rename", () => { assert.deepEqual(legacy.states(definitions), { api: "legacy", states: definitions }) }) -test("adapts static transition definitions only when the new capability is present", () => { +test("adapts wrapped static transition definitions when that capability is present", () => { const calls = [] + const targetless = (to) => to.none() const Machine = { logic: () => undefined, + targetless, transition: (definition) => { calls.push(definition) return { static: definition } @@ -28,11 +30,35 @@ test("adapts static transition definitions only when the new capability is prese assert.equal(api.initial(definition, legacy), definition) assert.deepEqual(api.transition(definition, legacy), { static: definition }) - assert.equal(typeof api.targetless.static.target, "function") - assert.equal(typeof api.targetless.static.resolve, "function") - assert.equal(api.targetless.static.target({ none: () => "none" }), "none") - assert.equal(api.targetless.static.resolve(), undefined) - assert.equal(calls.length, 2) + assert.equal(api.targetless.target, targetless) + assert.equal(calls.length, 1) +}) + +test("adapts benchmark definitions to fluent transition selectors", () => { + const calls = [] + const targetless = Object.assign((to) => to.none(), { + "~effect/Machine/TargetlessSelector": true + }) + const Machine = { targetless } + const api = makeEffectMachineBenchmarkApi(Machine) + const resolve = () => undefined + const selected = { + resolve: (...args) => { + calls.push(args) + return "resolved" + } + } + const transition = api.transition({ + target: (to) => to.selected, + resolve, + reenter: true, + declinable: true + }, "legacy") + + 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) }) test("uses the current child invocation capability when available", () => { diff --git a/scripts/type-performance.mjs b/scripts/type-performance.mjs index 8552405..36fb124 100644 --- a/scripts/type-performance.mjs +++ b/scripts/type-performance.mjs @@ -84,7 +84,7 @@ const scenarios = [ }, { id: "named-branches", - label: "Machine.transition (10 named branches)", + label: "fluent transition (10 named branches)", file: "named-branches.ts", control: "named-branches-control", maxInstantiations: 160_000, @@ -101,8 +101,8 @@ const scenarios = [ label: "Machine.invoke (state-dependent Effect)", file: "dynamic-invoke.ts", control: "dynamic-invoke-control", - maxInstantiations: 85_000, - maxMarginalInstantiations: 76_000 + maxInstantiations: 122_000, + maxMarginalInstantiations: 110_000 }, { id: "handle-depth-24-control", diff --git a/src/Machine.ts b/src/Machine.ts index 282e1ab..1603a36 100644 --- a/src/Machine.ts +++ b/src/Machine.ts @@ -220,15 +220,6 @@ export interface Machine< /** @internal */ readonly handlers: Machine.StateConfigs, E, R> - /** - * Machine-bound equivalent of {@link invoke}. Both forms preserve this - * machine's public input and parent protocols in invocation callbacks; - * `Machine.invoke(...)` inside `handle(...)` is the canonical inline form. - * - * @since 0.11.0 - */ - readonly invoke: Invoker - /** @internal */ readonly initial: (...args: [...Machine.InputArgs]) => Machine.InitialResult /** @internal */ @@ -600,6 +591,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 type StateDefinitionError< Message extends string, @@ -2495,8 +2487,6 @@ export declare namespace Machine { /** @internal */ readonly handlers: any /** @internal */ - readonly invoke: any - /** @internal */ readonly initial: any /** @internal */ readonly initialDefinition: InitialDefinition @@ -4966,8 +4956,10 @@ export declare namespace Machine { * @category utility types * @since 0.4.0 */ - export type EventTransitionReturn = Transition extends { readonly resolve?: infer Resolve } ? - NonNullable extends (...args: any) => infer Ret ? Ret : never + export type EventTransitionReturn = Transition extends (...args: any) => infer Authored ? + Authored extends TransitionBuilderEvidence ? Result : never + : Transition extends { readonly resolve?: infer Resolve } ? + NonNullable extends (...args: any) => infer Ret ? Ret : never : never /** * Extracts the return value from a state's event handlers. @@ -5184,7 +5176,7 @@ export declare namespace Machine { | Effect.Services> | InvokeRequirements - /** Type evidence retained by {@link transition} without affecting runtime data. */ + /** Type evidence retained by an authored transition without affecting runtime data. */ export interface TransitionTyped< States extends StateSchemas, Events extends ReadonlyArray, @@ -5218,14 +5210,8 @@ export declare namespace Machine { Reenter extends boolean = false, Acceptance extends TransitionAcceptance = "required" > = - & ( - | TransitionTyped - | TargetlessTransitionTyped - | TargetlessTransitionInput - ) - & { - readonly reenter?: [Reenter] extends [true] ? boolean : never - } + | TargetlessTransitionInput + | TransitionBuilderInput /** Direct shorthand for a non-reentering targetless transition. */ // Keep the context generic so omitting `target` is deferred until a resolver @@ -5263,6 +5249,13 @@ export declare namespace Machine { TargetBuilderResult : never + type SelectionSupportsDefaultConstruction = SelectionKind extends "none" ? true + : SelectionBuilder extends { readonly from: (...args: infer Args) => any } ? [] extends Args ? true + : false + : SelectionBuilder extends (...args: infer Args) => any ? [] extends Args ? true + : false + : false + export type TransitionResolveContext< Context, Selection @@ -5285,22 +5278,6 @@ export declare namespace Machine { enqueue: Enqueue, EmitOf> ) => SelectionKind extends "none" ? undefined : SelectedTargetResult | undefined - export type TransitionDirectInput< - States extends StateSchemas, - Events extends ReadonlyArray, - Emits extends ReadonlyArray, - StateId extends StateNodeIdentifier, - Context, - Reenter extends boolean, - Selection extends TargetSelection - > = { - readonly target: (to: TargetSelector) => Selection - readonly resolve?: TransitionResolver - readonly branches?: never - readonly reenter?: Reenter - readonly declinable?: false - } - export type DeclinableTransitionResolver< Events extends ReadonlyArray, Emits extends ReadonlyArray, @@ -5313,22 +5290,6 @@ export declare namespace Machine { | (SelectionKind extends "none" ? undefined : SelectedTargetResult | undefined) | Declined - export type DeclinableTransitionDirectInput< - States extends StateSchemas, - Events extends ReadonlyArray, - Emits extends ReadonlyArray, - StateId extends StateNodeIdentifier, - Context, - Reenter extends boolean, - Selection extends TargetSelection - > = { - readonly target: (to: TargetSelector) => Selection - readonly resolve: DeclinableTransitionResolver - readonly branches?: never - readonly reenter?: Reenter - readonly declinable: true - } - /** One named destination declared by a branching transition. */ export interface TransitionBranchInput< Selection extends TargetSelection = TargetSelection @@ -5395,40 +5356,221 @@ export declare namespace Machine { enqueue: Enqueue, EmitOf> ) => BranchSelectionResult - export type TransitionBranchesInput< + export type DeclinableTransitionBranchesResolver< + Events extends ReadonlyArray, + Emits extends ReadonlyArray, + Context, + Branches extends Readonly> + > = ( + context: TransitionBranchesResolveContext & DeclineCapability, + enqueue: Enqueue, EmitOf> + ) => BranchSelectionResult | Declined + + /** @internal Type evidence retained by a transition authored through its bound selector. */ + export interface TransitionBuilderEvidence { + readonly [TransitionBuilderTypeId]: { + readonly result: Types.Covariant + readonly acceptance: Types.Covariant + } + } + + type TransitionReenterOption = [Reenter] extends [true] ? { readonly reenter?: boolean } + : { readonly reenter?: never } + + type TransitionRequiredOptions = + & TransitionReenterOption + & { readonly declinable?: false } + + type TransitionDeclinableOptions = + & TransitionReenterOption + & { readonly declinable: true } + + type BuiltTransition< States extends StateSchemas, Events extends ReadonlyArray, Emits extends ReadonlyArray, StateId extends StateNodeIdentifier, Context, Reenter extends boolean, - Branches extends Readonly> - > = { - readonly target?: never - /** - * Declares every possible destination under a stable semantic key. - * - * Branches are captured once in ECMAScript property order. Array-index - * keys are rejected so reordering ordinary named properties affects only - * presentation order; the key remains the testing and inspection identity. - */ - readonly branches: (to: TargetSelector) => Branches - readonly resolve: TransitionBranchesResolver - readonly reenter?: Reenter - readonly declinable?: false + Result, + Acceptance extends TransitionAcceptance + > = + & ([Exclude] extends [never] ? TargetlessTransitionTyped + : TransitionTyped) + & TransitionBuilderEvidence + + interface TransitionResolveRequired< + States extends StateSchemas, + Events extends ReadonlyArray, + Emits extends ReadonlyArray, + StateId extends StateNodeIdentifier, + Context, + Reenter extends boolean, + Selection extends TargetSelection + > { + ( + resolve: TransitionResolver, + options?: TransitionRequiredOptions + ): BuiltTransition< + States, + Events, + Emits, + StateId, + Context, + Reenter, + SelectionKind extends "none" ? undefined : SelectedTargetResult | undefined, + "required" + > } - export type DeclinableTransitionBranchesResolver< + interface TransitionResolveDeclinable< + States extends StateSchemas, + Events extends ReadonlyArray, + Emits extends ReadonlyArray, + StateId extends StateNodeIdentifier, + Context, + Reenter extends boolean, + Selection extends TargetSelection + > { + ( + resolve: DeclinableTransitionResolver, + options: TransitionDeclinableOptions + ): BuiltTransition< + States, + Events, + Emits, + StateId, + Context, + Reenter, + | (SelectionKind extends "none" ? undefined : SelectedTargetResult | undefined) + | Declined, + "declinable" + > + } + + /** A selected transition target with target-specific resolver operations. */ + export type TransitionTarget< + States extends StateSchemas, + Events extends ReadonlyArray, + Emits extends ReadonlyArray, + StateId extends StateNodeIdentifier, + Context, + Reenter extends boolean, + Acceptance extends TransitionAcceptance, + Selection extends TargetSelection + > = + & Selection + & (SelectionSupportsDefaultConstruction extends true ? BuiltTransition< + States, + Events, + Emits, + StateId, + Context, + Reenter, + SelectionKind extends "none" ? undefined : SelectedTargetResult | undefined, + "required" + > + : {}) + & { + readonly resolve: + & TransitionResolveRequired + & ("declinable" extends Acceptance ? TransitionResolveDeclinable< + States, + Events, + Emits, + StateId, + Context, + Reenter, + Selection + > + : {}) + } + & ([Reenter] extends [true] ? SelectionSupportsDefaultConstruction extends true ? { + readonly reenter: () => BuiltTransition< + States, + Events, + Emits, + StateId, + Context, + Reenter, + SelectionKind extends "none" ? undefined : SelectedTargetResult | undefined, + "required" + > + } + : {} + : {}) + + type TransitionSelectorNode< + States extends StateSchemas, Events extends ReadonlyArray, Emits extends ReadonlyArray, + StateId extends StateNodeIdentifier, + Context, + Reenter extends boolean, + Acceptance extends TransitionAcceptance, + Node + > = Node extends (...args: infer Args) => infer Selection ? Selection extends TargetSelection ? + & ((...args: Args) => TransitionTarget< + States, + Events, + Emits, + StateId, + Context, + Reenter, + Acceptance, + Selection + >) + & { + readonly [Key in keyof Node]: TransitionSelectorNode< + States, + Events, + Emits, + StateId, + Context, + Reenter, + Acceptance, + Node[Key] + > + } + : never + : { + readonly [Key in keyof Node]: TransitionSelectorNode< + States, + Events, + Emits, + StateId, + Context, + Reenter, + Acceptance, + Node[Key] + > + } + + interface TransitionBranchesResolveRequired< + States extends StateSchemas, + Events extends ReadonlyArray, + Emits extends ReadonlyArray, + StateId extends StateNodeIdentifier, Context, + Reenter extends boolean, Branches extends Readonly> - > = ( - context: TransitionBranchesResolveContext & DeclineCapability, - enqueue: Enqueue, EmitOf> - ) => BranchSelectionResult | Declined + > { + ( + resolve: TransitionBranchesResolver, + options?: TransitionRequiredOptions + ): BuiltTransition< + States, + Events, + Emits, + StateId, + Context, + Reenter, + BranchSelectionResult, + "required" + > + } - export type DeclinableTransitionBranchesInput< + interface TransitionBranchesResolveDeclinable< States extends StateSchemas, Events extends ReadonlyArray, Emits extends ReadonlyArray, @@ -5436,14 +5578,79 @@ export declare namespace Machine { Context, Reenter extends boolean, Branches extends Readonly> - > = { - readonly target?: never - readonly branches: (to: TargetSelector) => Branches - readonly resolve: DeclinableTransitionBranchesResolver - readonly reenter?: Reenter - readonly declinable: true + > { + ( + resolve: DeclinableTransitionBranchesResolver, + options: TransitionDeclinableOptions + ): BuiltTransition< + States, + Events, + Emits, + StateId, + Context, + Reenter, + BranchSelectionResult | Declined, + "declinable" + > } + /** Selector supplied to inline transition declarations. */ + export type TransitionSelector< + States extends StateSchemas, + Events extends ReadonlyArray, + Emits extends ReadonlyArray, + StateId extends StateNodeIdentifier, + Context, + Reenter extends boolean, + Acceptance extends TransitionAcceptance + > = + & TransitionSelectorNode< + States, + Events, + Emits, + StateId, + Context, + Reenter, + Acceptance, + TargetSelector + > + & { + readonly branches: >>( + branches: Branches & ValidateTransitionBranchRecord> + ) => { + readonly resolve: + & TransitionBranchesResolveRequired + & ("declinable" extends Acceptance ? TransitionBranchesResolveDeclinable< + States, + Events, + Emits, + StateId, + Context, + Reenter, + Branches + > + : {}) + } + } + + /** Inline transition declaration accepted by state and invocation handlers. */ + export type TransitionBuilderInput< + States extends StateSchemas, + Events extends ReadonlyArray, + Emits extends ReadonlyArray, + StateId extends StateNodeIdentifier, + Context, + Reenter extends boolean, + Acceptance extends TransitionAcceptance + > = ( + to: TransitionSelector + ) => + & ( + | TransitionTyped + | TargetlessTransitionTyped + ) + & TransitionBuilderEvidence + export type InvokeTransition< States extends StateSchemas, Events extends ReadonlyArray, @@ -5459,9 +5666,13 @@ export declare namespace Machine { States extends StateSchemas, Events extends ReadonlyArray, Emits extends ReadonlyArray, - StateId extends StateIdentifier + StateId extends StateIdentifier, + InputEvents extends ReadonlyArray = Events, + ParentEvents extends ReadonlyArray = readonly [] > { - readonly "~effect/Machine/InvokeOwner"?: Types.Covariant + readonly "~effect/Machine/InvokeOwner"?: Types.Covariant< + readonly [States, Events, Emits, StateId, InputEvents, ParentEvents] + > } /** Type evidence retained by {@link invoke} without affecting runtime data. */ @@ -5484,7 +5695,7 @@ export declare namespace Machine { InputEvents extends ReadonlyArray = Events, ParentEvents extends ReadonlyArray = readonly [] > = - & InvokeOwned + & InvokeOwned & ( | { readonly id: string @@ -5811,6 +6022,7 @@ export declare namespace Machine { ParentEvents extends ReadonlyArray, Raw > = Raw extends InvokeTyped ? unknown + : [Extract] extends [never] ? unknown : Raw extends { readonly effect: infer Source } ? InvokeFactoryResult extends infer Fx extends Effect.Effect ? & InvokeDoneRequirement< @@ -7472,11 +7684,9 @@ interface Make { * }).handle({ * Count: { * on: { - * Increment: Machine.transition({ - * target: (to) => to.full.Count(), - * resolve: ({ event, state, target }) => - * target(new Count({ value: state.value + event.by })) - * }) + * Increment: (to) => + * to.full.Count().resolve(({ event, state, target }) => + * target(new Count({ value: state.value + event.by }))) * } * } * }) @@ -7850,7 +8060,7 @@ type EffectInvokeResult< StateId extends Machine.StateIdentifier, Source extends (...args: ReadonlyArray) => Effect.Effect > = - & Machine.InvokeConfig + & Machine.InvokeOwned & Machine.InvokeTyped< Effect.Success>, Effect.Error>, @@ -7954,7 +8164,7 @@ type StreamInvokeResult< StateId extends Machine.StateIdentifier, Source extends (...args: ReadonlyArray) => unknown > = - & Machine.InvokeConfig + & Machine.InvokeOwned & Machine.InvokeTyped< void, Stream.Error>, @@ -7964,131 +8174,9 @@ type StreamInvokeResult< type InvokeChannelIsNever = IsAny extends true ? false : [Value] extends [never] ? true : false -type BoundEffectInvokeSource< - States extends Machine.StateSchemas, - Events extends ReadonlyArray, - Emits extends ReadonlyArray, - InputEvents extends ReadonlyArray, - ParentEvents extends ReadonlyArray, - StateId extends Machine.StateIdentifier, - Source extends ( - context: Machine.InvokeContext - ) => Effect.Effect -> = { - readonly id: InvokeLifecycleId - readonly effect: Source - readonly stream?: never - readonly after?: never - readonly logic?: never - readonly child?: never - readonly address?: never - readonly onElement?: never - readonly onSnapshot?: never -} - -type BoundEffectDoneHandler< - States extends Machine.StateSchemas, - Events extends ReadonlyArray, - Emits extends ReadonlyArray, - InputEvents extends ReadonlyArray, - ParentEvents extends ReadonlyArray, - StateId extends Machine.StateIdentifier, - Source extends (...args: ReadonlyArray) => Effect.Effect -> = Machine.InvokeTransition< - States, - Events, - Emits, - StateId, - Machine.InvokeDoneContext< - States, - Events, - Emits, - StateId, - Effect.Success>>, - InputEvents, - ParentEvents - > -> - -type BoundEffectFailureHandler< - States extends Machine.StateSchemas, - Events extends ReadonlyArray, - Emits extends ReadonlyArray, - InputEvents extends ReadonlyArray, - ParentEvents extends ReadonlyArray, - StateId extends Machine.StateIdentifier, - Source extends (...args: ReadonlyArray) => Effect.Effect -> = Machine.InvokeTransition< - States, - Events, - Emits, - StateId, - Machine.InvokeFailureContext< - States, - Events, - Emits, - StateId, - Effect.Error>>, - InputEvents, - ParentEvents - > -> - -type BoundEffectInvokeResult< - States extends Machine.StateSchemas, - Events extends ReadonlyArray, - Emits extends ReadonlyArray, - InputEvents extends ReadonlyArray, - ParentEvents extends ReadonlyArray, - StateId extends Machine.StateIdentifier, - Source extends (...args: ReadonlyArray) => Effect.Effect -> = - & Machine.InvokeConfig - & Machine.InvokeTyped< - Effect.Success>, - Effect.Error>, - Effect.Services>, - never - > - -/** Captures one exact named-branch topology while preserving handler inference. */ -type BranchingTransitionResult< - States extends Machine.StateSchemas, - Events extends ReadonlyArray, - Emits extends ReadonlyArray, - StateId extends Machine.StateNodeIdentifier, - Context, - Reenter extends boolean, - Branches extends Readonly>, - Acceptance extends Machine.TransitionAcceptance = "required" -> = - & (Acceptance extends "declinable" ? Machine.DeclinableTransitionBranchesInput< - States, - Events, - Emits, - StateId, - Context, - Reenter, - Branches - > - : Machine.TransitionBranchesInput) - & Machine.TransitionTyped - -type DirectTransitionEvidence< - States extends Machine.StateSchemas, - Events extends ReadonlyArray, - Emits extends ReadonlyArray, - StateId extends Machine.StateNodeIdentifier, - Context, - Reenter extends boolean, - Selection, - Acceptance extends Machine.TransitionAcceptance -> = Machine.SelectionKind extends "none" ? Machine.TargetlessTransitionTyped - : Machine.TransitionTyped - -type TransitionBranchRecordError = { - readonly "~effect/Machine/TransitionBranchRecordError": Message - readonly key: Key +type TransitionBranchRecordError = { + readonly "~effect/Machine/TransitionBranchRecordError": Message + readonly key: Key } type InvalidStaticTransitionBranchKey = Extract @@ -8101,139 +8189,6 @@ type ValidateTransitionBranchRecord = [keyof Branches] extends [never] InvalidStaticTransitionBranchKey > -/** - * Contextually types direct and branching transition definitions. - * - * @category constructors - * @since 0.14.0 - */ -export interface TransitionConstructor { - < - const States extends Machine.StateSchemas, - const Events extends ReadonlyArray, - const Emits extends ReadonlyArray, - StateId extends Machine.StateNodeIdentifier, - Context, - const Selection extends Machine.TargetSelection, - Reenter extends boolean = never - >( - config: Machine.DeclinableTransitionDirectInput - ): - & Machine.DeclinableTransitionDirectInput - & DirectTransitionEvidence - < - const States extends Machine.StateSchemas, - const Events extends ReadonlyArray, - const Emits extends ReadonlyArray, - StateId extends Machine.StateNodeIdentifier, - Context, - const Selection extends Machine.TargetSelection, - Reenter extends boolean = never - >( - config: Machine.TransitionDirectInput - ): - & Machine.TransitionDirectInput - & DirectTransitionEvidence - < - const States extends Machine.StateSchemas, - const Events extends ReadonlyArray, - const Emits extends ReadonlyArray, - StateId extends Machine.StateNodeIdentifier, - Context, - const Branches extends Readonly>, - Reenter extends boolean = never - >( - config: - & Machine.DeclinableTransitionBranchesInput< - States, - Events, - Emits, - StateId, - Context, - Reenter, - Branches - > - & ValidateTransitionBranchRecord> - ): BranchingTransitionResult< - States, - Events, - Emits, - StateId, - Context, - Reenter, - Branches, - "declinable" - > - < - const States extends Machine.StateSchemas, - const Events extends ReadonlyArray, - const Emits extends ReadonlyArray, - StateId extends Machine.StateNodeIdentifier, - Context, - const Branches extends Readonly>, - Reenter extends boolean = never - >( - config: - & Machine.TransitionBranchesInput< - States, - Events, - Emits, - StateId, - Context, - Reenter, - Branches - > - & ValidateTransitionBranchRecord> - ): BranchingTransitionResult< - States, - Events, - Emits, - StateId, - Context, - Reenter, - Branches - > -} - -/** - * Defines a statically inspectable transition. - * - * It is required for every event, lifecycle, choice, and invocation transition - * so the destination selected by `target` can contextually type the - * corresponding resolver. Branching transitions declare every possible - * destination up front, then select one through ordinary TypeScript control - * flow in `resolve`. - * - * ```ts - * Machine.transition({ - * target: (to) => to.full.Ready(), - * resolve: ({ target }) => target.from() - * }) - * - * Machine.transition({ - * branches: (to) => ({ - * cached: { target: to.full.Ready() }, - * loading: { target: to.full.Loading() } - * }), - * resolve: ({ event, select }) => event.cached - * ? select.cached.from({ data: event.cached }) - * : select.loading.from() - * }) - * - * Machine.transition({ - * declinable: true, - * target: (to) => to.full.Ready(), - * resolve: ({ event, target, decline }) => event.accepted - * ? target.from() - * : decline() - * }) - * ``` - * - * @category constructors - * @since 0.14.0 - */ -export const transition: TransitionConstructor = ((config: unknown) => config) as TransitionConstructor - /** * Sentinel used by direct targetless transition shorthands. * @@ -8275,289 +8230,6 @@ export const targetless: TargetlessSelector = Object.assign( { "~effect/Machine/TargetlessSelector": true as const } ) -/** - * Machine-bound invocation constructor that preserves the owning machine's - * public input and parent protocols in every invocation callback. - * - * @category constructors - * @since 0.11.0 - */ -export interface Invoker< - States extends Machine.StateSchemas, - Events extends ReadonlyArray, - Emits extends ReadonlyArray, - InputEvents extends ReadonlyArray, - ParentEvents extends ReadonlyArray -> { - < - StateId extends Machine.StateIdentifier, - const Source extends ( - context: Machine.InvokeContext - ) => Effect.Effect - >( - config: - & BoundEffectInvokeSource - & { - readonly onDone: BoundEffectDoneHandler< - States, - Events, - Emits, - InputEvents, - ParentEvents, - StateId, - Source - > - readonly onFailure: BoundEffectFailureHandler< - States, - Events, - Emits, - InputEvents, - ParentEvents, - StateId, - Source - > - }, - ..._validation: InvokeChannelIsNever>> extends true ? [ - "onDone must be omitted when the Effect output is never" - ] - : InvokeChannelIsNever>> extends true ? [ - "onFailure must be omitted when the Effect error is never" - ] - : [] - ): BoundEffectInvokeResult - < - StateId extends Machine.StateIdentifier, - const Source extends ( - context: Machine.InvokeContext - ) => Effect.Effect - >( - config: - & BoundEffectInvokeSource - & { - readonly onDone: BoundEffectDoneHandler< - States, - Events, - Emits, - InputEvents, - ParentEvents, - StateId, - Source - > - readonly onFailure?: never - }, - ..._validation: InvokeChannelIsNever>> extends true ? [ - "onDone must be omitted when the Effect output is never" - ] - : [] - ): BoundEffectInvokeResult - < - StateId extends Machine.StateIdentifier, - const Source extends ( - context: Machine.InvokeContext - ) => Effect.Effect - >( - config: - & BoundEffectInvokeSource - & { - readonly onDone?: never - readonly onFailure: BoundEffectFailureHandler< - States, - Events, - Emits, - InputEvents, - ParentEvents, - StateId, - Source - > - }, - ..._validation: InvokeChannelIsNever>> extends true ? [ - "onFailure must be omitted when the Effect error is never" - ] - : [] - ): BoundEffectInvokeResult - < - StateId extends Machine.StateIdentifier, - const Source extends ( - context: Machine.InvokeContext - ) => Effect.Effect - >( - config: - & BoundEffectInvokeSource - & { - readonly onDone?: never - readonly onFailure?: never - } - ): BoundEffectInvokeResult - < - StateId extends Machine.StateIdentifier, - const Source extends ( - context: Machine.InvokeContext - ) => Stream.Stream - >( - config: - & StreamInvokeSource - & { - readonly onElement: StreamElementHandler - readonly onDone: StreamDoneHandler - readonly onFailure: StreamFailureHandler - }, - ..._validation: InvokeChannelIsNever>> extends true ? [ - "onElement must be omitted when the Stream element is never" - ] - : InvokeChannelIsNever>> extends true ? [ - "onFailure must be omitted when the Stream error is never" - ] - : [] - ): StreamInvokeResult - < - StateId extends Machine.StateIdentifier, - const Source extends ( - context: Machine.InvokeContext - ) => Stream.Stream - >( - config: - & StreamInvokeSource - & { - readonly onElement?: never - readonly onDone: StreamDoneHandler - readonly onFailure: StreamFailureHandler - }, - ..._validation: InvokeChannelIsNever>> extends true ? [ - "onFailure must be omitted when the Stream error is never" - ] - : [] - ): StreamInvokeResult - < - StateId extends Machine.StateIdentifier, - const Source extends ( - context: Machine.InvokeContext - ) => Stream.Stream - >( - config: - & StreamInvokeSource - & { - readonly onElement?: never - readonly onDone: StreamDoneHandler - readonly onFailure?: never - } - ): StreamInvokeResult - < - StateId extends Machine.StateIdentifier, - const Source extends ( - context: Machine.InvokeContext - ) => Stream.Stream - >( - config: - & StreamInvokeSource - & { - readonly onElement: StreamElementHandler - readonly onDone: StreamDoneHandler - readonly onFailure?: never - } - ): StreamInvokeResult - , const Config extends object>( - config: Config & Machine.TimerInvokeArgs - ): Config & Machine.InvokeOwned & Machine.InvokeTyped - < - StateId extends Machine.StateIdentifier, - const Source extends ( - context: Machine.InvokeContext - ) => unknown, - Address extends ChildAddress - >( - config: - & { readonly logic: Source } - & Machine.LogicInvokeArgs< - States, - Events, - Emits, - StateId, - Machine.LogicStateOf>, - Machine.LogicEventOf>, - Machine.LogicErrorOf>, - Machine.LogicServicesOf>, - Machine.LogicOutputOf>, - Machine.LogicInitialErrorOf>, - Address, - Source, - InputEvents, - ParentEvents - >, - ..._validation: ReturnType extends { readonly initial: unknown; readonly run: unknown } ? [] : [ - "logic factory must return Machine.Logic" - ] - ): - & Machine.InvokeConfig - & Machine.InvokeTyped< - Machine.LogicOutputOf>, - Machine.LogicErrorOf>, - Machine.LogicServicesOf>, - Machine.LogicInitialErrorOf> - > - < - StateId extends Machine.StateIdentifier, - const Source, - Address extends ChildAddress - >( - config: - & { readonly logic: Source } - & Machine.LogicInvokeArgs< - States, - Events, - Emits, - StateId, - Machine.LogicStateOf, - Machine.LogicEventOf, - Machine.LogicErrorOf, - Machine.LogicServicesOf, - Machine.LogicOutputOf, - Machine.LogicInitialErrorOf, - Address, - Source, - InputEvents, - ParentEvents - >, - ..._validation: Source extends { readonly initial: unknown; readonly run: unknown } ? [] : [ - "logic must implement Machine.Logic" - ] - ): - & Machine.InvokeConfig - & Machine.InvokeTyped< - Machine.LogicOutputOf, - Machine.LogicErrorOf, - Machine.LogicServicesOf, - Machine.LogicInitialErrorOf - > - < - StateId extends Machine.StateIdentifier, - const Child extends ChildMachine.Any, - const Config extends object - >( - config: - & Config - & Machine.ChildInvokeArgs< - States, - Events, - Emits, - StateId, - Child["machine"], - Child, - InputEvents, - ParentEvents - > - ): - & Config - & Machine.InvokeOwned - & Machine.InvokeTyped< - Machine.Output, - Machine.Error | ActionError>, - Machine.Services, - Machine.InitialError, - Machine.Emit, - Machine.EventOf> - > -} - /** * Preserves inference for a state-owned invocation configuration. * @@ -8581,7 +8253,8 @@ export interface Invoker< * Inside `handle(...)`, the owning definition contextually supplies its public * input and `parentEvents` protocols. Invocation sources and lifecycle handlers * can therefore send through `self` and `parent` without naming the definition. - * The bound `definition.invoke(...)` constructor remains an equivalent form. + * The standard `Machine.invoke(...)` constructor preserves these contexts + * directly; no intermediate definition method is required. * * ```ts * invoke: Machine.invoke({ @@ -8591,14 +8264,12 @@ export interface Invoker< * try: () => fetch("/api/data").then((response) => response.json()), * catch: (cause) => new LoadError({ cause }) * }), - * onDone: Machine.transition({ - * target: (to) => to.full.Ready(), - * resolve: ({ output, target }) => target.from({ data: output }) - * }), - * onFailure: Machine.transition({ - * target: (to) => to.full.Failed(), - * resolve: ({ error, target }) => target.from({ error }) - * }) + * onDone: (to) => + * to.full.Ready().resolve(({ output, target }) => + * target.from({ data: output })), + * onFailure: (to) => + * to.full.Failed().resolve(({ error, target }) => + * target.from({ error })) * }) * ``` * @@ -8614,10 +8285,12 @@ export const invoke: { const Source extends ( context: Machine.InvokeContext ) => Effect.Effect, + const Config extends object, const InputEvents extends ReadonlyArray = readonly [], const ParentEvents extends ReadonlyArray = readonly [] >( config: + & Config & EffectInvokeSource & { readonly onDone: EffectDoneHandler @@ -8630,7 +8303,7 @@ export const invoke: { "onFailure must be omitted when the Effect error is never" ] : [] - ): EffectInvokeResult + ): NoInfer & EffectInvokeResult < const States extends Machine.StateSchemas, const Events extends ReadonlyArray, @@ -8639,10 +8312,12 @@ export const invoke: { const Source extends ( context: Machine.InvokeContext ) => Effect.Effect, + const Config extends object, const InputEvents extends ReadonlyArray = readonly [], const ParentEvents extends ReadonlyArray = readonly [] >( config: + & Config & EffectInvokeSource & { readonly onDone: EffectDoneHandler @@ -8652,7 +8327,7 @@ export const invoke: { "onDone must be omitted when the Effect output is never" ] : [] - ): EffectInvokeResult + ): NoInfer & EffectInvokeResult < const States extends Machine.StateSchemas, const Events extends ReadonlyArray, @@ -8661,10 +8336,12 @@ export const invoke: { const Source extends ( context: Machine.InvokeContext ) => Effect.Effect, + const Config extends object, const InputEvents extends ReadonlyArray = readonly [], const ParentEvents extends ReadonlyArray = readonly [] >( config: + & Config & EffectInvokeSource & { readonly onDone?: never @@ -8674,7 +8351,7 @@ export const invoke: { "onFailure must be omitted when the Effect error is never" ] : [] - ): EffectInvokeResult + ): NoInfer & EffectInvokeResult < const States extends Machine.StateSchemas, const Events extends ReadonlyArray, @@ -8683,16 +8360,18 @@ export const invoke: { const Source extends ( context: Machine.InvokeContext ) => Effect.Effect, + const Config extends object, const InputEvents extends ReadonlyArray = readonly [], const ParentEvents extends ReadonlyArray = readonly [] >( config: + & Config & EffectInvokeSource & { readonly onDone?: never readonly onFailure?: never } - ): EffectInvokeResult + ): NoInfer & EffectInvokeResult < const States extends Machine.StateSchemas, const Events extends ReadonlyArray, @@ -8701,10 +8380,12 @@ export const invoke: { const Source extends ( context: Machine.InvokeContext ) => Stream.Stream, + const Config extends object, const InputEvents extends ReadonlyArray = readonly [], const ParentEvents extends ReadonlyArray = readonly [] >( config: + & Config & StreamInvokeSource & { readonly onElement: StreamElementHandler @@ -8718,7 +8399,7 @@ export const invoke: { "onFailure must be omitted when the Stream error is never" ] : [] - ): StreamInvokeResult + ): NoInfer & StreamInvokeResult < const States extends Machine.StateSchemas, const Events extends ReadonlyArray, @@ -8727,10 +8408,12 @@ export const invoke: { const Source extends ( context: Machine.InvokeContext ) => Stream.Stream, + const Config extends object, const InputEvents extends ReadonlyArray = readonly [], const ParentEvents extends ReadonlyArray = readonly [] >( config: + & Config & StreamInvokeSource & { readonly onElement?: never @@ -8741,7 +8424,7 @@ export const invoke: { "onFailure must be omitted when the Stream error is never" ] : [] - ): StreamInvokeResult + ): NoInfer & StreamInvokeResult < const States extends Machine.StateSchemas, const Events extends ReadonlyArray, @@ -8750,17 +8433,19 @@ export const invoke: { const Source extends ( context: Machine.InvokeContext ) => Stream.Stream, + const Config extends object, const InputEvents extends ReadonlyArray = readonly [], const ParentEvents extends ReadonlyArray = readonly [] >( config: + & Config & StreamInvokeSource & { readonly onElement?: never readonly onDone: StreamDoneHandler readonly onFailure?: never } - ): StreamInvokeResult + ): NoInfer & StreamInvokeResult < const States extends Machine.StateSchemas, const Events extends ReadonlyArray, @@ -8769,17 +8454,19 @@ export const invoke: { const Source extends ( context: Machine.InvokeContext ) => Stream.Stream, + const Config extends object, const InputEvents extends ReadonlyArray = readonly [], const ParentEvents extends ReadonlyArray = readonly [] >( config: + & Config & StreamInvokeSource & { readonly onElement: StreamElementHandler readonly onDone: StreamDoneHandler readonly onFailure?: never } - ): StreamInvokeResult + ): NoInfer & StreamInvokeResult < const States extends Machine.StateSchemas, const Events extends ReadonlyArray, @@ -8790,64 +8477,60 @@ export const invoke: { const ParentEvents extends ReadonlyArray = readonly [] >( config: Config & Machine.TimerInvokeArgs - ): Config & Machine.InvokeOwned & Machine.InvokeTyped + ): + & NoInfer + & Machine.InvokeOwned + & Machine.InvokeTyped < const States extends Machine.StateSchemas, const Events extends ReadonlyArray, const Emits extends ReadonlyArray, StateId extends Machine.StateIdentifier, - ChildState, - ChildEvent, - ChildError, - ChildRequirements, - ChildOutput, - ChildInitialError, + const Source extends ( + context: Machine.InvokeContext + ) => unknown, Address extends ChildAddress, + const Config extends object, const InputEvents extends ReadonlyArray = readonly [], const ParentEvents extends ReadonlyArray = readonly [] >( - config: Machine.LogicInvokeArgs< - States, - Events, - Emits, - StateId, - ChildState, - ChildEvent, - ChildError, - ChildRequirements, - ChildOutput, - ChildInitialError, - Address, - (context: Machine.InvokeContext) => Logic< - ChildState, - ChildEvent, - ChildError, - ChildRequirements, - ChildOutput, - ChildInitialError + config: + & Config + & { readonly logic: Source } + & Machine.LogicInvokeArgs< + States, + Events, + Emits, + StateId, + Machine.LogicStateOf>, + Machine.LogicEventOf>, + Machine.LogicErrorOf>, + Machine.LogicServicesOf>, + Machine.LogicOutputOf>, + Machine.LogicInitialErrorOf>, + Address, + Source, + InputEvents, + ParentEvents >, - InputEvents, - ParentEvents - > + ..._validation: ReturnType extends { readonly initial: unknown; readonly run: unknown } ? [] : [ + "logic factory must return Machine.Logic" + ] ): - & Machine.InvokeConfig + & NoInfer + & Machine.InvokeOwned & Machine.InvokeTyped< - ChildOutput, - ChildError, - ChildRequirements, - ChildInitialError + Machine.LogicOutputOf>, + Machine.LogicErrorOf>, + Machine.LogicServicesOf>, + Machine.LogicInitialErrorOf> > < const States extends Machine.StateSchemas, const Events extends ReadonlyArray, const Emits extends ReadonlyArray, StateId extends Machine.StateIdentifier, - ChildState, - ChildEvent, - ChildError, - ChildRequirements, - ChildOutput, - ChildInitialError, + const Source, Address extends ChildAddress, const Config extends object, const InputEvents extends ReadonlyArray = readonly [], @@ -8855,30 +8538,34 @@ export const invoke: { >( config: & Config + & { readonly logic: Source } & Machine.LogicInvokeArgs< States, Events, Emits, StateId, - ChildState, - ChildEvent, - ChildError, - ChildRequirements, - ChildOutput, - ChildInitialError, + Machine.LogicStateOf, + Machine.LogicEventOf, + Machine.LogicErrorOf, + Machine.LogicServicesOf, + Machine.LogicOutputOf, + Machine.LogicInitialErrorOf, Address, - Logic, + Source, InputEvents, ParentEvents - > + >, + ..._validation: Source extends { readonly initial: unknown; readonly run: unknown } ? [] : [ + "logic must implement Machine.Logic" + ] ): - & Config - & Machine.InvokeOwned + & NoInfer + & Machine.InvokeOwned & Machine.InvokeTyped< - ChildOutput, - ChildError, - ChildRequirements, - ChildInitialError + Machine.LogicOutputOf, + Machine.LogicErrorOf, + Machine.LogicServicesOf, + Machine.LogicInitialErrorOf > < const States extends Machine.StateSchemas, @@ -8895,7 +8582,7 @@ export const invoke: { & Machine.ChildInvokeArgs ): & Config - & Machine.InvokeOwned + & Machine.InvokeOwned & Machine.InvokeTyped< Machine.Output, Machine.Error | ActionError>, @@ -9202,10 +8889,8 @@ export const enabled: < * }).handle({ * Off: { * on: { - * Toggle: Machine.transition({ - * target: (to) => to.full.On(), - * resolve: ({ target }) => target.from() - * }) + * Toggle: (to) => + * to.full.On().resolve(({ target }) => target.from()) * } * }, * On: {} diff --git a/src/internal/machine/machine.ts b/src/internal/machine/machine.ts index 7c8cd31..756abd9 100644 --- a/src/internal/machine/machine.ts +++ b/src/internal/machine/machine.ts @@ -112,8 +112,6 @@ const Proto = { } } -const makeBoundInvoke = (config: unknown): unknown => config - const makeWithHandlers = ( self: Definition.Any, handlers: Machine.StateConfigs @@ -131,7 +129,6 @@ const makeWithHandlers = ( machine.stateNodes = self.stateNodes machine.makeTargetBuilder = self.makeTargetBuilder machine.handlers = handlers - machine.invoke = makeBoundInvoke Protocol.copyProtocol(self, machine) return machine } @@ -157,6 +154,137 @@ type CapturedNamedBranch = { readonly selection: Topology.TargetSelection } +const TransitionBuilderDescriptorTypeId: unique symbol = Symbol("effect/Machine/TransitionBuilderDescriptor") + +type DirectTransitionDescriptor = { + readonly [TransitionBuilderDescriptorTypeId]: typeof TransitionBuilderDescriptorTypeId + readonly type: "direct" + readonly selection: Topology.TargetSelection + readonly resolve?: (context: any, enqueue: unknown) => unknown + readonly reenter: boolean + readonly declinable: boolean +} + +type BranchesTransitionDescriptor = { + readonly [TransitionBuilderDescriptorTypeId]: typeof TransitionBuilderDescriptorTypeId + readonly type: "branches" + readonly declarations: unknown + readonly resolve: (context: any, enqueue: unknown) => unknown + readonly reenter: boolean + readonly declinable: boolean +} + +type TransitionBuilderDescriptor = DirectTransitionDescriptor | BranchesTransitionDescriptor + +const plainTargetSelection = (selection: Topology.TargetSelection): Topology.TargetSelection => + 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 + ? options as { readonly reenter?: unknown; readonly declinable?: unknown } + : {} + return { + reenter: configuration.reenter === true, + declinable: configuration.declinable === true + } +} + +const makeDirectTransitionDescriptor = ( + selection: Topology.TargetSelection, + resolve: ((context: any, enqueue: unknown) => unknown) | undefined, + options: unknown +): DirectTransitionDescriptor => { + const shared: Omit = { + [TransitionBuilderDescriptorTypeId]: TransitionBuilderDescriptorTypeId, + type: "direct", + selection: plainTargetSelection(selection), + ...transitionOptions(options) + } + return resolve === undefined + ? Object.freeze(shared) + : Object.freeze({ ...shared, resolve }) +} + +const decorateTransitionSelection = (selection: Topology.TargetSelection): Topology.TargetSelection => + Object.freeze({ + ...selection, + resolve: (resolve: (context: any, enqueue: unknown) => unknown, options?: unknown) => + makeDirectTransitionDescriptor(selection, resolve, options), + reenter: () => makeDirectTransitionDescriptor(selection, undefined, { reenter: true }) + }) + +const decorateTransitionSelectorNode = (node: unknown): unknown => { + if (typeof node === "function") { + const wrapped = ((...args: ReadonlyArray) => decorateTransitionSelection(node(...args))) as + & ((...args: ReadonlyArray) => unknown) + & Record + for (const key of Object.keys(node)) { + wrapped[key] = decorateTransitionSelectorNode((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] = decorateTransitionSelectorNode((node as Record)[key]) + } + return Object.freeze(wrapped) + } + return node +} + +const makeTransitionSelector = ( + stateNodes: Machine.StateNodes, + source: string +): unknown => { + const selector = { + ...decorateTransitionSelectorNode(makeTargetSelector(stateNodes, source)) as Record + } + selector.branches = (declarations: unknown) => + Object.freeze({ + resolve: ( + resolve: (context: any, enqueue: unknown) => unknown, + options?: unknown + ): BranchesTransitionDescriptor => + Object.freeze({ + [TransitionBuilderDescriptorTypeId]: TransitionBuilderDescriptorTypeId, + type: "branches", + declarations, + resolve, + ...transitionOptions(options) + }) + }) + return Object.freeze(selector) +} + +const normalizeTransitionBuilder = ( + transition: (selector: unknown) => unknown, + stateNodes: Machine.StateNodes, + path: string +): unknown => { + const result = transition(makeTransitionSelector(stateNodes, path)) + if (Topology.isTargetSelection(result)) { + const selection = plainTargetSelection(result) + return { target: () => selection } + } + if (!hasProperty(result, TransitionBuilderDescriptorTypeId)) return result + const descriptor = result as TransitionBuilderDescriptor + if (descriptor.type === "branches") { + return { + branches: () => descriptor.declarations, + resolve: descriptor.resolve, + reenter: descriptor.reenter, + declinable: descriptor.declinable + } + } + return { + target: () => descriptor.selection, + resolve: descriptor.resolve, + reenter: descriptor.reenter, + declinable: descriptor.declinable + } +} + const transitionTargetSelection = ( selection: Topology.TargetSelection ): Machine.TransitionTargetSelection => @@ -310,7 +438,8 @@ const getSelectionBuilder = ( return builder } -const constructSelectedTarget = (builder: any): unknown => typeof builder === "function" ? builder() : builder.from() +const constructSelectedTarget = (builder: any): unknown => + typeof builder?.from === "function" ? builder.from() : builder() const validateResolvedSelection = ( result: unknown, @@ -402,7 +531,7 @@ const captureNamedBranches = ( if (title !== undefined && (typeof title !== "string" || title.length === 0)) { throw new Error(`Machine transition branch "${key}" title must be a non-empty string`) } - return Object.freeze({ key, title: title ?? key, selection: target }) + return Object.freeze({ key, title: title ?? key, selection: plainTargetSelection(target) }) })) } @@ -482,11 +611,14 @@ const validateSelectedBranchResult = ( } const captureTransition = ( - transition: unknown, + rawTransition: unknown, stateNodes: Machine.StateNodes, path: string, trigger: PropertyKey ): unknown => { + const transition = typeof rawTransition === "function" + ? normalizeTransitionBuilder(rawTransition as (selector: unknown) => unknown, stateNodes, path) + : rawTransition if (typeof transition !== "object" || transition === null) { throw new Error(`Machine transition for state "${path}" on "${String(trigger)}" must be an object`) } @@ -1427,7 +1559,6 @@ export const make: Make = (< self.makeTargetBuilder = makeTargetBuilder(config.states, self.stateNodes) self.handlers = Object.create(null) self.handle = makeHandle(self) - self.invoke = makeBoundInvoke Protocol.setProtocol(self) return self }) as Make diff --git a/src/internal/testing/machine/finiteModel.ts b/src/internal/testing/machine/finiteModel.ts index 34fee8b..98883ba 100644 --- a/src/internal/testing/machine/finiteModel.ts +++ b/src/internal/testing/machine/finiteModel.ts @@ -1415,11 +1415,10 @@ const makeHandlers = ( if (node._tag === "History") continue if (node._tag === "Choice") { handlers[node.key] = { - choice: (Machine.transition as any)({ - target: (to: Record) => selectDefinitionTarget(to, path, node.selected, byPath) as any, - resolve: ({ target }: { readonly target: any }) => - resolveDefinitionTarget(target, path, node.selected, byPath) - } as any) + choice: (to: Record) => + (selectDefinitionTarget(to, path, node.selected, byPath) as any).resolve( + ({ target }: { readonly target: any }) => resolveDefinitionTarget(target, path, node.selected, byPath) + ) } continue } @@ -1432,17 +1431,16 @@ const makeHandlers = ( let onDone: unknown for (const transition of transitions.values()) { if (transition.source !== path) continue - const config = (Machine.transition as any)({ - ...("reenter" in transition ? { reenter: transition.reenter } : {}), - target: (to: Record) => - transition.target === undefined - ? to.none() - : selectDefinitionTarget(to, path, transition.target, byPath), - resolve: transition.target === undefined + const config = (to: Record) => { + const selected = transition.target === undefined + ? to.none() + : selectDefinitionTarget(to, path, transition.target, byPath) + const resolve = transition.target === undefined ? () => undefined : ({ target }: { readonly target: any }) => resolveDefinitionTarget(target, path, transition.target!, byPath, transition.targetValue) - } as any) + return selected.resolve(resolve, "reenter" in transition ? { reenter: transition.reenter } : undefined) + } if (transition.trigger.type === "event") on[transition.trigger.event] = config else if (transition.trigger.type === "always") always = config else onDone = config diff --git a/src/testing/MachineTest.ts b/src/testing/MachineTest.ts index 55da490..f726dd0 100644 --- a/src/testing/MachineTest.ts +++ b/src/testing/MachineTest.ts @@ -238,10 +238,8 @@ export interface Scenarios { * }).handle({ * Idle: { * on: { - * Reset: Machine.transition({ - * target: (to) => to.full.Idle(), - * resolve: ({ target }) => target.from() - * }) + * Reset: (to) => + * to.full.Idle().resolve(({ target }) => target.from()) * } * } * }) @@ -1447,10 +1445,9 @@ export type ExploreOptions to.full.Count(), - * resolve: ({ state, target }) => target(new Count({ value: state.value + 1 })) - * }) + * Increment: (to) => + * to.full.Count().resolve(({ state, target }) => + * target(new Count({ value: state.value + 1 }))) * } * } * }) diff --git a/test/internal/machine/activities.test.ts b/test/internal/machine/activities.test.ts index b8c450c..4725337 100644 --- a/test/internal/machine/activities.test.ts +++ b/test/internal/machine/activities.test.ts @@ -47,22 +47,13 @@ const activityMachine = Machine.make({ Machine.invoke({ id: "load-document", effect: () => Effect.fail("unavailable").pipe(Effect.as(1)), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless }, + onFailure: { target: Machine.targetless } }), Machine.invoke({ id: "load-timeout", after: timerDuration, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }), Machine.invoke({ id: "updates", @@ -200,10 +191,7 @@ describe("machine activity metadata", () => { invoke: Machine.invoke({ id, after: durationMillis, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }) } }) diff --git a/test/internal/machine/strategyDifferential.test.ts b/test/internal/machine/strategyDifferential.test.ts index 3dd5644..2f72021 100644 --- a/test/internal/machine/strategyDifferential.test.ts +++ b/test/internal/machine/strategyDifferential.test.ts @@ -42,16 +42,11 @@ const makeFlatMachine = () => { }).handle({ Count: { on: { - Noop: Machine.transition({ target: (to) => to.none(), resolve: () => undefined }), - Increment: Machine.transition({ - target: (to) => to.full.Count(), - resolve: ({ state, target }) => target(new Count({ value: state.value + 1 })) - }), - Reenter: Machine.transition({ target: (to) => to.none(), resolve: () => undefined, reenter: true }), - Finish: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ state, target }) => target(new Done({ value: state.value })) - }) + Noop: { target: Machine.targetless }, + Increment: (to) => + to.full.Count().resolve(({ state, target }) => target(new Count({ value: state.value + 1 }))), + Reenter: (to) => to.none().resolve(() => undefined, { reenter: true }), + Finish: (to) => to.full.Done().resolve(({ state, target }) => target(new Done({ value: state.value }))) } }, Done: { output: ({ state }) => state.value } @@ -79,19 +74,18 @@ describe("machine planner and runtime strategies", () => { }).handle({ Count: { on: { - Select: Machine.transition({ - branches: (to) => ({ + Select: (to) => + to.branches({ negative: { target: to.none() }, zero: { target: to.none() }, positive: { target: to.none() } - }), - resolve: ({ event, select }) => + }).resolve(({ event, select }) => event.value < 0 ? select.negative() : event.value === 0 ? select.zero() : select.positive() - }) + ) } } }) @@ -125,14 +119,11 @@ describe("machine planner and runtime strategies", () => { }).handle({ Count: { on: { - Select: Machine.transition({ - declinable: true, - target: (to) => to.full.Count(), - resolve: ({ event, state, target, decline }) => + Select: (to) => + to.full.Count().resolve(({ event, state, target, decline }) => event.value < 0 ? decline() - : target(new Count({ value: state.value + event.value })) - }) + : target(new Count({ value: state.value + event.value })), { declinable: true }) } } }) @@ -204,18 +195,14 @@ describe("machine planner and runtime strategies", () => { states: { Left: { on: { - Advance: Machine.transition({ - target: (to) => to.branch.Root.Left(), - resolve: ({ state, target }) => target(new Left({ value: state.value + 1 })) - }) + Advance: (to) => + to.branch.Root.Left().resolve(({ state, target }) => target(new Left({ value: state.value + 1 }))) } }, Right: { on: { - Advance: Machine.transition({ - target: (to) => to.branch.Root.Right(), - resolve: ({ state, target }) => target(new Right({ value: state.value + 10 })) - }) + Advance: (to) => + to.branch.Root.Right().resolve(({ state, target }) => target(new Right({ value: state.value + 10 }))) } } } @@ -254,10 +241,7 @@ describe("machine planner and runtime strategies", () => { }).handle({ Outside: { on: { - Enter: Machine.transition({ - target: (to) => to.full.Opened.initial(), - resolve: ({ target }) => target(new Opened({})) - }) + Enter: (to) => to.full.Opened.initial().resolve(({ target }) => target(new Opened({}))) } }, Opened: { @@ -339,10 +323,7 @@ describe("machine planner and runtime strategies", () => { } }).handle({ Idle: { - always: Machine.transition({ - target: (to) => to.full.Ready(), - resolve: ({ target }) => target(new Ready({})) - }) + always: (to) => to.full.Ready().resolve(({ target }) => target(new Ready({}))) }, Ready: {} }) @@ -466,7 +447,7 @@ describe("machine planner and runtime strategies", () => { }) const machine = definition.handle({ Streaming: { - invoke: definition.invoke({ + invoke: Machine.invoke({ id: "values", stream: () => Stream.fromIterable([1, 2, 3]), onElement: { @@ -475,10 +456,7 @@ describe("machine planner and runtime strategies", () => { seen.push(element) } }, - onDone: Machine.transition({ - target: (to) => to.full.StreamDone(), - resolve: ({ target }) => target(new StreamDone({ values: [...seen] })) - }) + onDone: (to) => to.full.StreamDone().resolve(({ target }) => target(new StreamDone({ values: [...seen] }))) }) }, StreamDone: { output: ({ state }) => state.values } @@ -508,10 +486,8 @@ describe("machine planner and runtime strategies", () => { const machine = definition.handle({ Count: { on: { - Set: Machine.transition({ - target: (to) => to.full.Count(), - resolve: ({ event, target }) => target(new Count({ value: event.value.length })) - }) + Set: (to) => + to.full.Count().resolve(({ event, target }) => target(new Count({ value: event.value.length }))) } } }) @@ -562,15 +538,13 @@ describe("machine planner and runtime strategies", () => { }).handle({ Idle: { on: { - Publish: Machine.transition({ - target: (to) => to.none(), - resolve: ({ parent, self }, enqueue) => { + Publish: (to) => + to.none().resolve(({ parent, self }, enqueue) => { assert.strictEqual(parent, undefined) assert.ok(self.sessionId.startsWith("machine:")) enqueue.emit(Emissions.Published({ value } as never)) return undefined - } - }) + }) } } }) @@ -743,20 +717,15 @@ describe("machine planner and runtime strategies", () => { }).handle({ Idle: { on: { - Load: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({})) - }) + Load: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({}))) } }, Loading: { invoke: Machine.invoke({ id: "load", effect: () => Effect.succeed(new Loaded({ value: "complete" })), - onDone: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ output, target }) => target(new Success({ value: output.value })) - }) + onDone: (to) => + to.full.Success().resolve(({ output, target }) => target(new Success({ value: output.value }))) }) }, Success: { output: ({ state }) => state.value } @@ -801,10 +770,7 @@ describe("machine planner and runtime strategies", () => { invoke: Machine.invoke({ id: "load", effect: () => Effect.fail("unavailable"), - onFailure: Machine.transition({ - target: (to) => to.full.Failed(), - resolve: ({ error, target }) => target(new Failed({ error })) - }) + onFailure: (to) => to.full.Failed().resolve(({ error, target }) => target(new Failed({ error }))) }) }, Failed: { output: ({ state }) => state.error } @@ -844,35 +810,9 @@ describe("machine planner and runtime strategies", () => { resolve: ({ target }) => target(new Loading({ epoch: 0 })) } }) - const onSnapshot: Machine.Machine.InvokeTransition< - Machine.Machine.States, - Machine.Machine.Events, - Machine.Machine.Emits, - "Loading", - Machine.Machine.InvokeSnapshotContext< - Machine.Machine.States, - Machine.Machine.Events, - Machine.Machine.Emits, - "Loading", - string, - Machine.StoppedError, - never, - Machine.Machine.InputEvents, - Machine.Machine.ParentEvents - > - > = Machine.transition({ - branches: (to) => ({ - stale: { title: "Worker is stale", target: to.full.Failed() }, - unchanged: { target: to.none() } - }), - resolve: ({ snapshot, select }) => - snapshot.state === "stale" - ? select.stale(new Failed({})) - : select.unchanged() - }) const machine = definition.handle({ Loading: { - invoke: { + invoke: Machine.invoke({ id: "worker", address: Machine.childAddress("worker"), logic: () => { @@ -893,22 +833,23 @@ describe("machine planner and runtime strategies", () => { ) }) }, - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onSnapshot - }, + onFailure: { target: Machine.targetless }, + onSnapshot: (to) => + to.branches({ + stale: { title: "Worker is stale", target: to.full.Failed() }, + unchanged: { target: to.none() } + }).resolve(({ snapshot, select }) => + snapshot.state === "stale" + ? select.stale(new Failed({})) + : select.unchanged() + ) + }), on: { - Reenter: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ state, target }) => target(new Loading({ epoch: state.epoch + 1 })), - reenter: true - }), - Stale: Machine.transition({ - target: (to) => to.full.Failed(), - resolve: ({ target }) => target(new Failed({})) - }) + Reenter: (to) => + to.full.Loading().resolve(({ state, target }) => target(new Loading({ epoch: state.epoch + 1 })), { + reenter: true + }), + Stale: (to) => to.full.Failed().resolve(({ target }) => target(new Failed({}))) } }, Failed: {} diff --git a/test/machine/ActivityLifecycleModel.test.ts b/test/machine/ActivityLifecycleModel.test.ts index 0e3ae29..be1a8bc 100644 --- a/test/machine/ActivityLifecycleModel.test.ts +++ b/test/machine/ActivityLifecycleModel.test.ts @@ -80,10 +80,7 @@ describe("machine activity lifecycle model", () => { }).handle({ Idle: { on: { - Enter: Machine.transition({ - target: (to) => to.full.Active(), - resolve: ({ target }) => target(new Active({})) - }) + Enter: (to) => to.full.Active().resolve(({ target }) => target(new Active({}))) } }, Active: { @@ -91,25 +88,12 @@ describe("machine activity lifecycle model", () => { id: "activity", address: Machine.childAddress("activity"), logic: probe.logic("active", { _tag: "Blocked" }), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless }, + onFailure: { target: Machine.targetless } }), on: { - Leave: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ target }) => target(new Idle({})) - }), - Restart: Machine.transition({ - target: (to) => to.full.Active(), - resolve: ({ target }) => target(new Active({})), - reenter: true - }) + Leave: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({}))), + Restart: (to) => to.full.Active().resolve(({ target }) => target(new Active({})), { reenter: true }) } } }) @@ -174,14 +158,8 @@ describe("machine activity lifecycle model", () => { id: "immediate", address: Machine.childAddress("immediate"), logic: probe.immediate("immediate", (epoch) => new Completed({ epoch })), - onDone: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ output, target }) => target(new Done({ epoch: output.epoch })) - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: (to) => to.full.Done().resolve(({ output, target }) => target(new Done({ epoch: output.epoch }))), + onFailure: { target: Machine.targetless } }) }, Done: { @@ -222,29 +200,20 @@ describe("machine activity lifecycle model", () => { _tag: "StaleOnCancel", event: (epoch) => new Completed({ epoch }) }), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless }, + onFailure: { target: Machine.targetless } }), on: { - Restart: Machine.transition({ - target: (to) => to.full.Active(), - resolve: ({ state, target }) => target(new EpochActive({ acknowledged: state.acknowledged })), - reenter: true - }), - QueueBarrier: Machine.transition({ - target: (to) => to.full.Active(), - resolve: ({ state, target }) => target(new EpochActive({ acknowledged: state.acknowledged + 1 })) - }), - Completed: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ event, target }) => target(new Done({ epoch: event.epoch })) - }) + Restart: (to) => + to.full.Active().resolve( + ({ state, target }) => target(new EpochActive({ acknowledged: state.acknowledged })), + { reenter: true } + ), + QueueBarrier: (to) => + to.full.Active().resolve(({ state, target }) => + target(new EpochActive({ acknowledged: state.acknowledged + 1 })) + ), + Completed: (to) => to.full.Done().resolve(({ event, target }) => target(new Done({ epoch: event.epoch }))) } }, Done: {} @@ -349,20 +318,11 @@ describe("machine activity lifecycle model", () => { id: "left-activity", address: Machine.childAddress("left-activity"), logic: probe.logic("left", { _tag: "Blocked" }), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless }, + onFailure: { target: Machine.targetless } }), on: { - LeaveLeft: Machine.transition({ - target: (to) => to.local.idle(), - resolve: ({ target }) => target(new LeftIdle({})) - }) + LeaveLeft: (to) => to.local.idle().resolve(({ target }) => target(new LeftIdle({}))) } } } @@ -374,14 +334,8 @@ describe("machine activity lifecycle model", () => { id: "right-activity", address: Machine.childAddress("right-activity"), logic: probe.logic("right", { _tag: "Blocked" }), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless }, + onFailure: { target: Machine.targetless } }) } } @@ -433,29 +387,17 @@ describe("machine activity lifecycle model", () => { id: "timed-activity", address: Machine.childAddress("timed-activity"), logic: probe.logic("timed", { _tag: "Blocked" }), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless }, + onFailure: { target: Machine.targetless } }), Machine.invoke({ id: "deadline", after: "1 hour", - onDone: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ target }) => target(new Done({ epoch: -1 })) - }) + onDone: (to) => to.full.Done().resolve(({ target }) => target(new Done({ epoch: -1 }))) }) ], on: { - Leave: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ target }) => target(new Idle({})) - }) + Leave: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({}))) } }, Done: {} @@ -493,29 +435,18 @@ describe("machine activity lifecycle model", () => { id: "failing", address: Machine.childAddress("failing"), logic: probe.logic("failing", { _tag: "Failure" }), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: ({ error }) => { + onDone: { target: Machine.targetless }, + onFailure: (to) => + to.none().resolve(({ error }) => { throw error - } - }) + }) }), Machine.invoke({ id: "sibling", address: Machine.childAddress("sibling"), logic: probe.logic("sibling", { _tag: "Blocked" }), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless }, + onFailure: { target: Machine.targetless } }) ] } @@ -556,27 +487,15 @@ describe("machine activity lifecycle model", () => { id: "first", address: Machine.childAddress("first"), logic: probe.logic("first", { _tag: "Blocked" }), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless }, + onFailure: { target: Machine.targetless } }), Machine.invoke({ id: "second", address: Machine.childAddress("second"), logic: probe.logic("second", { _tag: "Blocked" }), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless }, + onFailure: { target: Machine.targetless } }) ] } diff --git a/test/machine/Choice.test.ts b/test/machine/Choice.test.ts index 57f6138..6598256 100644 --- a/test/machine/Choice.test.ts +++ b/test/machine/Choice.test.ts @@ -33,18 +33,15 @@ const machine = Machine.make({ Flow: { states: { Routing: { - choice: Machine.transition({ - branches: (to) => { - branchFactoryCalls += 1 - return { - perfect: { title: "Score is perfect", target: to.local.Approved() }, - negative: { title: "Score is negative", target: to.local.Rejected() }, - zero: { title: "Score is zero", target: to.local.Rejected() }, - passing: { title: "Score is at least 70", target: to.local.Approved() }, - failing: { target: to.local.Rejected() } - } - }, - resolve: ({ containingState, select }) => { + choice: (to) => { + branchFactoryCalls += 1 + return to.branches({ + perfect: { title: "Score is perfect", target: to.local.Approved() }, + negative: { title: "Score is negative", target: to.local.Rejected() }, + zero: { title: "Score is zero", target: to.local.Rejected() }, + passing: { title: "Score is at least 70", target: to.local.Approved() }, + failing: { target: to.local.Rejected() } + }).resolve(({ containingState, select }) => { const score = containingState.score return score === 100 ? select.perfect(new Approved({})) @@ -55,15 +52,13 @@ const machine = Machine.make({ : score >= 70 ? select.passing(new Approved({})) : select.failing(new Rejected({})) - } - }) + }) + } }, Approved: { on: { - Recheck: Machine.transition({ - target: (to) => to.branch.Flow.initial(), - resolve: ({ event, target }) => target(new Flow({ score: event.score })) - }) + Recheck: (to) => + to.branch.Flow.initial().resolve(({ event, target }) => target(new Flow({ score: event.score }))) } } } @@ -167,13 +162,10 @@ describe("Machine choice pseudo-states", () => { Flow: { states: { First: { - choice: Machine.transition({ target: (to) => to.local.Second(), resolve: ({ target }) => target() }) + choice: (to) => to.local.Second().resolve(({ target }) => target()) }, Second: { - choice: Machine.transition({ - target: (to) => to.local.Approved(), - resolve: ({ target }) => target(new Approved({})) - }) + choice: (to) => to.local.Approved().resolve(({ target }) => target(new Approved({}))) } } } @@ -220,10 +212,10 @@ describe("Machine choice pseudo-states", () => { Flow: { states: { First: { - choice: Machine.transition({ target: (to) => to.local.Second(), resolve: ({ target }) => target() }) + choice: (to) => to.local.Second().resolve(({ target }) => target()) }, Second: { - choice: Machine.transition({ target: (to) => to.local.First(), resolve: ({ target }) => target() }) + choice: (to) => to.local.First().resolve(({ target }) => target()) } } } @@ -263,16 +255,10 @@ describe("Machine choice pseudo-states", () => { Flow: { states: { Approved: { - always: Machine.transition({ - target: (to) => to.local.Routing(), - resolve: ({ target }) => target() - }) + always: (to) => to.local.Routing().resolve(({ target }) => target()) }, Routing: { - choice: Machine.transition({ - target: (to) => to.local.Rejected(), - resolve: ({ target }) => target(new Rejected({})) - }) + choice: (to) => to.local.Rejected().resolve(({ target }) => target(new Rejected({}))) } } } @@ -321,16 +307,10 @@ describe("Machine choice pseudo-states", () => { } }).handle({ Flow: { - onDone: Machine.transition({ - target: (to) => to.full.Flow(), - resolve: ({ state, target }) => target(state, (flow) => flow.Routing()) - }), + onDone: (to) => to.full.Flow().resolve(({ state, target }) => target(state, (flow) => flow.Routing())), states: { Routing: { - choice: Machine.transition({ - target: (to) => to.local.Rejected(), - resolve: ({ target }) => target(new Rejected({})) - }) + choice: (to) => to.local.Rejected().resolve(({ target }) => target(new Rejected({}))) } } } @@ -398,20 +378,14 @@ describe("Machine choice pseudo-states", () => { Left: { states: { Routing: { - choice: Machine.transition({ - target: (to) => to.local.Ready(), - resolve: ({ target }) => target(new Ready({})) - }) + choice: (to) => to.local.Ready().resolve(({ target }) => target(new Ready({}))) } } }, Right: { states: { Routing: { - choice: Machine.transition({ - target: (to) => to.local.Ready(), - resolve: ({ target }) => target(new RightReady({})) - }) + choice: (to) => to.local.Ready().resolve(({ target }) => target(new RightReady({}))) } } } @@ -464,26 +438,18 @@ describe("Machine choice pseudo-states", () => { states: { Active: { on: { - Leave: Machine.transition({ - target: (to) => to.full.Outside(), - resolve: ({ target }) => target(new Outside({})) - }) + Leave: (to) => to.full.Outside().resolve(({ target }) => target(new Outside({}))) } }, Routing: { - choice: Machine.transition({ - target: (to) => to.history.Flow.Recent(), - resolve: ({ target }) => target() - }) + choice: (to) => to.history.Flow.Recent().resolve(({ target }) => target()) } } }, Outside: { on: { - Resume: Machine.transition({ - target: (to) => to.full.Flow(), - resolve: ({ target }) => target(new Flow({ score: 2 }), (flow) => flow.Routing()) - }) + Resume: (to) => + to.full.Flow().resolve(({ target }) => target(new Flow({ score: 2 }), (flow) => flow.Routing())) } } }) @@ -532,10 +498,7 @@ describe("Machine choice pseudo-states", () => { }, states: { Routing: { - choice: Machine.transition({ - target: (to) => to.history.Flow.Recent(), - resolve: ({ target }) => target() - }) + choice: (to) => to.history.Flow.Recent().resolve(({ target }) => target()) } } } @@ -580,19 +543,13 @@ describe("Machine choice pseudo-states", () => { }, states: { Routing: { - choice: Machine.transition({ - target: (to) => to.local.Active(), - resolve: ({ target }) => target(new Active({})) - }) + choice: (to) => to.local.Active().resolve(({ target }) => target(new Active({}))) } } }, Outside: { on: { - FallbackChoiceResume: Machine.transition({ - target: (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 50f9200..c4b562e 100644 --- a/test/machine/DeepHandlers.test.ts +++ b/test/machine/DeepHandlers.test.ts @@ -136,11 +136,10 @@ const machine = Machine.make({ states: { idle: { on: { - Advance: Machine.transition({ - target: (to) => to.local.done(), - resolve: ({ event, target }) => + Advance: (to) => + to.local.done().resolve(({ event, target }) => target(new DeepDone({ value: event.value })) - }) + ) } }, done: {} diff --git a/test/machine/History.test.ts b/test/machine/History.test.ts index f0fa858..aa1920d 100644 --- a/test/machine/History.test.ts +++ b/test/machine/History.test.ts @@ -159,31 +159,21 @@ const makeCheckoutMachine = ( } }, on: { - Leave: Machine.transition({ - target: (to) => to.full.support(), - resolve: ({ target }) => target(new Support({ ticket: "ticket-1" })) - }), - GoShipping: Machine.transition({ - target: (to) => to.local.shipping(), - resolve: ({ event, target }) => target(new Shipping({ address: event.address })) - }), - ReenterHistory: Machine.transition({ - target: (to) => to.history.checkout.exact(), - resolve: ({ target }) => target(), - reenter: true - }) + 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 }) }, states: { shipping: { on: { - EnterVerifying: Machine.transition({ - target: (to) => to.local.payment(), - resolve: ({ target }) => + EnterVerifying: (to) => + to.local.payment().resolve(({ target }) => target( new Payment({ attempt: 2 }), (payment) => payment.verifying(new Verifying({ challengeId: "challenge-7" })) ) - }) + ) } }, payment: { @@ -218,14 +208,8 @@ const makeCheckoutMachine = ( lifecycle?.push("exit:support") }, on: { - ResumeShallow: Machine.transition({ - target: (to) => to.history.checkout.recent(), - resolve: ({ target }) => target() - }), - ResumeDeep: Machine.transition({ - target: (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()) } } }) @@ -365,10 +349,7 @@ const makeWorkspaceMachine = (initialized: Array) => } }, on: { - LeaveWorkspace: Machine.transition({ - target: (to) => to.full.away(), - resolve: ({ target }) => target(new Away({})) - }) + LeaveWorkspace: (to) => to.full.away().resolve(({ target }) => target(new Away({}))) }, states: { editor: { @@ -387,14 +368,8 @@ const makeWorkspaceMachine = (initialized: Array) => }, away: { on: { - ResumeWorkspaceShallow: Machine.transition({ - target: (to) => to.history.workspace.recent(), - resolve: ({ target }) => target() - }), - ResumeWorkspaceDeep: Machine.transition({ - target: (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()) } } }) @@ -479,23 +454,14 @@ const nestedHistoryMachine = Machine.make({ states: { preview: { on: { - RestoreEditor: Machine.transition({ - target: (to) => to.history.workspace.editor.exact(), - resolve: ({ target }) => target(), - reenter: true - }), - DefaultEditor: Machine.transition({ - target: (to) => to.history.workspace.editor.exact(), - resolve: ({ target }) => target() - }) + RestoreEditor: (to) => + to.history.workspace.editor.exact().resolve(({ target }) => target(), { reenter: true }), + DefaultEditor: (to) => to.history.workspace.editor.exact().resolve(({ target }) => target()) } }, writing: { on: { - DefaultEditor: Machine.transition({ - target: (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 f38c94d..4f4f20e 100644 --- a/test/machine/InitialEntry.test.ts +++ b/test/machine/InitialEntry.test.ts @@ -48,14 +48,8 @@ const makeMachine = () => }).handle({ closed: { on: { - Open: Machine.transition({ - target: (to) => to.full.opened.initial(), - resolve: ({ target }) => target.from({ id: "team-1" }) - }), - OpenInvalid: Machine.transition({ - target: (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: { @@ -90,10 +84,7 @@ const makeParallelMachine = () => }).handle({ outside: { on: { - EnterDashboard: Machine.transition({ - target: (to) => to.full.dashboard.initial(), - resolve: ({ target }) => target(new Dashboard({})) - }) + EnterDashboard: (to) => to.full.dashboard.initial().resolve(({ target }) => target(new Dashboard({}))) } }, dashboard: { @@ -129,19 +120,13 @@ const makeChoiceMachine = () => }).handle({ outside: { on: { - EnterFlow: Machine.transition({ - target: (to) => to.full.flow.initial(), - resolve: ({ target }) => target(new Flow({})) - }) + EnterFlow: (to) => to.full.flow.initial().resolve(({ target }) => target(new Flow({}))) } }, flow: { states: { routing: { - choice: Machine.transition({ - target: (to) => to.local.approved(), - resolve: ({ target }) => target(new Approved({})) - }) + choice: (to) => to.local.approved().resolve(({ target }) => target(new Approved({}))) } } } @@ -166,10 +151,7 @@ const makeStructuralMachine = () => }).handle({ outside: { on: { - EnterFlow: Machine.transition({ - target: (to) => to.full.group.initial(), - resolve: ({ target }) => target.from() - }) + EnterFlow: (to) => to.full.group.initial().resolve(({ target }) => target.from()) } } }) @@ -201,14 +183,8 @@ const makeNestedMachine = () => states: { closed: { on: { - OpenLocal: Machine.transition({ - target: (to) => to.local.opened.initial(), - resolve: ({ target }) => target.from({ id: "local" }) - }), - OpenBranch: Machine.transition({ - target: (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: { diff --git a/test/machine/Inspection.test.ts b/test/machine/Inspection.test.ts index 37000cf..3b3f859 100644 --- a/test/machine/Inspection.test.ts +++ b/test/machine/Inspection.test.ts @@ -69,7 +69,7 @@ const choiceMachine = Machine.make({ Flow: { states: { Routing: { - choice: Machine.transition({ target: (to) => to.local.Ready(), resolve: ({ target }) => target(new Ready({})) }) + choice: (to) => to.local.Ready().resolve(({ target }) => target(new Ready({}))) } } } diff --git a/test/machine/Invoke.test.ts b/test/machine/Invoke.test.ts index 60a87aa..8f43fad 100644 --- a/test/machine/Invoke.test.ts +++ b/test/machine/Invoke.test.ts @@ -38,11 +38,7 @@ describe("inline invoke", () => { invoke: Machine.invoke({ id: "load", effect: () => Effect.succeed("ignored"), - onDone: Machine.transition({ - declinable: true, - target: (to) => to.full.Complete(), - resolve: ({ decline }) => decline() - }) + onDone: (to) => to.full.Complete().resolve(({ decline }) => decline(), { declinable: true }) }) }, Complete: {}, @@ -78,16 +74,14 @@ describe("inline invoke", () => { enqueue.raise(new Add({ value: element })) } }, - onDone: Machine.transition({ - target: (to) => to.full.Complete(), - resolve: ({ state, target }) => target(new Complete({ value: state.values.join(",") })) - }) + onDone: (to) => + to.full.Complete().resolve(({ state, target }) => target(new Complete({ value: state.values.join(",") }))) }), on: { - Add: Machine.transition({ - target: (to) => to.full.Collecting(), - resolve: ({ event, state, target }) => target(new Collecting({ values: [...state.values, event.value] })) - }) + Add: (to) => + to.full.Collecting().resolve(({ event, state, target }) => + target(new Collecting({ values: [...state.values, event.value] })) + ) } }, Complete: {} @@ -153,14 +147,11 @@ describe("inline invoke", () => { }) const machine = definition.handle({ Loading: { - invoke: definition.invoke({ + invoke: Machine.invoke({ id: "updates", stream: () => Stream.fail("offline"), onDone: { target: Machine.targetless }, - onFailure: Machine.transition({ - target: (to) => to.full.Failed(), - resolve: ({ error, target }) => target(new Failed({ message: error })) - }) + onFailure: (to) => to.full.Failed().resolve(({ error, target }) => target(new Failed({ message: error }))) }) }, Complete: {}, @@ -192,7 +183,7 @@ describe("inline invoke", () => { }) const machine = definition.handle({ Loading: { - invoke: definition.invoke({ + invoke: Machine.invoke({ id: "updates", stream: () => Stream.die(defect), onDone: { target: Machine.targetless } @@ -230,7 +221,7 @@ describe("inline invoke", () => { }) const machine = definition.handle({ Loading: { - invoke: definition.invoke({ + invoke: Machine.invoke({ id: "updates", stream: () => source, onElement: { @@ -242,10 +233,8 @@ describe("inline invoke", () => { onDone: { target: Machine.targetless } }), on: { - FinishStream: Machine.transition({ - target: (to) => to.full.Complete(), - resolve: ({ event, target }) => target(new Complete({ value: String(event.value) })) - }) + FinishStream: (to) => + to.full.Complete().resolve(({ event, target }) => target(new Complete({ value: String(event.value) }))) } }, Complete: {}, @@ -278,10 +267,7 @@ describe("inline invoke", () => { invoke: Machine.invoke({ id: "load", effect: () => Effect.succeed("ready"), - onDone: Machine.transition({ - target: (to) => to.full.Complete(), - resolve: ({ output, target }) => target(new Complete({ value: output })) - }) + onDone: (to) => to.full.Complete().resolve(({ output, target }) => target(new Complete({ value: output }))) }) }, Complete: {}, @@ -319,10 +305,7 @@ describe("inline invoke", () => { invoke: Machine.invoke({ id: "load", effect: () => Effect.fail("offline"), - onFailure: Machine.transition({ - target: (to) => to.full.Failed(), - resolve: ({ error, target }) => target(new Failed({ message: error })) - }) + onFailure: (to) => to.full.Failed().resolve(({ error, target }) => target(new Failed({ message: error }))) }) }, Complete: {}, @@ -347,10 +330,7 @@ describe("inline invoke", () => { }).handle({ Idle: { on: { - Start: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target.from() - }) + Start: (to) => to.full.Loading().resolve(({ target }) => target.from()) } }, Loading: { @@ -359,10 +339,7 @@ describe("inline invoke", () => { effect: (): Effect.Effect => { throw defect }, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }) }, Complete: {}, @@ -396,10 +373,7 @@ describe("inline invoke", () => { }).handle({ Idle: { on: { - Start: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target.from() - }) + Start: (to) => to.full.Loading().resolve(({ target }) => target.from()) } }, Loading: { diff --git a/test/machine/LiveInspection.test.ts b/test/machine/LiveInspection.test.ts index b2bdbab..a1befc8 100644 --- a/test/machine/LiveInspection.test.ts +++ b/test/machine/LiveInspection.test.ts @@ -26,13 +26,11 @@ const machine = Machine.make({ }).handle({ Idle: { on: { - Increment: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ event, target }, enqueue) => { + Increment: (to) => + to.full.Idle().resolve(({ event, target }, enqueue) => { enqueue.emit(Emissions.Notice({ value: event.by })) return target(new Idle({})) - } - }) + }) } } }) @@ -243,13 +241,11 @@ describe("Machine live inspection", () => { }).handle({ ChildIdle: { on: { - Trigger: Machine.transition({ - target: (to) => to.none(), - resolve: ({ parent }, enqueue) => { + Trigger: (to) => + to.none().resolve(({ parent }, enqueue) => { if (parent !== undefined) enqueue.sendTo(parent, ParentEvents.ChildReady()) return undefined - } - }) + }) } } }) @@ -270,10 +266,7 @@ describe("Machine live inspection", () => { ParentIdle: { invoke: Machine.invoke({ child: Child }), on: { - ChildReady: Machine.transition({ - target: (to) => to.full.ParentDone(), - resolve: ({ target }) => target(new ParentDone({})) - }) + ChildReady: (to) => to.full.ParentDone().resolve(({ target }) => target(new ParentDone({}))) } }, ParentDone: {} diff --git a/test/machine/LocalTargetWith.test.ts b/test/machine/LocalTargetWith.test.ts index 1af263e..7964aac 100644 --- a/test/machine/LocalTargetWith.test.ts +++ b/test/machine/LocalTargetWith.test.ts @@ -34,20 +34,17 @@ describe("local compound target selection", () => { }).handle({ search: { on: { - UpdateQuery: Machine.transition({ - target: (to) => to.local.with(), - resolve: ({ event, target }) => target.from({ query: event.query }, (search) => search.Updated.from()), - reenter: true - }) + UpdateQuery: (to) => + to.local.with().resolve( + ({ event, target }) => target.from({ query: event.query }, (search) => search.Updated.from()), + { reenter: true } + ) }, states: { Idle: {}, Updated: { on: { - Reset: Machine.transition({ - target: (to) => to.local.Idle(), - resolve: ({ target }) => target.from() - }) + Reset: (to) => to.local.Idle().resolve(({ target }) => target.from()) } } } @@ -130,10 +127,10 @@ describe("local compound target selection", () => { invoke: Machine.invoke({ id: "search", effect: () => Effect.succeed("resolved"), - onDone: Machine.transition({ - target: (to) => to.local.with(), - resolve: ({ output, target }) => target.from({ query: output }, (search) => search.Updated.from()) - }) + onDone: (to) => + to.local.with().resolve(({ output, target }) => + target.from({ query: output }, (search) => search.Updated.from()) + ) }) }, Updated: {} @@ -190,13 +187,10 @@ describe("local compound target selection", () => { states: { Idle: { on: { - Advance: Machine.transition({ - target: (to) => { - assert.notProperty(to.local, "with") - return to.local.Updated() - }, - resolve: ({ target }) => target.from() - }) + Advance: (to) => { + assert.notProperty(to.local, "with") + return to.local.Updated().resolve(({ target }) => target.from()) + } } }, Updated: {} diff --git a/test/machine/Machine.test.ts b/test/machine/Machine.test.ts index b1400e3..75a2267 100644 --- a/test/machine/Machine.test.ts +++ b/test/machine/Machine.test.ts @@ -113,10 +113,7 @@ describe("Machine", () => { resolve: ({ target }) => target(new Stable({})) } }) - const transition = Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + const transition = { target: Machine.targetless } const handlingPing = definition.handle({ Stable: { on: { Ping: transition } } }) const ignoringPing = definition.handle({ Stable: {} }) @@ -135,12 +132,7 @@ describe("Machine", () => { class Stable extends Schema.TaggedClass("Stable")("Stable", {}) {} class Ping extends Schema.TaggedClass("Ping")("Ping", {}) {} const states = Machine.states({ Stable }) - const transition = Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined, - reenter: false - }) - const on: any = { Ping: transition } + let captures = 0 const machine = Machine.make({ states: states.states, events: Machine.events(Ping), @@ -148,13 +140,18 @@ describe("Machine", () => { target: (to) => to.Stable(), resolve: ({ target }) => target(new Stable({})) } - }).handle({ Stable: { on } }) + }).handle({ + Stable: { + on: { + Ping: (to) => { + captures++ + return to.none() + } + } + } + }) - delete on.Ping - ;(transition as any).reenter = true - ;(transition as any).target = () => { - throw new Error("captured transition unexpectedly re-evaluated its target selector") - } + assert.strictEqual(captures, 1) assert.deepStrictEqual(Machine.transitionDefinitions(machine), [{ source: "Stable", @@ -177,6 +174,55 @@ describe("Machine", () => { assert.deepStrictEqual(step.entryPaths, []) })) + it.effect("uses a bare selected target's default construction", () => + Effect.gen(function*() { + class Idle extends Schema.TaggedClass("BareTargetIdle")("Idle", {}) {} + class Done extends Schema.TaggedClass("BareTargetDone")("Done", {}) {} + class Finish extends Schema.TaggedClass("BareTargetFinish")("Finish", {}) {} + const machine = Machine.make({ + states: { Idle, Done }, + events: Machine.events(Finish), + initial: { + target: (to) => to.Idle(), + resolve: ({ target }) => target.from() + } + }).handle({ + Idle: { on: { Finish: (to) => to.full.Done() } }, + Done: {} + }) + + const initial = yield* Machine.planInitial(machine) + const planned = yield* Machine.plan(machine, initial.state, new Finish({})) + + assert.deepStrictEqual(planned.next, { path: "Done", value: new Done({}) }) + assert.deepStrictEqual(planned.microsteps[0]?.exitPaths, ["Idle"]) + assert.deepStrictEqual(planned.microsteps[0]?.entryPaths, ["Done"]) + })) + + it.effect("reenters a selected target without requiring a resolver", () => + Effect.gen(function*() { + class Stable extends Schema.TaggedClass("ResolverFreeReentryStable")("Stable", {}) {} + class Restart extends Schema.TaggedClass("ResolverFreeReentryRestart")("Restart", {}) {} + const machine = Machine.make({ + states: { Stable }, + events: Machine.events(Restart), + initial: { + target: (to) => to.Stable(), + resolve: ({ target }) => target.from() + } + }).handle({ + Stable: { on: { Restart: (to) => to.none().reenter() } } + }) + + assert.strictEqual(Machine.transitionDefinitions(machine)[0]?.reenter, true) + const initial = yield* Machine.planInitial(machine) + const planned = yield* Machine.plan(machine, initial.state, new Restart({})) + + assert.deepStrictEqual(planned.next, initial.state) + assert.deepStrictEqual(planned.microsteps[0]?.exitPaths, ["Stable"]) + assert.deepStrictEqual(planned.microsteps[0]?.entryPaths, ["Stable"]) + })) + it.effect("captures named branches once with stable semantic keys", () => Effect.gen(function*() { class Stable extends Schema.TaggedClass("NamedBranchStable")("Stable", {}) {} @@ -195,22 +241,18 @@ describe("Machine", () => { const machine = definition.handle({ Stable: { on: { - Ping: Machine.transition({ - branches: (to) => { - captures++ - const captured = { - unchanged: { target: to.none() }, - refresh: { title: "Refresh stable state", target: to.full.Stable() } - } - declarations = captured - return captured - }, - resolve: ({ event, select }) => + Ping: (to) => { + captures++ + const captured = { + unchanged: { target: to.none() }, + refresh: { title: "Refresh stable state", target: to.full.Stable() } + } + declarations = captured + return to.branches(captured).resolve(({ event, select }) => event.route ? select.refresh(new Stable({})) - : select.unchanged(), - reenter: true - }) + : select.unchanged(), { reenter: true }) + } } } }) @@ -264,10 +306,7 @@ describe("Machine", () => { makeDefinition().handle({ Stable: { on: { - Ping: Machine.transition({ - branches, - resolve: (() => undefined) as any - } as any) + Ping: ((to: any) => to.branches(branches(to)).resolve((() => undefined) as any)) as any } } }) @@ -300,17 +339,12 @@ describe("Machine", () => { }).handle({ Stable: { on: { - Capture: Machine.transition({ - branches: (to) => ({ unchanged: { target: to.none() } }), - resolve: ({ select }) => { + Capture: (to) => + to.branches({ unchanged: { target: to.none() } }).resolve(({ select }) => { captured = select.unchanged() return captured as any - } - }), - Reuse: Machine.transition({ - branches: (to) => ({ unchanged: { target: to.none() } }), - resolve: () => captured as any - }) + }), + Reuse: (to) => to.branches({ unchanged: { target: to.none() } }).resolve(() => captured as any) } } }) @@ -528,13 +562,11 @@ describe("Machine", () => { const machine = definition.handle({ Submit: { on: { - Convert: Machine.transition({ - target: (to) => to.full.RequestSucceeded(), - resolve: ({ state, target }) => { + Convert: (to) => + to.full.RequestSucceeded().resolve(({ state, target }) => { const { _tag: _, ...fields } = state return target.from(fields) - } - }) + }) } } }) @@ -562,12 +594,10 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => { + Submit: (to) => + to.full.Loading().resolve(({ target }) => { return target(new Loading({ requestId: "request-1" })) - } - }) + }) } } }) @@ -843,37 +873,18 @@ describe("Machine", () => { const machine = definition.handle({ Active: { on: { - SetValue: Machine.transition({ - target: (to) => to.full.Active(), - resolve: ({ event, target }) => target.from({ value: event.value }) - }), - Reset: Machine.transition({ - target: (to) => to.none(), - resolve: (_, enqueue) => { + SetValue: (to) => to.full.Active().resolve(({ event, target }) => target.from({ value: event.value })), + Reset: (to) => + to.none().resolve((_, enqueue) => { enqueue.raise(internalEvents.Loaded({ value: "loaded" })) return undefined - } - }), - Defaulted: Machine.transition({ - target: (to) => to.full.Active(), - resolve: ({ event, target }) => target.from({ value: event.label ?? "default-label" }) - }), - Loaded: Machine.transition({ - target: (to) => to.full.Active(), - resolve: ({ event, target }) => target.from({ value: event.value }) - }), - TimedOut: Machine.transition({ - target: (to) => to.full.Active(), - resolve: ({ target }) => target.from({ value: "timed-out" }) - }), - Alpha: Machine.transition({ - target: (to) => to.full.Active(), - resolve: ({ event, target }) => target.from({ value: event.value }) - }), - Beta: Machine.transition({ - target: (to) => to.full.Active(), - resolve: ({ event, target }) => target.from({ value: event.value }) - }) + }), + Defaulted: (to) => + to.full.Active().resolve(({ event, target }) => target.from({ value: event.label ?? "default-label" })), + Loaded: (to) => to.full.Active().resolve(({ event, target }) => target.from({ value: event.value })), + TimedOut: (to) => to.full.Active().resolve(({ target }) => target.from({ value: "timed-out" })), + Alpha: (to) => to.full.Active().resolve(({ event, target }) => target.from({ value: event.value })), + Beta: (to) => to.full.Active().resolve(({ event, target }) => target.from({ value: event.value })) } } }) @@ -938,10 +949,7 @@ describe("Machine", () => { const machine = definition.handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + Submit: { target: Machine.targetless } } } }) @@ -996,20 +1004,14 @@ describe("Machine", () => { invoke: Machine.invoke({ id: "load", effect: () => Deferred.await(release), - onDone: Machine.transition({ - target: (to) => to.full.Waiting(), - resolve: ({ target }) => target.from() - }) + onDone: (to) => to.full.Waiting().resolve(({ target }) => target.from()) }) }, Waiting: { invoke: Machine.invoke({ id: "timeout", after: "1 second", - onDone: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ target }) => target.from() - }) + onDone: (to) => to.full.Done().resolve(({ target }) => target.from()) }) }, Done: {} @@ -1055,10 +1057,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + Submit: { target: Machine.targetless } } } }) @@ -1119,10 +1118,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ event, target }) => target.from({ requestId: event.requestId }) - }) + Submit: (to) => to.full.Done().resolve(({ event, target }) => target.from({ requestId: event.requestId })) } }, Done: {} @@ -1222,27 +1218,15 @@ describe("Machine", () => { states: { Idle: { on: { - Local: Machine.transition({ - target: (to) => to.local.Running(), - resolve: ({ target }) => target.from() - }), - LocalWith: Machine.transition({ - target: (to) => to.local.Running(), - resolve: ({ target }) => target.from() - }), - Branch: Machine.transition({ - target: (to) => to.branch.Flow.Nested(), - resolve: ({ target }) => target.from((nested) => nested.NestedIdle.from()) - }), - Full: Machine.transition({ - target: (to) => to.full.Flow(), - resolve: ({ target }) => + Local: (to) => to.local.Running().resolve(({ target }) => target.from()), + LocalWith: (to) => to.local.Running().resolve(({ target }) => target.from()), + Branch: (to) => + to.branch.Flow.Nested().resolve(({ target }) => target.from((nested) => nested.NestedIdle.from())), + Full: (to) => + to.full.Flow().resolve(({ target }) => target.from((flow) => flow.Nested.from((nested) => nested.NestedIdle.from())) - }), - Finish: Machine.transition({ - target: (to) => to.local.Done(), - resolve: ({ target }) => target.from() - }) + ), + Finish: (to) => to.local.Done().resolve(({ target }) => target.from()) } }, Running: {}, @@ -1385,10 +1369,7 @@ describe("Machine", () => { }).handle({ NonEmptyIdle: { on: { - NonEmptySubmit: Machine.transition({ - target: (to) => to.full.NonEmptyLoading(), - resolve: ({ target }) => target.from({ requestId: "" }) - }) + NonEmptySubmit: (to) => to.full.NonEmptyLoading().resolve(({ target }) => target.from({ requestId: "" })) } } }) @@ -1443,9 +1424,8 @@ describe("Machine", () => { }).handle({ idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.fulfillment(), - resolve: ({ event, target }) => + Submit: (to) => + to.full.fulfillment().resolve(({ event, target }) => target.from( { id: event.value }, (fulfillment) => @@ -1459,7 +1439,7 @@ describe("Machine", () => { (shipping) => shipping.quoted.from({ quoteId: event.value }) ) ) - }) + ) } } }) @@ -1515,14 +1495,13 @@ describe("Machine", () => { states: { entering: { on: { - Submit: Machine.transition({ - target: (to) => to.branch.payment(), - resolve: ({ event, target }) => + Submit: (to) => + to.branch.payment().resolve(({ event, target }) => target.from( { id: "payment-2" }, (payment) => payment.authorized.from({ code: event.value }) ) - }) + ) } } } @@ -1572,9 +1551,8 @@ describe("Machine", () => { states: { idle: { on: { - Submit: Machine.transition({ - target: (to) => to.branch.workflow(), - resolve: ({ event, target }) => + Submit: (to) => + to.branch.workflow().resolve(({ event, target }) => target.from( { id: "workflow-2" }, (workflow) => @@ -1583,7 +1561,7 @@ describe("Machine", () => { (checkout) => checkout.quoted.from({ quoteId: event.value }) ) ) - }) + ) } } } @@ -1653,10 +1631,7 @@ describe("Machine", () => { }).handle({ NonEmptyIdle: { on: { - NonEmptySubmit: Machine.transition({ - target: (to) => to.full.NonEmptyIdle(), - resolve: ({ state, target }) => target(state) - }) + NonEmptySubmit: (to) => to.full.NonEmptyIdle().resolve(({ state, target }) => target(state)) } } }) @@ -1685,10 +1660,7 @@ describe("Machine", () => { }).handle({ NonEmptyIdle: { on: { - NonEmptySubmit: Machine.transition({ - target: (to) => to.full.NonEmptyIdle(), - resolve: ({ state, target }) => target(state) - }) + NonEmptySubmit: (to) => to.full.NonEmptyIdle().resolve(({ state, target }) => target(state)) } } }) @@ -1726,10 +1698,10 @@ describe("Machine", () => { }).handle({ NonEmptyIdle: { on: { - NonEmptySubmit: Machine.transition({ - target: (to) => to.full.NonEmptyLoading(), - resolve: ({ target }) => target(unsafeTagged({ _tag: "NonEmptyLoading", requestId: "" })) - }) + NonEmptySubmit: (to) => + to.full.NonEmptyLoading().resolve(({ target }) => + target(unsafeTagged({ _tag: "NonEmptyLoading", requestId: "" })) + ) } } }) @@ -1758,10 +1730,10 @@ describe("Machine", () => { }).handle({ NonEmptyIdle: { on: { - NonEmptySubmit: Machine.transition({ - target: (to) => to.full.NonEmptyIdle(), - resolve: ({ target }) => target(unsafeTagged({ _tag: "NonEmptyIdle", userId: "" })) - }) + NonEmptySubmit: (to) => + to.full.NonEmptyIdle().resolve(({ target }) => + target(unsafeTagged({ _tag: "NonEmptyIdle", userId: "" })) + ) } } }) @@ -1798,10 +1770,8 @@ describe("Machine", () => { }).handle({ NonEmptyIdle: { on: { - NonEmptySubmit: Machine.transition({ - target: (to) => to.full.done(), - resolve: ({ event, target }) => target(new NonEmptyDone({ requestId: event.value })) - }) + NonEmptySubmit: (to) => + to.full.done().resolve(({ event, target }) => target(new NonEmptyDone({ requestId: event.value }))) } }, done: { @@ -2191,11 +2161,10 @@ describe("Machine", () => { }).handle({ idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.loading(), - resolve: ({ event, state, target }) => + Submit: (to) => + to.full.loading().resolve(({ event, state, target }) => target(new Loading({ requestId: `${state.userId}:${event.value}` })) - }) + ) } } }) @@ -2228,18 +2197,12 @@ describe("Machine", () => { }).handle({ a: { on: { - Submit: Machine.transition({ - target: (to) => to.full.b(), - resolve: ({ event, target }) => target(new Duplicate({ value: event.value })) - }) + Submit: (to) => to.full.b().resolve(({ event, target }) => target(new Duplicate({ value: event.value }))) } }, b: { on: { - Reset: Machine.transition({ - target: (to) => to.full.a(), - resolve: ({ target }) => target(new Duplicate({ value: "reset" })) - }) + Reset: (to) => to.full.a().resolve(({ target }) => target(new Duplicate({ value: "reset" }))) } } }) @@ -2277,10 +2240,7 @@ describe("Machine", () => { }).handle({ a: { on: { - Submit: Machine.transition({ - target: (to) => to.full.b(), - resolve: ({ event, target }) => target(new Duplicate({ value: event.value })) - }) + Submit: (to) => to.full.b().resolve(({ event, target }) => target(new Duplicate({ value: event.value }))) } } }) @@ -2315,10 +2275,8 @@ describe("Machine", () => { }).handle({ idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.success(), - resolve: ({ event, target }) => target(new Success({ requestId: event.value })) - }) + Submit: (to) => + to.full.success().resolve(({ event, target }) => target(new Success({ requestId: event.value }))) } } }) @@ -2366,22 +2324,17 @@ describe("Machine", () => { }).handle({ payment: { on: { - Authorize: Machine.transition({ - target: (to) => to.full.failed(), - resolve: ({ target }) => target(new Failed({ message: "parent" })) - }) + Authorize: (to) => to.full.failed().resolve(({ target }) => target(new Failed({ message: "parent" }))) }, states: { entering: { on: { - Authorize: Machine.transition({ - target: (to) => to.local.authorized(), - resolve: ({ event, containingState, ancestors, target }) => { + Authorize: (to) => + to.local.authorized().resolve(({ event, containingState, ancestors, target }) => { assert.deepStrictEqual(containingState, payment) assert.deepStrictEqual(ancestors, { payment }) return target(new AuthorizedPayment({ code: event.code })) - } - }) + }) } } } @@ -2430,29 +2383,23 @@ describe("Machine", () => { }).handle({ payment: { on: { - Authorize: Machine.transition({ - target: (to) => to.full.failed(), - resolve: ({ target }) => target(new Failed({ message: "parent" })) - }) + Authorize: (to) => to.full.failed().resolve(({ target }) => target(new Failed({ message: "parent" }))) }, states: { entering: { on: { - Authorize: Machine.transition({ - declinable: true, - branches: (to) => ({ + Authorize: (to) => + to.branches({ authorize: { target: to.local.authorized() }, consume: { target: to.none() } - }), - resolve: ({ event, select, decline }, enqueue) => { + }).resolve(({ event, select, decline }, enqueue) => { if (event.code === "child") { return select.authorize(new AuthorizedPayment({ code: event.code })) } if (event.code === "consume") return select.consume() enqueue.emit(new Notice({})) return decline() - } - }) + }, { declinable: true }) } } } @@ -2505,17 +2452,11 @@ describe("Machine", () => { } }).handle({ workflow: { - always: Machine.transition({ - target: (to) => to.full.finished(), - resolve: ({ target }) => target(new Finished({})) - }), + always: (to) => to.full.finished().resolve(({ target }) => target(new Finished({}))), states: { waiting: { - always: Machine.transition({ - declinable: true, - target: (to) => to.none(), - resolve: ({ state, decline }) => state.ready ? undefined : decline() - }) + always: (to) => + to.none().resolve(({ state, decline }) => state.ready ? undefined : decline(), { declinable: true }) } } } @@ -2541,11 +2482,7 @@ describe("Machine", () => { }).handle({ Stable: { on: { - Ping: Machine.transition({ - declinable: true, - target: (to) => to.none(), - resolve: ({ decline }) => decline() - }) + Ping: (to) => to.none().resolve(({ decline }) => decline(), { declinable: true }) } } }) @@ -2581,11 +2518,7 @@ describe("Machine", () => { } }).handle({ workflow: { - onDone: Machine.transition({ - declinable: true, - target: (to) => to.full.finished(), - resolve: ({ decline }) => decline() - }), + onDone: (to) => to.full.finished().resolve(({ decline }) => decline(), { declinable: true }), states: { complete: { output: () => "complete" } } @@ -2626,31 +2559,24 @@ describe("Machine", () => { }).handle({ root: { on: { - Ping: Machine.transition({ - target: (to) => to.full.finished(), - resolve: ({ target }) => { + Ping: (to) => + to.full.finished().resolve(({ target }) => { parentCalls++ return target(new Finished({})) - } - }) + }) }, states: { left: { on: { - Ping: Machine.transition({ - declinable: true, - target: (to) => to.none(), - resolve: ({ decline }) => decline() - }) + Ping: (to) => to.none().resolve(({ decline }) => decline(), { declinable: true }) } }, right: { on: { - Ping: Machine.transition({ - declinable: true, - target: (to) => to.none(), - resolve: ({ event, decline }) => event.handleRight ? undefined : decline() - }) + Ping: (to) => + to.none().resolve(({ event, decline }) => event.handleRight ? undefined : decline(), { + declinable: true + }) } } } @@ -2704,18 +2630,15 @@ describe("Machine", () => { }).handle({ payment: { on: { - Reset: Machine.transition({ - target: (to) => to.full.failed(), - resolve: ({ target }) => target(new Failed({ message: "reset" })) - }) + Reset: (to) => to.full.failed().resolve(({ target }) => target(new Failed({ message: "reset" }))) }, states: { entering: { on: { - Authorize: Machine.transition({ - target: (to) => to.local.authorized(), - resolve: ({ event, target }) => target(new AuthorizedPayment({ code: event.code })) - }) + Authorize: (to) => + to.local.authorized().resolve(({ event, target }) => + target(new AuthorizedPayment({ code: event.code })) + ) } }, authorized: { @@ -2774,10 +2697,7 @@ describe("Machine", () => { }).handle({ payment: { on: { - Reset: Machine.transition({ - target: (to) => to.full.idle(), - resolve: ({ target }) => target(new Idle({ userId: "user-1" })) - }) + Reset: (to) => to.full.idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) } } }) @@ -2833,9 +2753,8 @@ describe("Machine", () => { }).handle({ idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.fulfillment(), - resolve: ({ event, target }) => + Submit: (to) => + to.full.fulfillment().resolve(({ event, target }) => target( new Fulfillment({ id: event.value }), (fulfillment) => @@ -2849,7 +2768,7 @@ describe("Machine", () => { (shipping) => shipping.quoted(new ShippingQuoted({ quoteId: event.value })) ) ) - }) + ) } } }) @@ -2937,9 +2856,8 @@ describe("Machine", () => { states: { idle: { on: { - Submit: Machine.transition({ - target: (to) => to.local.fulfillment(), - resolve: ({ event, target }) => + Submit: (to) => + to.local.fulfillment().resolve(({ event, target }) => target( new Fulfillment({ id: event.value }), (fulfillment) => @@ -2953,7 +2871,7 @@ describe("Machine", () => { (shipping) => shipping.quoted(new ShippingQuoted({ quoteId: event.value })) ) ) - }) + ) } } } @@ -3056,9 +2974,8 @@ describe("Machine", () => { states: { idle: { on: { - Submit: Machine.transition({ - target: (to) => to.branch.app.flow.fulfillment(), - resolve: ({ event, target }) => + Submit: (to) => + to.branch.app.flow.fulfillment().resolve(({ event, target }) => target( new Fulfillment({ id: event.value }), (fulfillment) => @@ -3066,7 +2983,7 @@ describe("Machine", () => { .inventory(new Inventory({ warehouse: "warehouse-1" })) .shipping(new Shipping({ address: "Main Street" })) ) - }) + ) } } } @@ -3160,11 +3077,10 @@ describe("Machine", () => { states: { checking: { on: { - ReserveInventory: Machine.transition({ - target: (to) => to.local.reserved(), - resolve: ({ event, target }) => + ReserveInventory: (to) => + to.local.reserved().resolve(({ event, target }) => target(new InventoryReserved({ reservationId: event.reservationId })) - }) + ) } } } @@ -3253,15 +3169,14 @@ describe("Machine", () => { states: { checking: { on: { - ReserveInventory: Machine.transition({ - target: (to) => to.branch.fulfillment.inventory(), - resolve: ({ event, target }) => + ReserveInventory: (to) => + to.branch.fulfillment.inventory().resolve(({ event, target }) => target( nextInventory, (inventory) => inventory.reserved(new InventoryReserved({ reservationId: event.reservationId })) ) - }) + ) } } } @@ -3351,15 +3266,14 @@ describe("Machine", () => { states: { checking: { on: { - ReserveInventory: Machine.transition({ - target: (to) => to.branch.fulfillment.inventory(), - resolve: ({ event, target }) => + ReserveInventory: (to) => + to.branch.fulfillment.inventory().resolve(({ event, target }) => target( nextInventory, (inventory) => inventory.reserved(new InventoryReserved({ reservationId: event.reservationId })) ) - }) + ) } } } @@ -3450,9 +3364,8 @@ describe("Machine", () => { states: { checking: { on: { - ReserveInventory: Machine.transition({ - target: (to) => to.branch.fulfillment(), - resolve: ({ event, target }) => + ReserveInventory: (to) => + to.branch.fulfillment().resolve(({ event, target }) => target( nextFulfillment, (fulfillment) => @@ -3462,7 +3375,7 @@ describe("Machine", () => { inventory.reserved(new InventoryReserved({ reservationId: event.reservationId })) ) ) - }) + ) } } } @@ -3545,14 +3458,13 @@ describe("Machine", () => { states: { checking: { on: { - ReserveInventory: Machine.transition({ - target: (to) => to.branch.payment.shipping(), - resolve: ({ event, target }) => + ReserveInventory: (to) => + to.branch.payment.shipping().resolve(({ event, target }) => target( shipping, (shipping) => shipping.quoted(new ShippingQuoted({ quoteId: event.reservationId })) ) - }) + ) } } } @@ -3611,18 +3523,15 @@ describe("Machine", () => { }).handle({ payment: { on: { - Reset: Machine.transition({ - target: (to) => to.local.entering(), - resolve: ({ target }) => target(new EnteringPayment({ amount: 0 })) - }) + Reset: (to) => to.local.entering().resolve(({ target }) => target(new EnteringPayment({ amount: 0 }))) }, states: { entering: { on: { - Authorize: Machine.transition({ - target: (to) => to.local.authorized(), - resolve: ({ event, target }) => target(new AuthorizedPayment({ code: event.code })) - }) + Authorize: (to) => + to.local.authorized().resolve(({ event, target }) => + target(new AuthorizedPayment({ code: event.code })) + ) } }, authorized: { @@ -3729,18 +3638,15 @@ describe("Machine", () => { }).handle({ payment: { on: { - Reset: Machine.transition({ - target: (to) => to.full.idle(), - resolve: ({ target }) => target(new Idle({ userId: "user-1" })) - }) + Reset: (to) => to.full.idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) }, states: { entering: { on: { - Authorize: Machine.transition({ - target: (to) => to.local.authorized(), - resolve: ({ event, target }) => target(new AuthorizedPayment({ code: event.code })) - }) + Authorize: (to) => + to.local.authorized().resolve(({ event, target }) => + target(new AuthorizedPayment({ code: event.code })) + ) } }, authorized: { @@ -3820,25 +3726,21 @@ describe("Machine", () => { }).handle({ checkout: { on: { - Reset: Machine.transition({ - target: (to) => to.full.failed(), - resolve: ({ target }) => target(new Failed({ message: "reset" })) - }) + Reset: (to) => to.full.failed().resolve(({ target }) => target(new Failed({ message: "reset" }))) }, states: { inventory: { - onDone: Machine.transition({ - target: (to) => to.branch.checkout.shipped(), - resolve: ({ output, target }) => target(new ShippingQuoted({ quoteId: String(output) })) - }), + onDone: (to) => + to.branch.checkout.shipped().resolve(({ output, target }) => + target(new ShippingQuoted({ quoteId: String(output) })) + ), states: { checking: { on: { - ReserveInventory: Machine.transition({ - target: (to) => to.local.reserved(), - resolve: ({ event, target }) => + ReserveInventory: (to) => + to.local.reserved().resolve(({ event, target }) => target(new InventoryReserved({ reservationId: event.reservationId })) - }) + ) } }, reserved: { @@ -3933,11 +3835,10 @@ describe("Machine", () => { states: { checking: { on: { - ReserveInventory: Machine.transition({ - target: (to) => to.local.reserved(), - resolve: ({ event, target }) => + ReserveInventory: (to) => + to.local.reserved().resolve(({ event, target }) => target(new InventoryReserved({ reservationId: event.reservationId })) - }) + ) } } } @@ -4058,11 +3959,10 @@ describe("Machine", () => { states: { checking: { on: { - ReserveInventory: Machine.transition({ - target: (to) => to.local.reserved(), - resolve: ({ event, target }) => + ReserveInventory: (to) => + to.local.reserved().resolve(({ event, target }) => target(new InventoryReserved({ reservationId: event.reservationId })) - }) + ) } }, reserved: { @@ -4074,10 +3974,10 @@ describe("Machine", () => { states: { quoting: { on: { - ReserveInventory: Machine.transition({ - target: (to) => to.local.quoted(), - resolve: ({ event, target }) => target(new ShippingQuoted({ quoteId: event.reservationId })) - }) + ReserveInventory: (to) => + to.local.quoted().resolve(({ event, target }) => + target(new ShippingQuoted({ quoteId: event.reservationId })) + ) } }, quoted: { @@ -4203,11 +4103,10 @@ describe("Machine", () => { states: { checking: { on: { - ReserveInventory: Machine.transition({ - target: (to) => to.local.reserved(), - resolve: ({ event, target }) => + ReserveInventory: (to) => + to.local.reserved().resolve(({ event, target }) => target(new InventoryReserved({ reservationId: event.reservationId })) - }) + ) } }, reserved: { @@ -4219,10 +4118,8 @@ describe("Machine", () => { states: { quoting: { on: { - Resolve: Machine.transition({ - target: (to) => to.local.quoted(), - resolve: ({ target }) => target(new ShippingQuoted({ quoteId: "quote-1" })) - }) + Resolve: (to) => + to.local.quoted().resolve(({ target }) => target(new ShippingQuoted({ quoteId: "quote-1" }))) } }, quoted: { @@ -4334,11 +4231,10 @@ describe("Machine", () => { states: { checking: { on: { - ReserveInventory: Machine.transition({ - target: (to) => to.local.reserved(), - resolve: ({ event, target }) => + ReserveInventory: (to) => + to.local.reserved().resolve(({ event, target }) => target(new InventoryReserved({ reservationId: event.reservationId })) - }) + ) } } } @@ -4347,10 +4243,10 @@ describe("Machine", () => { states: { quoting: { on: { - ReserveInventory: Machine.transition({ - target: (to) => to.local.quoted(), - resolve: ({ event, target }) => target(new ShippingQuoted({ quoteId: event.reservationId })) - }) + ReserveInventory: (to) => + to.local.quoted().resolve(({ event, target }) => + target(new ShippingQuoted({ quoteId: event.reservationId })) + ) } } } @@ -4447,17 +4343,15 @@ describe("Machine", () => { states: { checking: { on: { - ReserveInventory: Machine.transition({ - target: (to) => to.local.reserved(), - resolve: ({ event, target }, enqueue) => { + ReserveInventory: (to) => + to.local.reserved().resolve(({ event, target }, enqueue) => { enqueue.raise(new Resolve({})) return target( new InventoryReserved({ reservationId: event.reservationId }) ) - } - }) + }) } } } @@ -4466,10 +4360,8 @@ describe("Machine", () => { states: { quoting: { on: { - Resolve: Machine.transition({ - target: (to) => to.local.quoted(), - resolve: ({ target }) => target(new ShippingQuoted({ quoteId: "raised" })) - }) + Resolve: (to) => + to.local.quoted().resolve(({ target }) => target(new ShippingQuoted({ quoteId: "raised" }))) } } } @@ -4531,10 +4423,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } } }) @@ -4565,18 +4454,12 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } }, Loading: { on: { - Reset: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ target }) => target(new Idle({ userId: "user-1" })) - }) + Reset: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) } } }) @@ -4603,10 +4486,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ target }) => target(new Success({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))) } }, Success: {} @@ -4631,10 +4511,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ target }) => target(new Success({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))) } }, Success: { @@ -4667,10 +4544,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ target }) => target(new Success({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))) } }, Success: { @@ -4770,10 +4644,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ target }) => target(new Success({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))) } }, Success: {} @@ -4810,14 +4681,8 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ target }) => target(new Success({ requestId: "request-1" })) - }), - Reset: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ target }) => target(new Idle({ userId: "user-2" })) - }) + Submit: (to) => to.full.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))), + Reset: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({ userId: "user-2" }))) } }, Success: {} @@ -4851,10 +4716,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } } }) @@ -4885,10 +4747,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } } }) @@ -4944,10 +4803,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + Submit: { target: Machine.targetless } } } }) @@ -4973,10 +4829,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } } }) @@ -5023,10 +4876,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ target }) => target(new Success({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))) } }, Success: { @@ -5064,10 +4914,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } } }) @@ -5095,20 +4942,15 @@ describe("Machine", () => { const machine = definition.handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } }, Loading: { - invoke: definition.invoke({ + invoke: Machine.invoke({ id: "request", effect: () => Effect.succeed("done:request-1"), - onDone: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ output, target }) => target(new Success({ requestId: output })) - }) + onDone: (to) => + to.full.Success().resolve(({ output, target }) => target(new Success({ requestId: output }))) }) }, Success: { @@ -5146,20 +4988,15 @@ describe("Machine", () => { const machine = definition.handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } }, Loading: { - invoke: definition.invoke({ + invoke: Machine.invoke({ id: "request", effect: () => Effect.succeed("done:request-1"), - onDone: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ output, target }) => target(new Success({ requestId: output })) - }) + onDone: (to) => + to.full.Success().resolve(({ output, target }) => target(new Success({ requestId: output }))) }) }, Success: { @@ -5360,10 +5197,8 @@ describe("Machine", () => { Loading: { invoke: Machine.invoke({ child: Child, - onDone: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ output, target }) => target(new Success({ requestId: output })) - }) + onDone: (to) => + to.full.Success().resolve(({ output, target }) => target(new Success({ requestId: output }))) }) }, Success: { output: ({ state }) => state.requestId } @@ -5498,20 +5333,15 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } }, Loading: { invoke: Machine.invoke({ id: "request", effect: () => Effect.fail(error), - onFailure: Machine.transition({ - target: (to) => to.full.Failed(), - resolve: ({ error, target }) => target(new Failed({ message: error.message })) - }) + onFailure: (to) => + to.full.Failed().resolve(({ error, target }) => target(new Failed({ message: error.message }))) }) }, Failed: { @@ -5548,20 +5378,15 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } }, Loading: { invoke: Machine.invoke({ id: "request", effect: () => Effect.succeed("loaded"), - onDone: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ output, target }) => target(new Success({ requestId: output })) - }) + onDone: (to) => + to.full.Success().resolve(({ output, target }) => target(new Success({ requestId: output }))) }) }, Success: { @@ -5600,16 +5425,11 @@ describe("Machine", () => { Effect.andThen(Effect.never) ) }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onFailure: { target: Machine.targetless } }), on: { - RequestSucceeded: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ event, target }) => target(new Success({ requestId: event.value })) - }) + RequestSucceeded: (to) => + to.full.Success().resolve(({ event, target }) => target(new Success({ requestId: event.value }))) } }, Success: { @@ -5636,10 +5456,8 @@ describe("Machine", () => { }).handle({ Idle: { on: { - RequestSucceeded: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ event, target }) => target(new Success({ requestId: event.value })) - }) + RequestSucceeded: (to) => + to.full.Success().resolve(({ event, target }) => target(new Success({ requestId: event.value }))) } }, Loading: { @@ -5656,16 +5474,10 @@ describe("Machine", () => { Effect.onInterrupt(() => sendTo(parent, new RequestSucceeded({ value: "stale" }))) ) }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onFailure: { target: Machine.targetless } }), on: { - Resolve: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ target }) => target(new Idle({ userId: "resolved" })) - }) + Resolve: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({ userId: "resolved" }))) } }, Success: { @@ -5705,10 +5517,8 @@ describe("Machine", () => { invoke: Machine.invoke({ id: "request", effect: () => Effect.fail(failure), - onFailure: Machine.transition({ - target: (to) => to.full.Failed(), - resolve: ({ error, target }) => target(new Failed({ message: error.message })) - }) + onFailure: (to) => + to.full.Failed().resolve(({ error, target }) => target(new Failed({ message: error.message }))) }) }, Failed: { @@ -5739,10 +5549,8 @@ describe("Machine", () => { invoke: Machine.invoke({ id: "request", effect: () => requiredMessage, - onDone: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ output, target }) => target(new Success({ requestId: output })) - }) + onDone: (to) => + to.full.Success().resolve(({ output, target }) => target(new Success({ requestId: output }))) }) }, Success: { @@ -5775,10 +5583,7 @@ describe("Machine", () => { invoke: Machine.invoke({ id: "timeout", after: "1 hour", - onDone: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ target }) => target(new Success({ requestId: "timeout" })) - }) + onDone: (to) => to.full.Success().resolve(({ target }) => target(new Success({ requestId: "timeout" }))) }) }, Success: { @@ -5820,17 +5625,14 @@ describe("Machine", () => { Machine.Machine.InputEvents, Machine.Machine.ParentEvents > - > = Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ id, snapshot, target }) => target(new Success({ requestId: `${id}:${snapshot.state}` })) - }) + > = (to) => + to.full.Success().resolve(({ id, snapshot, target }) => + target(new Success({ requestId: `${id}:${snapshot.state}` })) + ) const machine = definition.handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } }, Loading: { @@ -5876,10 +5678,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } }, Loading: { @@ -5934,23 +5733,19 @@ describe("Machine", () => { Machine.Machine.InputEvents, Machine.Machine.ParentEvents > - > = Machine.transition({ - branches: (to) => ({ + > = (to) => + to.branches({ ready: { title: "Request is ready", target: to.full.Success() }, unchanged: { target: to.none() } - }), - resolve: ({ snapshot, select }) => + }).resolve(({ snapshot, select }) => snapshot.state === "ready" ? select.ready(new Success({ requestId: snapshot.state })) : select.unchanged() - }) + ) const machine = definition.handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } }, Loading: { @@ -6012,10 +5807,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } }, Loading: { @@ -6026,10 +5818,7 @@ describe("Machine", () => { initial: "pending", run: () => Effect.void }), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }) } }) @@ -6076,10 +5865,7 @@ describe("Machine", () => { }).handle({ Idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } }, Loading: { @@ -6089,14 +5875,9 @@ describe("Machine", () => { logic: childLogic }), on: { - Resolve: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ target }) => target(new Success({ requestId: "request-1" })) - }), - RequestSucceeded: Machine.transition({ - target: (to) => to.full.Success(), - resolve: ({ event, target }) => target(new Success({ requestId: event.value })) - }) + Resolve: (to) => to.full.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))), + RequestSucceeded: (to) => + to.full.Success().resolve(({ event, target }) => target(new Success({ requestId: event.value }))) } }, Success: { @@ -6194,10 +5975,10 @@ describe("Machine", () => { logic: makeInvokeLogic("entering", enteringStarted) }), on: { - Authorize: Machine.transition({ - target: (to) => to.local.authorized(), - resolve: ({ event, target }) => target(new AuthorizedPayment({ code: event.code })) - }) + Authorize: (to) => + to.local.authorized().resolve(({ event, target }) => + target(new AuthorizedPayment({ code: event.code })) + ) } }, authorized: { @@ -6330,10 +6111,8 @@ describe("Machine", () => { states: { checking: { on: { - ReserveInventory: Machine.transition({ - target: (to) => to.full.success(), - resolve: ({ target }) => target(new Success({ requestId: "done" })) - }) + ReserveInventory: (to) => + to.full.success().resolve(({ target }) => target(new Success({ requestId: "done" }))) } } } @@ -6450,22 +6229,13 @@ describe("Machine", () => { } }).handle({ Idle: { - always: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }), + always: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))), on: { - Submit: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) } }, Loading: { - always: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ target }) => target(new Idle({ userId: "user-1" })) - }) + always: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) } }) @@ -6491,16 +6261,10 @@ describe("Machine", () => { } }).handle({ Idle: { - always: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({ requestId: "request-1" })) - }) + always: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) }, Loading: { - always: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ target }) => target(new Idle({ userId: "user-1" })) - }) + always: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) } }) @@ -6541,25 +6305,23 @@ describe("Machine", () => { }).handle({ idle: { on: { - Submit: Machine.transition({ - target: (to) => to.full.flow(), - resolve: ({ target }) => + Submit: (to) => + to.full.flow().resolve(({ target }) => target( new Loading({ requestId: "request-1" }), (flow) => flow.done(new Success({ requestId: "request-1" })) ) - }) + ) } }, flow: { - onDone: Machine.transition({ - target: (to) => to.full.flow(), - resolve: ({ state, target }) => + onDone: (to) => + to.full.flow().resolve(({ state, target }) => target( state, (flow) => flow.done(new Success({ requestId: state.requestId })) ) - }) + ) } }) @@ -6620,18 +6382,18 @@ describe("Machine", () => { states: { left: { on: { - AdvanceCounters: Machine.transition({ - target: (to) => to.branch.running.left(), - resolve: ({ state, target }) => target(new LeftCounter({ value: state.value + 1 })) - }) + AdvanceCounters: (to) => + to.branch.running.left().resolve(({ state, target }) => + target(new LeftCounter({ value: state.value + 1 })) + ) } }, right: { on: { - AdvanceCounters: Machine.transition({ - target: (to) => to.branch.running.right(), - resolve: ({ state, target }) => target(new RightCounter({ value: state.value + 1 })) - }) + AdvanceCounters: (to) => + to.branch.running.right().resolve(({ state, target }) => + target(new RightCounter({ value: state.value + 1 })) + ) } } } @@ -6650,10 +6412,7 @@ describe("Machine", () => { }).handle({ ConcurrentIdle: { on: { - ConcurrentPing: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + ConcurrentPing: { target: Machine.targetless } } } }) diff --git a/test/machine/MachineReferences.test.ts b/test/machine/MachineReferences.test.ts index 85919ce..13674d4 100644 --- a/test/machine/MachineReferences.test.ts +++ b/test/machine/MachineReferences.test.ts @@ -122,13 +122,11 @@ describe("machine reference event channels", () => { }).handle({ Idle: { on: { - Publish: Machine.transition({ - target: (to) => to.none(), - resolve: ({ event }, enqueue) => { + Publish: (to) => + to.none().resolve(({ event }, enqueue) => { enqueue.emit(Emissions.Published({ value: event.value })) return undefined - } - }) + }) } } }) @@ -178,13 +176,11 @@ describe("machine reference event channels", () => { }).handle({ Idle: { on: { - Publish: Machine.transition({ - target: (to) => to.none(), - resolve: (_, enqueue) => { + Publish: (to) => + to.none().resolve((_, enqueue) => { enqueue.emit(Emissions.Published({ value: "invalid" } as never)) return undefined - } - }) + }) } } }) @@ -237,17 +233,15 @@ describe("machine reference event channels", () => { }).handle({ Waiting: { on: { - Trigger: Machine.transition({ - target: (to) => to.full.Reported(), - resolve: ({ parent, target }, enqueue) => { + Trigger: (to) => + to.full.Reported().resolve(({ parent, target }, enqueue) => { rootHadParent = parent !== undefined enqueue.emit(ChildEmissions.Notice({ value: 1 })) if (parent !== undefined) { enqueue.sendTo(parent, ParentEvents.ChildReported({ value: 1 })) } return target(new Reported({})) - } - }) + }) } }, Reported: {} @@ -281,14 +275,9 @@ describe("machine reference event channels", () => { Awaiting: { invoke: Machine.invoke({ child: Child }), on: { - ChildReported: Machine.transition({ - target: (to) => to.full.Finished(), - resolve: ({ target }) => target(new Finished({ source: "parent event" })) - }), - Notice: Machine.transition({ - target: (to) => to.full.Finished(), - resolve: ({ target }) => target(new Finished({ source: "emission" })) - }) + ChildReported: (to) => + to.full.Finished().resolve(({ target }) => target(new Finished({ source: "parent event" }))), + Notice: (to) => to.full.Finished().resolve(({ target }) => target(new Finished({ source: "emission" }))) } }, Finished: { output: ({ state }) => state.source } @@ -304,7 +293,7 @@ describe("machine reference event channels", () => { assert.strictEqual(yield* parent.join, "parent event") })) - it.effect("types and delivers parent input from a machine-bound invocation source", () => + it.effect("types and delivers parent input from a Machine.invoke source", () => Effect.gen(function*() { class ChildIdle extends Schema.TaggedClass("BoundInvokeChildIdle")("ChildIdle", {}) {} class ParentWaiting extends Schema.TaggedClass("BoundInvokeParentWaiting")( @@ -327,17 +316,11 @@ describe("machine reference event channels", () => { }) const childMachine = childDefinition.handle({ ChildIdle: { - invoke: childDefinition.invoke({ + invoke: Machine.invoke({ id: "notify-ready", effect: ({ parent }) => parent === undefined ? Effect.void : parent.send(ParentEvents.ChildReady()), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless }, + onFailure: { target: Machine.targetless } }) } }) @@ -357,16 +340,10 @@ describe("machine reference event channels", () => { ParentWaiting: { invoke: Machine.invoke({ child: Child, - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onFailure: { target: Machine.targetless } }), on: { - ChildReady: Machine.transition({ - target: (to) => to.full.ParentDone(), - resolve: ({ target }) => target(new ParentDone({})) - }) + ChildReady: (to) => to.full.ParentDone().resolve(({ target }) => target(new ParentDone({}))) } }, ParentDone: { output: () => undefined } diff --git a/test/machine/Resume.test.ts b/test/machine/Resume.test.ts index b8dbb1d..2713c9d 100644 --- a/test/machine/Resume.test.ts +++ b/test/machine/Resume.test.ts @@ -196,10 +196,7 @@ describe("Machine.resume", () => { states: { A: { on: { - Advance: Machine.transition({ - target: (to) => to.local.B(), - resolve: ({ target }) => target(new LeftB({})) - }) + Advance: (to) => to.local.B().resolve(({ target }) => target(new LeftB({}))) } } } @@ -208,10 +205,7 @@ describe("Machine.resume", () => { states: { A: { on: { - Advance: Machine.transition({ - target: (to) => to.local.B(), - resolve: ({ target }) => target(new RightB({})) - }) + Advance: (to) => to.local.B().resolve(({ target }) => target(new RightB({}))) } } } @@ -263,10 +257,7 @@ describe("Machine.resume", () => { }).handle({ Count: { on: { - Finish: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ target }) => target(new Done({ value: 9 })) - }) + Finish: (to) => to.full.Done().resolve(({ target }) => target(new Done({ value: 9 }))) } }, Done: { output: ({ state }) => state.value } @@ -309,10 +300,7 @@ describe("Machine.resume", () => { }) .handle({ Flow: { - onDone: Machine.transition({ - target: (to) => to.full.Next(), - resolve: ({ target }) => target(new Next({})) - }) + onDone: (to) => to.full.Next().resolve(({ target }) => target(new Next({}))) }, Next: {} }) @@ -340,10 +328,7 @@ describe("Machine.resume", () => { } }).handle({ A: { - always: Machine.transition({ - target: (to) => to.full.B(), - resolve: ({ target }) => target(new B({})) - }) + always: (to) => to.full.B().resolve(({ target }) => target(new B({}))) }, B: {} }) @@ -375,16 +360,10 @@ describe("Machine.resume", () => { invoke: Machine.invoke({ id: "timeout", after: "1 second", - onDone: Machine.transition({ - target: (to) => to.full.TimedOut(), - resolve: ({ target }) => target(new TimedOut({})) - }) + onDone: (to) => to.full.TimedOut().resolve(({ target }) => target(new TimedOut({}))) }), on: { - Cancel: Machine.transition({ - target: (to) => to.full.Cancelled(), - resolve: ({ target }) => target(new Cancelled({})) - }) + Cancel: (to) => to.full.Cancelled().resolve(({ target }) => target(new Cancelled({}))) } }, Cancelled: {}, @@ -427,10 +406,7 @@ describe("Machine.resume", () => { invoke: Machine.invoke({ id: "load", effect: () => Ref.updateAndGet(runs, (n) => n + 1).pipe(Effect.as("fresh")), - onDone: Machine.transition({ - target: (to) => to.full.Loaded(), - resolve: ({ output, target }) => target(new Loaded({ value: output })) - }) + onDone: (to) => to.full.Loaded().resolve(({ output, target }) => target(new Loaded({ value: output }))) }) }, Loaded: {} @@ -471,10 +447,8 @@ describe("Machine.resume", () => { Ref.update(runs, (n) => n + 1).pipe( Effect.andThen(Effect.fail(new LoadFailure({ message: "offline" }))) ), - onFailure: Machine.transition({ - target: (to) => to.full.Failed(), - resolve: ({ error, target }) => target(new Failed({ message: error.message })) - }) + onFailure: (to) => + to.full.Failed().resolve(({ error, target }) => target(new Failed({ message: error.message }))) }) }, Failed: {} @@ -510,10 +484,8 @@ describe("Machine.resume", () => { }).handle({ ChildIdle: { on: { - ChildFinish: Machine.transition({ - target: (to) => to.full.ChildDone(), - resolve: ({ state, target }) => target(new ChildDone({ value: state.value + 1 })) - }) + ChildFinish: (to) => + to.full.ChildDone().resolve(({ state, target }) => target(new ChildDone({ value: state.value + 1 }))) } }, ChildDone: { output: ({ state }) => state.value } @@ -531,10 +503,8 @@ describe("Machine.resume", () => { Parent: { invoke: Machine.invoke({ child: Child, - onDone: Machine.transition({ - target: (to) => to.full.ChildOutput(), - resolve: ({ output, target }) => target(new ChildOutput({ value: output })) - }) + onDone: (to) => + to.full.ChildOutput().resolve(({ output, target }) => target(new ChildOutput({ value: output }))) }) }, ChildOutput: {} @@ -635,10 +605,10 @@ describe("Machine.resume", () => { }).handle({ Count: { on: { - Add: Machine.transition({ - target: (to) => to.full.Count(), - resolve: ({ event, state, target }) => target(new Count({ value: state.value + event.value })) - }) + Add: (to) => + to.full.Count().resolve(({ event, state, target }) => + target(new Count({ value: state.value + event.value })) + ) } } }) diff --git a/test/machine/RuntimeDifferential.test.ts b/test/machine/RuntimeDifferential.test.ts index 838485a..3044cbb 100644 --- a/test/machine/RuntimeDifferential.test.ts +++ b/test/machine/RuntimeDifferential.test.ts @@ -61,21 +61,14 @@ describe("pure planning and managed runtime differential", () => { }).handle({ Count: { on: { - Cascade: Machine.transition({ - target: (to) => to.none(), - resolve: (_, enqueue) => { + Cascade: (to) => + to.none().resolve((_, enqueue) => { enqueue.raise(new Increment({})) return undefined - } - }), - Increment: Machine.transition({ - target: (to) => to.full.Count(), - resolve: ({ state, target }) => target(new Count({ value: state.value + 1 })) - }), - Finish: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ state, target }) => target(new Done({ value: state.value })) - }) + }), + Increment: (to) => + to.full.Count().resolve(({ state, target }) => target(new Count({ value: state.value + 1 }))), + Finish: (to) => to.full.Done().resolve(({ state, target }) => target(new Done({ value: state.value }))) } }, Done: { output: ({ state }) => state.value } @@ -163,43 +156,38 @@ describe("pure planning and managed runtime differential", () => { }).handle({ Running: { on: { - Finish: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ snapshot, target }) => { + Finish: (to) => + to.full.Done().resolve(({ snapshot, target }) => { if (snapshot.path !== "Running") throw new Error("expected Running snapshot") return target( new Done({ value: snapshot.states.Left.value.value + snapshot.states.Right.value.value }) ) - } - }) + }) }, states: { Left: { on: { - Advance: Machine.transition({ - target: (to) => to.branch.Running.Left(), - resolve: ({ state, target }, enqueue) => { + Advance: (to) => + to.branch.Running.Left().resolve(({ state, target }, enqueue) => { enqueue.raise(new Bump({})) return target(new Left({ value: state.value + 1 })) - } - }) + }) } }, Right: { on: { - Advance: Machine.transition({ - target: (to) => to.branch.Running.Right(), - resolve: ({ state, target }) => target(new Right({ value: state.value + 10 })) - }), - Bump: Machine.transition({ - target: (to) => to.branch.Running.Right(), - resolve: ({ state, target }) => target(new Right({ value: state.value + 100 })) - }), - Inspect: Machine.transition({ - target: (to) => to.none(), - resolve: (context) => { + Advance: (to) => + to.branch.Running.Right().resolve(({ state, target }) => + target(new Right({ value: state.value + 10 })) + ), + Bump: (to) => + to.branch.Running.Right().resolve(({ state, target }) => + target(new Right({ value: state.value + 100 })) + ), + Inspect: (to) => + to.none().resolve((context) => { const { state, containingState, ancestors, snapshot } = context if (snapshot.path !== "Running") throw new Error("expected Running snapshot") const expectedKeys = [ @@ -229,8 +217,7 @@ describe("pure planning and managed runtime differential", () => { right: snapshot.states.Right.value.value }) return undefined - } - }) + }) } } } @@ -478,15 +465,13 @@ describe("pure planning and managed runtime differential", () => { enqueue.emit(new Notice({ label: "initial" })) }, on: { - Begin: Machine.transition({ - target: (to) => to.full.Working(), - resolve: ({ target }, enqueue) => { + Begin: (to) => + to.full.Working().resolve(({ target }, enqueue) => { record("transition:begin") enqueue.emit(new Notice({ label: "transition" })) enqueue.raise(new RaisedOne({})) return target(new Working({})) - } - }) + }) } }, Working: { @@ -496,22 +481,18 @@ describe("pure planning and managed runtime differential", () => { enqueue.raise(new RaisedTwo({})) }, on: { - RaisedOne: Machine.transition({ - target: (to) => to.none(), - resolve: (_, enqueue) => { + RaisedOne: (to) => + to.none().resolve((_, enqueue) => { record("raised:one") enqueue.emit(new Notice({ label: "raised-one" })) return undefined - } - }), - RaisedTwo: Machine.transition({ - target: (to) => to.full.Finished(), - resolve: ({ target }, enqueue) => { + }), + RaisedTwo: (to) => + to.full.Finished().resolve(({ target }, enqueue) => { record("raised:two") enqueue.emit(new Notice({ label: "raised-two" })) return target(new Finished({})) - } - }) + }) } }, Finished: { @@ -596,10 +577,7 @@ describe("pure planning and managed runtime differential", () => { }).handle({ Idle: { on: { - Go: Machine.transition({ - target: (to) => to.full.Active(), - resolve: ({ target }) => target(new Active({})) - }) + Go: (to) => to.full.Active().resolve(({ target }) => target(new Active({}))) } }, Active: {} diff --git a/test/machine/Scheduling.test.ts b/test/machine/Scheduling.test.ts index 02774a9..da4ea84 100644 --- a/test/machine/Scheduling.test.ts +++ b/test/machine/Scheduling.test.ts @@ -28,21 +28,17 @@ describe("machine scheduling", () => { }).handle({ SchedulingActive: { on: { - StartBurst: Machine.transition({ - target: (to) => to.full.SchedulingActive(), - resolve: ({ state, target }, enqueue) => { + StartBurst: (to) => + to.full.SchedulingActive().resolve(({ state, target }, enqueue) => { enqueue.raise(new Burst({})) return target(state) - } - }), - Burst: Machine.transition({ - target: (to) => to.full.SchedulingActive(), - resolve: ({ state, target }, enqueue) => { + }), + Burst: (to) => + to.full.SchedulingActive().resolve(({ state, target }, enqueue) => { const count = state.count + 1 if (count < burstSize) enqueue.raise(new Burst({})) return target(new SchedulingActive({ count })) - } - }) + }) } } }) diff --git a/test/machine/SnapshotCodecAdversarial.test.ts b/test/machine/SnapshotCodecAdversarial.test.ts index 7cfc920..2fe3d0b 100644 --- a/test/machine/SnapshotCodecAdversarial.test.ts +++ b/test/machine/SnapshotCodecAdversarial.test.ts @@ -248,10 +248,7 @@ describe("snapshot codec adversarial boundaries", () => { } }).handle({ Before: { - always: Machine.transition({ - target: (to) => to.full.Boundary(), - resolve: ({ target }) => target(new Boundary({})) - }) + always: (to) => to.full.Boundary().resolve(({ target }) => target(new Boundary({}))) }, Boundary: {}, After: {} @@ -267,10 +264,7 @@ describe("snapshot codec adversarial boundaries", () => { }).handle({ Before: {}, Boundary: { - always: Machine.transition({ - target: (to) => to.full.After(), - resolve: ({ target }) => target(new After({})) - }) + always: (to) => to.full.After().resolve(({ target }) => target(new After({}))) }, After: {} }) diff --git a/test/machine/SnapshotContext.test.ts b/test/machine/SnapshotContext.test.ts index a4a7c52..921661f 100644 --- a/test/machine/SnapshotContext.test.ts +++ b/test/machine/SnapshotContext.test.ts @@ -68,18 +68,16 @@ describe("Machine transition snapshot context", () => { states: { Buffering: { on: { - BufferReady: Machine.transition({ - branches: (to) => ({ + BufferReady: (to) => + to.branches({ online: { title: "Network is online", target: to.local.Playing() }, unchanged: { target: to.none() } - }), - resolve: ({ snapshot, select }) => { + }).resolve(({ snapshot, select }) => { captured = snapshot return States.matches(snapshot, "System.Network.Online") ? select.online(new Playing({})) : select.unchanged() - } - }) + }) } } } @@ -113,13 +111,11 @@ describe("Machine transition snapshot context", () => { states: { Buffering: { on: { - Disconnect: Machine.transition({ - target: (to) => to.local.Playing(), - resolve: ({ snapshot, target }) => { + Disconnect: (to) => + to.local.Playing().resolve(({ snapshot, target }) => { captured.push(snapshot) return target(new Playing({})) - } - }) + }) } } } @@ -128,13 +124,11 @@ describe("Machine transition snapshot context", () => { states: { Online: { on: { - Disconnect: Machine.transition({ - target: (to) => to.local.Offline(), - resolve: ({ snapshot, target }) => { + Disconnect: (to) => + to.local.Offline().resolve(({ snapshot, target }) => { captured.push(snapshot) return target(new Offline({})) - } - }) + }) } } } @@ -167,18 +161,16 @@ describe("Machine transition snapshot context", () => { Playback: { states: { Buffering: { - always: Machine.transition({ - branches: (to) => ({ + always: (to) => + to.branches({ online: { title: "Network is online", target: to.local.Playing() }, unchanged: { target: to.none() } - }), - resolve: ({ snapshot, select }) => { + }).resolve(({ snapshot, select }) => { captured = snapshot return States.matches(snapshot, "System.Network.Online") ? select.online(new Playing({})) : select.unchanged() - } - }) + }) } } } @@ -240,13 +232,11 @@ describe("Machine transition snapshot context", () => { System: { states: { Work: { - onDone: Machine.transition({ - target: (to) => to.local.Restarted(), - resolve: ({ snapshot, target }) => { + onDone: (to) => + to.local.Restarted().resolve(({ snapshot, target }) => { captured = snapshot return target(new Restarted({})) - } - }) + }) } } } diff --git a/test/machine/StructuralStates.test.ts b/test/machine/StructuralStates.test.ts index 8d733c5..9bf9596 100644 --- a/test/machine/StructuralStates.test.ts +++ b/test/machine/StructuralStates.test.ts @@ -89,48 +89,42 @@ const makeMachine = () => states: { Empty: { on: { - SourceSelected: Machine.transition({ - target: (to) => to.local.Loading(), - resolve: ({ event, state, target }) => { + SourceSelected: (to) => + to.local.Loading().resolve(({ event, state, target }) => { assert.strictEqual(state, undefined) return target.from({ url: event.url }) - } - }) + }) } }, Loading: { on: { - Loaded: Machine.transition({ - target: (to) => to.local.Ready(), - resolve: ({ event, state, target }) => { + Loaded: (to) => + to.local.Ready().resolve(({ event, state, target }) => { assert.strictEqual(state._tag, "Loading") return target.from( { duration: event.duration }, (ready) => ready.Paused.from() ) - } - }) + }) } }, Ready: { states: { Paused: { on: { - Play: Machine.transition({ - target: (to) => to.local.Playing(), - resolve: ({ containingState, state, target }) => { + Play: (to) => + to.local.Playing().resolve(({ containingState, state, target }) => { assert.strictEqual(state, undefined) return target.from({ position: Math.min(0, containingState.duration) }) - } - }) + }) } }, Playing: { on: { - Mute: Machine.transition({ - target: (to) => to.branch.player.settings.Muted(), - resolve: ({ event, target }) => target.from({ volume: event.volume }) - }) + Mute: (to) => + to.branch.player.settings.Muted().resolve(({ event, target }) => + target.from({ volume: event.volume }) + ) } } } @@ -183,20 +177,14 @@ const historyMachine = Machine.make({ exact: { default: historyFallback } }, on: { - Leave: Machine.transition({ - target: (to) => to.full.away(), - resolve: ({ target }) => target.from() - }) + Leave: (to) => to.full.away().resolve(({ target }) => target.from()) }, states: { section: { states: { Idle: { on: { - Edit: Machine.transition({ - target: (to) => to.local.Editing(), - resolve: ({ event, target }) => target.from({ draft: event.draft }) - }) + Edit: (to) => to.local.Editing().resolve(({ event, target }) => target.from({ draft: event.draft })) } } } @@ -205,14 +193,8 @@ const historyMachine = Machine.make({ }, away: { on: { - ResumeShallow: Machine.transition({ - target: (to) => to.history.flow.recent(), - resolve: ({ target }) => target() - }), - ResumeDeep: Machine.transition({ - target: (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()) } } }) diff --git a/test/machine/Totality.test.ts b/test/machine/Totality.test.ts index 30549ff..879c8b4 100644 --- a/test/machine/Totality.test.ts +++ b/test/machine/Totality.test.ts @@ -264,10 +264,7 @@ describe("machine operation totality", () => { }).handle({ Value: { on: { - Finish: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ target }) => target({ _tag: "Done" }) - }) + Finish: (to) => to.full.Done().resolve(({ target }) => target({ _tag: "Done" })) } }, Done: { output: () => 42 } diff --git a/test/machine/Visualization.test.ts b/test/machine/Visualization.test.ts index d6e6d13..982bc9f 100644 --- a/test/machine/Visualization.test.ts +++ b/test/machine/Visualization.test.ts @@ -100,15 +100,15 @@ const makeMachine = (unsafeStart = false) => idle: { on: { Start: unsafeStart ? - Machine.transition({ - target: (to) => to.full.disabled(), - resolve: ({ target }) => ({ ...target(new Disabled({})), path: "application.workflow.idle" }) as any - }) : - Machine.transition({ - target: (to) => to.local.running(), - resolve: ({ target }) => target(new Running({}), (running) => running.editing(new Editing({}))) - }), - Refresh: Machine.transition({ target: (to) => to.none(), resolve: () => undefined }) + (to) => + to.full.disabled().resolve(({ target }) => + ({ ...target(new Disabled({})), path: "application.workflow.idle" }) as any + ) : + (to) => + to.local.running().resolve(({ target }) => + target(new Running({}), (running) => running.editing(new Editing({}))) + ), + Refresh: { target: Machine.targetless } } }, running: { @@ -120,10 +120,7 @@ const makeMachine = (unsafeStart = false) => states: { online: { on: { - Disconnect: Machine.transition({ - target: (to) => to.local.offline(), - resolve: ({ target }) => target(new Offline({})) - }) + Disconnect: (to) => to.local.offline().resolve(({ target }) => target(new Offline({}))) } } } @@ -165,22 +162,18 @@ const lifecycleDefinition = Machine.make({ const makeLifecycleMachine = (unsafe: "always" | "done" | undefined = undefined) => lifecycleDefinition.handle({ idle: { - always: Machine.transition({ - target: (to) => to.full.workflow(), - resolve: ({ target }) => { + always: (to) => + to.full.workflow().resolve(({ target }) => { const selected = target(new Workflow({}), (workflow) => workflow.complete(new Complete({}))) return unsafe === "always" ? ({ ...selected, path: "idle" } as any) : selected - } - }) + }) }, workflow: { - onDone: Machine.transition({ - target: (to) => to.full.disabled(), - resolve: ({ target }) => { + onDone: (to) => + to.full.disabled().resolve(({ target }) => { const selected = target(new Disabled({})) return unsafe === "done" ? ({ ...selected, path: "workflow" } as any) : selected - } - }) + }) } }) @@ -286,10 +279,10 @@ describe("Machine structural visualization", () => { }).handle({ idle: { on: { - Refresh: Machine.transition({ target: (to) => to.none(), resolve: () => undefined, reenter: true }) + Refresh: (to) => to.none().resolve(() => undefined, { reenter: true }) }, - always: Machine.transition({ target: (to) => to.none(), resolve: () => undefined }), - onDone: Machine.transition({ target: (to) => to.none(), resolve: () => undefined }) + always: { target: Machine.targetless }, + onDone: { target: Machine.targetless } } }) diff --git a/test/testing/Coverage.test.ts b/test/testing/Coverage.test.ts index 72702c7..abf42de 100644 --- a/test/testing/Coverage.test.ts +++ b/test/testing/Coverage.test.ts @@ -27,13 +27,12 @@ const counterMachine = Machine.make({ }).handle({ count: { on: { - Add: Machine.transition({ - declinable: true, - target: (to) => to.full.count(), - resolve: ({ event, state, target }) => target(new Count({ value: state.value + event.amount })), - reenter: true - }), - Finish: Machine.transition({ target: (to) => to.full.done(), resolve: ({ target }) => target(new Done({})) }) + Add: (to) => + to.full.count().resolve( + ({ event, state, target }) => target(new Count({ value: state.value + event.amount })), + { reenter: true, declinable: true } + ), + Finish: (to) => to.full.done().resolve(({ target }) => target(new Done({}))) } }, done: {} @@ -64,21 +63,18 @@ const startupMachine = Machine.make({ } }).handle({ count: { - always: Machine.transition({ - branches: (to) => ({ + always: (to) => + to.branches({ zero: { title: "Count is zero", target: to.full.count() }, unchanged: { target: to.none() } - }), - resolve: ({ state, select }) => + }).resolve(({ state, select }) => state.value === 0 ? select.zero(new Count({ value: 1 })) : select.unchanged() - }), + ), on: { - Add: Machine.transition({ - target: (to) => to.full.count(), - resolve: ({ event, state, target }) => target(new Count({ value: state.value + event.amount })) - }) + Add: (to) => + to.full.count().resolve(({ event, state, target }) => target(new Count({ value: state.value + event.amount }))) } } }) @@ -97,19 +93,18 @@ const branchMachine = Machine.make({ }).handle({ count: { on: { - Select: Machine.transition({ - branches: (to) => ({ + Select: (to) => + to.branches({ negative: { target: to.none() }, zero: { target: to.none() }, positive: { target: to.none() } - }), - resolve: ({ event, select }) => + }).resolve(({ event, select }) => event.value < 0 ? select.negative() : event.value === 0 ? select.zero() : select.positive() - }) + ) } } }) @@ -131,18 +126,9 @@ const finiteEventMachine = Machine.make({ }).handle({ count: { on: { - [Tick]: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - Alpha: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - Beta: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + [Tick]: { target: Machine.targetless }, + Alpha: { target: Machine.targetless }, + Beta: { target: Machine.targetless } } } }) diff --git a/test/testing/Exploration.test.ts b/test/testing/Exploration.test.ts index 01fac0d..ed3d0b4 100644 --- a/test/testing/Exploration.test.ts +++ b/test/testing/Exploration.test.ts @@ -27,18 +27,10 @@ const machine = Machine.make({ }).handle({ counter: { on: { - Increment: Machine.transition({ - target: (to) => to.full.counter(), - resolve: ({ state, target }) => target(new Counter({ count: state.count + 1 })) - }), - Reset: Machine.transition({ - target: (to) => to.full.counter(), - resolve: ({ target }) => target(new Counter({ count: 0 })) - }), - Corrupt: Machine.transition({ - target: (to) => to.full.counter(), - resolve: ({ target }) => target(new Counter({ count: -1 })) - }) + Increment: (to) => + to.full.counter().resolve(({ state, target }) => target(new Counter({ count: state.count + 1 }))), + Reset: (to) => to.full.counter().resolve(({ target }) => target(new Counter({ count: 0 }))), + Corrupt: (to) => to.full.counter().resolve(({ target }) => target(new Counter({ count: -1 }))) } } }) @@ -56,19 +48,18 @@ const branchMachine = Machine.make({ }).handle({ counter: { on: { - Select: Machine.transition({ - branches: (to) => ({ + Select: (to) => + to.branches({ negative: { target: to.none() }, zero: { target: to.none() }, positive: { target: to.none() } - }), - resolve: ({ event, select }) => + }).resolve(({ event, select }) => event.value < 0 ? select.negative() : event.value === 0 ? select.zero() : select.positive() - }) + ) } } }) diff --git a/test/testing/Invariant.test.ts b/test/testing/Invariant.test.ts index 1ef5eb2..12fd13d 100644 --- a/test/testing/Invariant.test.ts +++ b/test/testing/Invariant.test.ts @@ -29,14 +29,14 @@ const makeAccountMachine = (withdraw: (balance: number, amount: number) => numbe }).handle({ account: { on: { - Withdraw: Machine.transition({ - target: (to) => to.full.account(), - resolve: ({ event, state, target }) => target(new Account({ balance: withdraw(state.balance, event.amount) })) - }), - Deposit: Machine.transition({ - target: (to) => to.full.account(), - resolve: ({ event, state, target }) => target(new Account({ balance: state.balance + event.amount })) - }) + Withdraw: (to) => + to.full.account().resolve(({ event, state, target }) => + target(new Account({ balance: withdraw(state.balance, event.amount) })) + ), + Deposit: (to) => + to.full.account().resolve(({ event, state, target }) => + target(new Account({ balance: state.balance + event.amount })) + ) } } }) diff --git a/test/testing/MachineTest.test.ts b/test/testing/MachineTest.test.ts index 70975b5..f64b2ad 100644 --- a/test/testing/MachineTest.test.ts +++ b/test/testing/MachineTest.test.ts @@ -36,24 +36,20 @@ const makeTraceMachine = (onAction: () => void) => }).handle({ Idle: { on: { - Start: Machine.transition({ - target: (to) => to.full.Ready(), - resolve: ({ target }) => { + Start: (to) => + to.full.Ready().resolve(({ target }) => { onAction() return target(new Ready({ count: 0 })) - } - }) + }) } }, Ready: { on: { - Add: Machine.transition({ - target: (to) => to.full.Ready(), - resolve: ({ event, state, target }) => { + Add: (to) => + to.full.Ready().resolve(({ event, state, target }) => { onAction() return target(new Ready({ count: state.count + event.amount })) - } - }) + }) } } }) @@ -221,10 +217,7 @@ describe("MachineTest", () => { states: { idle: { on: { - Stop: Machine.transition({ - target: (to) => to.full.disabled(), - resolve: ({ target }) => target(new Disabled({})) - }) + Stop: (to) => to.full.disabled().resolve(({ target }) => target(new Disabled({}))) } } } @@ -233,10 +226,7 @@ describe("MachineTest", () => { states: { idle: { on: { - Stop: Machine.transition({ - target: (to) => to.full.disabled(), - resolve: ({ target }) => target(new Disabled({})) - }) + Stop: (to) => to.full.disabled().resolve(({ target }) => target(new Disabled({}))) } } } @@ -278,7 +268,7 @@ describe("MachineTest", () => { }).handle({ Idle: { on: { - Start: Machine.transition({ target: (to) => to.none(), resolve: () => undefined }) + Start: { target: Machine.targetless } } } }) diff --git a/test/testing/Probe.test.ts b/test/testing/Probe.test.ts index 79da6ba..da776d1 100644 --- a/test/testing/Probe.test.ts +++ b/test/testing/Probe.test.ts @@ -31,35 +31,23 @@ const machine = Machine.make({ }).handle({ Counter: { on: { - Increment: Machine.transition({ - target: (to) => to.full.Counter(), - resolve: ({ event, state, target }) => target(new Counter({ count: state.count + event.amount })) - }), - Noop: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - Decline: Machine.transition({ - declinable: true, - target: (to) => to.none(), - resolve: ({ decline }) => decline() - }), - Reenter: Machine.transition({ - target: (to) => to.full.Counter(), - resolve: ({ state, target }) => target(new Counter({ count: state.count })), - reenter: true - }), - Burst: Machine.transition({ - target: (to) => to.full.Counter(), - resolve: ({ state, target }, enqueue) => { + Increment: (to) => + to.full.Counter().resolve(({ event, state, target }) => + target(new Counter({ count: state.count + event.amount })) + ), + Noop: { target: Machine.targetless }, + Decline: (to) => to.none().resolve(({ decline }) => decline(), { declinable: true }), + Reenter: (to) => + to.full.Counter().resolve(({ state, target }) => target(new Counter({ count: state.count })), { + reenter: true + }), + Burst: (to) => + to.full.Counter().resolve(({ state, target }, enqueue) => { enqueue.raise(new RaisedIncrement({})) return target(new Counter({ count: state.count + 1 })) - } - }), - RaisedIncrement: Machine.transition({ - target: (to) => to.full.Counter(), - resolve: ({ state, target }) => target(new Counter({ count: state.count + 10 })) - }) + }), + RaisedIncrement: (to) => + to.full.Counter().resolve(({ state, target }) => target(new Counter({ count: state.count + 10 }))) } } }) @@ -176,10 +164,7 @@ describe("MachineTest probe", () => { }).handle({ Idle: { on: { - Load: Machine.transition({ - target: (to) => to.full.Loading(), - resolve: ({ target }) => target(new Loading({})) - }) + Load: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({}))) } }, Loading: { diff --git a/test/testing/ReferenceModel.test.ts b/test/testing/ReferenceModel.test.ts index ff4961d..1f6fde1 100644 --- a/test/testing/ReferenceModel.test.ts +++ b/test/testing/ReferenceModel.test.ts @@ -1000,14 +1000,8 @@ describe("MachineTest finite-model reference interpreter", () => { states: { idle: { on: { - Local: Machine.transition({ - target: (to) => to.local.done(), - resolve: ({ target }) => target({ _tag: "LeftDone", version: 1 }) - }), - Exit: Machine.transition({ - target: (to) => to.full.outside(), - resolve: ({ target }) => target({ _tag: "Outside", version: 1 }) - }) + Local: (to) => to.local.done().resolve(({ target }) => target({ _tag: "LeftDone", version: 1 })), + Exit: (to) => to.full.outside().resolve(({ target }) => target({ _tag: "Outside", version: 1 })) } } } @@ -1016,14 +1010,8 @@ describe("MachineTest finite-model reference interpreter", () => { states: { idle: { on: { - Local: Machine.transition({ - target: (to) => to.local.idle(), - resolve: ({ target }) => target({ _tag: "RightIdle", version: 1 }) - }), - Exit: Machine.transition({ - target: (to) => to.local.idle(), - resolve: ({ target }) => target({ _tag: "RightIdle", version: 2 }) - }) + Local: (to) => to.local.idle().resolve(({ target }) => target({ _tag: "RightIdle", version: 1 })), + Exit: (to) => to.local.idle().resolve(({ target }) => target({ _tag: "RightIdle", version: 2 })) } } } diff --git a/test/testing/Runtime.test.ts b/test/testing/Runtime.test.ts index 73c3df9..e64be49 100644 --- a/test/testing/Runtime.test.ts +++ b/test/testing/Runtime.test.ts @@ -34,14 +34,14 @@ const makeCounterMachine = () => }).handle({ Counter: { on: { - Add: Machine.transition({ - target: (to) => to.full.Counter(), - resolve: ({ event, state, target }) => target(new Counter({ count: state.count + event.amount })) - }), - InternalAdd: Machine.transition({ - target: (to) => to.full.Counter(), - resolve: ({ event, state, target }) => target(new Counter({ count: state.count + event.amount })) - }) + Add: (to) => + to.full.Counter().resolve(({ event, state, target }) => + target(new Counter({ count: state.count + event.amount })) + ), + InternalAdd: (to) => + to.full.Counter().resolve(({ event, state, target }) => + target(new Counter({ count: state.count + event.amount })) + ) } } }) @@ -57,25 +57,20 @@ const causalMachine = Machine.make({ }).handle({ Counter: { on: { - Add: Machine.transition({ - target: (to) => to.full.Counter(), - resolve: ({ event, state, target }) => target(new Counter({ count: state.count + event.amount })) - }), - Noop: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - Burst: Machine.transition({ - target: (to) => to.full.Counter(), - resolve: ({ state, target }, enqueue) => { + Add: (to) => + to.full.Counter().resolve(({ event, state, target }) => + target(new Counter({ count: state.count + event.amount })) + ), + Noop: { target: Machine.targetless }, + Burst: (to) => + to.full.Counter().resolve(({ state, target }, enqueue) => { enqueue.raise(new InternalAdd({ amount: 10 })) return target(new Counter({ count: state.count + 1 })) - } - }), - InternalAdd: Machine.transition({ - target: (to) => to.full.Counter(), - resolve: ({ event, state, target }) => target(new Counter({ count: state.count + event.amount })) - }) + }), + InternalAdd: (to) => + to.full.Counter().resolve(({ event, state, target }) => + target(new Counter({ count: state.count + event.amount })) + ) } } }) @@ -267,10 +262,7 @@ describe("MachineTest runtime commands", () => { invoke: Machine.invoke({ id: "timeout", after: "1 second", - onDone: Machine.transition({ - target: (to) => to.full.TimedOut(), - resolve: ({ target }) => target(new TimedOut({})) - }) + onDone: (to) => to.full.TimedOut().resolve(({ target }) => target(new TimedOut({}))) }) }, TimedOut: {} @@ -754,10 +746,7 @@ describe("MachineTest causal runtime commands", () => { invoke: Machine.invoke({ id: "timeout", after: "1 second", - onDone: Machine.transition({ - target: (to) => to.full.TimedOut(), - resolve: ({ target }) => target(new TimedOut({})) - }) + onDone: (to) => to.full.TimedOut().resolve(({ target }) => target(new TimedOut({}))) }) }, TimedOut: {} diff --git a/test/testing/Verification.test.ts b/test/testing/Verification.test.ts index a919cef..c38c880 100644 --- a/test/testing/Verification.test.ts +++ b/test/testing/Verification.test.ts @@ -41,10 +41,7 @@ const navigationMachine = Machine.make({ }).handle({ off: { on: { - Go: Machine.transition({ - target: (to) => to.full.app(), - resolve: ({ target }) => target(new App({}), (app) => app.two(new Two({}))) - }) + Go: (to) => to.full.app().resolve(({ target }) => target(new App({}), (app) => app.two(new Two({})))) } } }) @@ -58,15 +55,9 @@ const raisedNavigationMachine = Machine.make({ } }).handle({ off: { - always: Machine.transition({ - target: (to) => to.full.app(), - resolve: ({ target }) => target(new App({}), (app) => app.one(new One({}))) - }), + always: (to) => to.full.app().resolve(({ target }) => target(new App({}), (app) => app.one(new One({})))), on: { - Go: Machine.transition({ - target: (to) => to.full.app(), - resolve: ({ target }) => target(new App({}), (app) => app.one(new One({}))) - }) + Go: (to) => to.full.app().resolve(({ target }) => target(new App({}), (app) => app.one(new One({})))) } } }) @@ -83,14 +74,9 @@ const counterMachine = Machine.make({ }).handle({ counter: { on: { - Increment: Machine.transition({ - target: (to) => to.full.counter(), - resolve: ({ state, target }) => target(new Counter({ count: state.count + 1 })) - }), - Noop: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + Increment: (to) => + to.full.counter().resolve(({ state, target }) => target(new Counter({ count: state.count + 1 }))), + Noop: { target: Machine.targetless } } } }) @@ -105,19 +91,18 @@ const conditionalMachine = Machine.make({ }).handle({ counter: { on: { - Select: Machine.transition({ - branches: (to) => ({ + Select: (to) => + to.branches({ negative: { target: to.none() }, zero: { target: to.full.counter() }, positive: { target: to.none() } - }), - resolve: ({ event, select }) => + }).resolve(({ event, select }) => event.value < 0 ? select.negative() : event.value === 0 ? select.zero(new Counter({ count: 0 })) : select.positive() - }) + ) } } }) @@ -135,12 +120,12 @@ const invokedMachine = Machine.make({ Machine.invoke({ id: "first", effect: () => Effect.succeed(1), - onDone: Machine.transition({ target: (to) => to.none(), resolve: () => undefined }) + onDone: { target: Machine.targetless } }), Machine.invoke({ id: "second", effect: () => Effect.succeed(2), - onDone: Machine.transition({ target: (to) => to.none(), resolve: () => undefined }) + onDone: { target: Machine.targetless } }) ] } @@ -172,16 +157,10 @@ const routedStartupMachine = Machine.make({ a: { states: { route: { - choice: Machine.transition({ - target: (to) => to.local.second(), - resolve: ({ target }) => target() - }) + choice: (to) => to.local.second().resolve(({ target }) => target()) }, second: { - choice: Machine.transition({ - target: (to) => to.full.b(), - resolve: ({ target }) => target(new StartupB({})) - }) + choice: (to) => to.full.b().resolve(({ target }) => target(new StartupB({}))) } } }, @@ -217,23 +196,14 @@ const choiceResolutionMachine = Machine.make({ states: { ready: { on: { - Route: Machine.transition({ - target: (to) => to.local.first(), - resolve: ({ target }) => target() - }) + Route: (to) => to.local.first().resolve(({ target }) => target()) } }, first: { - choice: Machine.transition({ - target: (to) => to.local.second(), - resolve: ({ target }) => target() - }) + choice: (to) => to.local.second().resolve(({ target }) => target()) }, second: { - choice: Machine.transition({ - target: (to) => to.local.routed(), - resolve: ({ target }) => target(new ChoiceRouted({})) - }) + choice: (to) => to.local.routed().resolve(({ target }) => target(new ChoiceRouted({}))) }, routed: {} } @@ -254,11 +224,8 @@ const reentryMachine = Machine.make({ }).handle({ app: { on: { - Restart: Machine.transition({ - target: (to) => to.full.app(), - resolve: ({ target }) => target(new App({}), (app) => app.one(new One({}))), - reenter: true - }) + Restart: (to) => + to.full.app().resolve(({ target }) => target(new App({}), (app) => app.one(new One({}))), { reenter: true }) } } }) @@ -366,10 +333,7 @@ const historyMachine = Machine.make({ } }, on: { - Leave: Machine.transition({ - target: (to) => to.full.away(), - resolve: ({ target }) => target(new Away({})) - }) + Leave: (to) => to.full.away().resolve(({ target }) => target(new Away({}))) }, states: { editor: { @@ -379,10 +343,7 @@ const historyMachine = Machine.make({ }, away: { on: { - Resume: Machine.transition({ - target: (to) => to.history.workspace.exact(), - resolve: ({ target }) => target() - }) + Resume: (to) => to.history.workspace.exact().resolve(({ target }) => target()) } } }) @@ -424,10 +385,7 @@ const structuralHistoryMachine = Machine.make({ exact: { default: structuralHistoryInitial } }, on: { - Leave: Machine.transition({ - target: (to) => to.full.away(), - resolve: ({ target }) => target.from() - }) + Leave: (to) => to.full.away().resolve(({ target }) => target.from()) } } }) @@ -486,10 +444,7 @@ const doneTransitionMachine = Machine.make({ } }).handle({ workflow: { - onDone: Machine.transition({ - target: (to) => to.full.archived(), - resolve: ({ target }) => target(new Archived({})) - }), + onDone: (to) => to.full.archived().resolve(({ target }) => target(new Archived({}))), states: { finished: { output: () => "workflow-output" diff --git a/test/unstable/cluster/ClusterMachine.test.ts b/test/unstable/cluster/ClusterMachine.test.ts index 7cdabc1..cc9b834 100644 --- a/test/unstable/cluster/ClusterMachine.test.ts +++ b/test/unstable/cluster/ClusterMachine.test.ts @@ -73,9 +73,8 @@ const makeCounter = (state: { state.initialEntries += 1 }, on: { - Increment: Machine.transition({ - target: (to) => to.full.Count(), - resolve: ({ event, state: current, target }, enqueue) => { + Increment: (to) => + to.full.Count().resolve(({ event, state: current, target }, enqueue) => { state.actions += 1 state.inFlight += 1 state.maxInFlight = Math.max(state.maxInFlight, state.inFlight) @@ -83,33 +82,24 @@ const makeCounter = (state: { const value = current.value + event.by enqueue.emit(new Changed({ value })) return target(new Count({ value })) - } - }), - Fail: Machine.transition({ - target: (to) => to.full.Count(), - resolve: ({ state: current, target }, enqueue) => { + }), + Fail: (to) => + to.full.Count().resolve(({ state: current, target }, enqueue) => { enqueue.emit(new Changed({ value: 999 })) return target(current) - } - }), - Finish: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ state: current, target }) => target(new Done({ value: current.value })) - }), - RaiseFromAction: Machine.transition({ - target: (to) => to.full.Count(), - resolve: ({ state: current, target }, enqueue) => { + }), + Finish: (to) => + to.full.Done().resolve(({ state: current, target }) => target(new Done({ value: current.value }))), + RaiseFromAction: (to) => + to.full.Count().resolve(({ state: current, target }, enqueue) => { enqueue.raise(new Increment({ by: 1, block: false })) return target(current) - } - }), - SpawnFromAction: Machine.transition({ - target: (to) => to.full.Count(), - resolve: ({ state: current, target }, enqueue) => { + }), + SpawnFromAction: (to) => + to.full.Count().resolve(({ state: current, target }, enqueue) => { enqueue.stop(UnsupportedChild) return target(current) - } - }) + }) } }, Done: {} @@ -604,10 +594,7 @@ describe("ClusterMachine", () => { invoke: Machine.invoke({ id: "child", effect: () => Effect.void, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }) } }) diff --git a/test/unstable/reactivity/AtomMachine.test.ts b/test/unstable/reactivity/AtomMachine.test.ts index ccabc99..631fad9 100644 --- a/test/unstable/reactivity/AtomMachine.test.ts +++ b/test/unstable/reactivity/AtomMachine.test.ts @@ -75,10 +75,8 @@ const makeCounterMachine = () => }).handle({ Count: { on: { - Finish: Machine.transition({ - target: (to) => to.full.Count(), - resolve: ({ state, event, target }) => target(new Count({ value: state.value + event.by })) - }) + Finish: (to) => + to.full.Count().resolve(({ state, event, target }) => target(new Count({ value: state.value + event.by }))) } }, Done: {} @@ -171,10 +169,10 @@ describe("AtomMachine", () => { }) }), on: { - Finish: Machine.transition({ - target: (to) => to.full.Count(), - resolve: ({ event, state, target }) => target(new Count({ value: state.value + event.by })) - }) + Finish: (to) => + to.full.Count().resolve(({ event, state, target }) => + target(new Count({ value: state.value + event.by })) + ) } }, Done: {} @@ -224,25 +222,16 @@ describe("AtomMachine", () => { }).handle({ Count: { on: { - Finish: Machine.transition({ - target: (to) => to.full.ValueRead(), - resolve: ({ target }) => target(new ValueRead({ value: "active" })) - }) + Finish: (to) => to.full.ValueRead().resolve(({ target }) => target(new ValueRead({ value: "active" }))) } }, ValueRead: { invoke: Machine.invoke({ child: Child, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }), on: { - ReadValue: Machine.transition({ - target: (to) => to.full.Count(), - resolve: ({ target }) => target(new Count({ value: 0 })) - }) + ReadValue: (to) => to.full.Count().resolve(({ target }) => target(new Count({ value: 0 }))) } } }) @@ -557,10 +546,8 @@ describe("AtomMachine", () => { }).handle({ Count: { on: { - Finish: Machine.transition({ - target: (to) => to.full.Done(), - resolve: ({ state, event, target }) => target(new Done({ value: state.value + event.by })) - }) + Finish: (to) => + to.full.Done().resolve(({ state, event, target }) => target(new Done({ value: state.value + event.by }))) } }, Done: { diff --git a/typetest/machine/Activities.tst.ts b/typetest/machine/Activities.tst.ts index e4f98b1..01a2688 100644 --- a/typetest/machine/Activities.tst.ts +++ b/typetest/machine/Activities.tst.ts @@ -19,20 +19,14 @@ const machine = Machine.make({ invoke: Machine.invoke({ id: "timeout", after: "1 second", - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }) }, Dynamic: { invoke: Machine.invoke({ id: "dynamic", after: () => "2 seconds" as const, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }) } }) diff --git a/typetest/machine/Choice.tst.ts b/typetest/machine/Choice.tst.ts index 2745f55..acceb3d 100644 --- a/typetest/machine/Choice.tst.ts +++ b/typetest/machine/Choice.tst.ts @@ -44,16 +44,15 @@ describe("Machine choice pseudo-states", () => { Flow: { states: { Routing: { - choice: Machine.transition({ - target: (to) => to.local.Approved(), - resolve: (context) => { + choice: (to) => + to.local.Approved().resolve((context) => { + expect(to.local.Approved()).type.not.toHaveProperty("reenter") expect(context).type.not.toHaveProperty("state") expect(context.containingState).type.toBe() expect(context.ancestors.Flow).type.toBe() expect(context.event).type.toBe>() return context.target(new Approved({})) - } - }) + }) } } } @@ -70,15 +69,18 @@ describe("Machine choice pseudo-states", () => { resolve: ({ target }) => (target(new Flow({ score: 80 }), (flow) => flow.Routing())) } }) - const target = (to: Machine.Machine.TargetSelector) => to.local.Approved() - expect(Machine.transition).type.not.toBeCallableWith({ - target, - resolve: ( - { target: selectedTarget }: Machine.Machine.TransitionResolveContext< - Machine.Machine.ChoiceContext, - ReturnType - > - ) => Effect.succeed(selectedTarget(new Approved({}))) + machine.handle({ + Flow: { + states: { + Routing: { + choice: (to) => + to.local.Approved().resolve(({ target }) => + // @ts-expect-error! + Effect.succeed(target(new Approved({}))) + ) + } + } + } }) }) @@ -125,7 +127,7 @@ describe("Machine choice pseudo-states", () => { } }) - it("requires Machine.transition and validates the selected result", () => { + it("validates the selected choice result", () => { const base = Machine.make({ states: States.states, events: Machine.events(), @@ -134,18 +136,15 @@ describe("Machine choice pseudo-states", () => { resolve: ({ target }) => (target(new Flow({ score: 80 }), (flow) => flow.Routing())) } }) - const target = (to: Machine.Machine.TargetSelector) => to.local.Rejected() base.handle({ Flow: { states: { Routing: { - choice: Machine.transition({ - target, - resolve: ({ target: selectedTarget }) => { + choice: (to) => + to.local.Rejected().resolve(({ target: selectedTarget }) => { expect(selectedTarget).type.not.toBeCallableWith(new Approved({})) return selectedTarget(new Rejected({})) - } - }) + }) } } } diff --git a/typetest/machine/EventByTag.tst.ts b/typetest/machine/EventByTag.tst.ts index 60dccfe..98ce1cc 100644 --- a/typetest/machine/EventByTag.tst.ts +++ b/typetest/machine/EventByTag.tst.ts @@ -42,28 +42,24 @@ describe("Machine.EventByTag", () => { }).handle({ Idle: { on: { - Alpha: Machine.transition({ - target: (to) => to.none(), - resolve: ({ event }) => { + Alpha: (to) => + to.none().resolve(({ event }) => { expect(event).type.toBe<{ readonly _tag: "Alpha" readonly payload: string readonly count: number }>() return undefined - } - }), - Beta: Machine.transition({ - target: (to) => to.none(), - resolve: ({ event }) => { + }), + Beta: (to) => + to.none().resolve(({ event }) => { expect(event).type.toBe<{ readonly _tag: "Beta" readonly payload: string readonly count: number }>() return undefined - } - }) + }) } } }) diff --git a/typetest/machine/EventConstructors.tst.ts b/typetest/machine/EventConstructors.tst.ts index 6012649..80e36a1 100644 --- a/typetest/machine/EventConstructors.tst.ts +++ b/typetest/machine/EventConstructors.tst.ts @@ -123,24 +123,20 @@ describe("Machine event constructor collections", () => { Machine.invoke({ id: "load", effect: () => Effect.succeed("ready"), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: ({ output }, enqueue) => { + onDone: (to) => + to.none().resolve(({ output }, enqueue) => { enqueue.raise(internalEvents.Loaded({ value: output })) return undefined - } - }) + }) }), Machine.invoke({ id: "timeout", after: "1 second", - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: (_, enqueue) => { + onDone: (to) => + to.none().resolve((_, enqueue) => { enqueue.raise(internalEvents.Failed()) return undefined - } - }) + }) }) ] } diff --git a/typetest/machine/History.tst.ts b/typetest/machine/History.tst.ts index 9ed6e13..e713c95 100644 --- a/typetest/machine/History.tst.ts +++ b/typetest/machine/History.tst.ts @@ -179,20 +179,17 @@ describe("Machine history states", () => { const incomplete = definition.handle({ support: { on: { - Resume: Machine.transition({ - target: (to) => { - expect(to.history.checkout.recent).type.toBeCallableWith() - expect(to.history.checkout.recent).type.not.toBeCallableWith(new Checkout({ orderId: "new" })) - expect(to.full.checkout).type.not.toHaveProperty("recent") - expect(to.local).type.not.toHaveProperty("recent") - expect(to.branch).type.not.toHaveProperty("recent") - return to.history.checkout.exact() - }, - resolve: ({ target }) => { + Resume: (to) => { + expect(to.history.checkout.recent).type.toBeCallableWith() + expect(to.history.checkout.recent).type.not.toBeCallableWith(new Checkout({ orderId: "new" })) + expect(to.full.checkout).type.not.toHaveProperty("recent") + expect(to.local).type.not.toHaveProperty("recent") + expect(to.branch).type.not.toHaveProperty("recent") + return to.history.checkout.exact().resolve(({ target }) => { expect(target).type.toBeCallableWith() return target() - } - }) + }) + } } } }) @@ -214,10 +211,7 @@ describe("Machine history states", () => { const incomplete = definition.handle({ support: { on: { - Resume: Machine.transition({ - target: (to) => to.history.checkout.recent(), - resolve: ({ target }) => target() - }) + Resume: (to) => to.history.checkout.recent().resolve(({ target }) => target()) } } }) diff --git a/typetest/machine/InitialEntry.tst.ts b/typetest/machine/InitialEntry.tst.ts index 5d96267..49536c4 100644 --- a/typetest/machine/InitialEntry.tst.ts +++ b/typetest/machine/InitialEntry.tst.ts @@ -26,73 +26,39 @@ const base = Machine.make({ } }) -type Events = readonly [typeof Open] -type ClosedContext = Machine.Machine.HandlerContext< - typeof States.states, - Events, - readonly [], - "closed", - "Open", - never, - never -> -type ClosedTransition = Machine.Machine.TransitionConfig< - typeof States.states, - Events, - readonly [], - "closed", - ClosedContext, - true -> -type OpenedInitializeContext = Machine.Machine.StateInitializeContext< - typeof States.states, - Events, - readonly [], - "opened" -> - describe("declared initial entry types", () => { it("requires initialize at the handle call that returns an initial target", () => { - const invalid = Machine.transition({ - target: (to) => to.full.opened.initial(), - resolve: ({ target }) => target(new Opened({ id: "team-1" })) - }) satisfies ClosedTransition - expect(base.handle).type.not.toBeCallableWith({ + base.handle({ closed: { on: { - Open: invalid + Open: (to) => to.full.opened.initial().resolve(({ target }) => target(new Opened({ id: "team-1" }))) } }, + // @ts-expect-error! opened: {} }) - const initial = Machine.transition({ - target: (to) => to.full.opened.initial(), - resolve: ({ target }) => target.from({ id: "team-1" }) - }) satisfies ClosedTransition - expect(base.handle).type.toBeCallableWith({ + base.handle({ closed: { on: { - Open: initial + Open: (to) => to.full.opened.initial().resolve(({ target }) => target.from({ id: "team-1" })) } }, opened: { - initialize: ({ builder }: OpenedInitializeContext) => builder.from({ count: 0 }) + initialize: ({ builder }) => builder.from({ count: 0 }) } }) - const explicit = Machine.transition({ - target: (to) => to.full.opened(), - resolve: ({ target }) => - target( - new Opened({ id: "team-1" }), - (opened) => opened.loading(new Loading({})) - ) - }) satisfies ClosedTransition - expect(base.handle).type.toBeCallableWith({ + base.handle({ closed: { on: { - Open: explicit + Open: (to) => + to.full.opened().resolve(({ target }) => + target( + new Opened({ id: "team-1" }), + (opened) => opened.loading(new Loading({})) + ) + ) } }, opened: {} @@ -103,9 +69,8 @@ describe("declared initial entry types", () => { base.handle({ closed: { on: { - Open: Machine.transition({ - target: (to) => to.full.opened(), - resolve: ({ target }) => { + Open: (to) => + to.full.opened().resolve(({ target }) => { expect(target).type.toHaveProperty("initial") expect(target.initial).type.not.toBeCallableWith() expect(target.initial).type.toBeCallableWith(new Opened({ id: "team-1" })) @@ -114,8 +79,7 @@ describe("declared initial entry types", () => { new Opened({ id: "team-1" }), (opened) => opened.loading(new Loading({})) ) - } - }) + }) } } }) diff --git a/typetest/machine/Inspection.tst.ts b/typetest/machine/Inspection.tst.ts index 9e7f463..2558a46 100644 --- a/typetest/machine/Inspection.tst.ts +++ b/typetest/machine/Inspection.tst.ts @@ -33,10 +33,7 @@ describe("Machine inspection", () => { }).handle({ root: { on: { - Reset: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + Reset: { target: Machine.targetless } } } }) @@ -53,10 +50,7 @@ describe("Machine inspection", () => { }).handle({ Idle: { on: { - Reset: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + Reset: { target: Machine.targetless } } } }) @@ -216,10 +210,7 @@ describe("Machine inspection", () => { flat.handle({ idle: { on: { - Reset: Machine.transition({ - target: (to) => to.full.running(), - resolve: ({ target }) => target(new Running({})) - }) + Reset: (to) => to.full.running().resolve(({ target }) => target(new Running({}))) } } }) diff --git a/typetest/machine/Machine.tst.ts b/typetest/machine/Machine.tst.ts index 53c2233..c483f76 100644 --- a/typetest/machine/Machine.tst.ts +++ b/typetest/machine/Machine.tst.ts @@ -496,9 +496,8 @@ describe("Machine", () => { }).handle({ down: { on: { - SignIn: Machine.transition({ - target: (to) => to.full.down(), - resolve: ({ event, target }, enqueue) => { + SignIn: (to) => + to.full.down().resolve(({ event, target }, enqueue) => { expect(enqueue.raise).type.toBeCallableWith(event) expect(enqueue.raise).type.not.toBeCallableWith(new Down({})) expect(enqueue.emit).type.toBeCallableWith(new SignInCompleted({ userId: event.userId })) @@ -508,8 +507,7 @@ describe("Machine", () => { expect(enqueue.stop).type.toBeCallableWith(worker) expect(enqueue.stop).type.not.toBeCallableWith("worker") return target(new Down({})) - } - }) + }) } } }) @@ -565,13 +563,10 @@ describe("Machine", () => { machine.handle({ down: { - invoke: machine.invoke({ + invoke: Machine.invoke({ id: "valid", effect: () => Effect.succeed(1), - onDone: Machine.transition({ - target: (to) => to.full.down(), - resolve: ({ target }) => target(new Down({})) - }) + onDone: (to) => to.full.down().resolve(({ target }) => target(new Down({}))) }) } }) @@ -586,7 +581,7 @@ describe("Machine", () => { }) }) - it("contextually types dynamic Effect sources through the bound constructor", () => { + it("contextually types dynamic Effect sources through Machine.invoke", () => { const machine = Machine.make({ states: UpStates.states, events: Machine.events(SignIn), @@ -598,22 +593,19 @@ describe("Machine", () => { machine.handle({ down: { - invoke: machine.invoke({ + invoke: Machine.invoke({ id: "dynamic", effect: ({ state }) => { expect(state).type.toBe() return Effect.succeed(state._tag) }, - onDone: Machine.transition({ - target: (to) => to.full.down(), - resolve: ({ target }) => target(new Down({})) - }) + onDone: (to) => to.full.down().resolve(({ target }) => target(new Down({}))) }) } }) }) - it("infers Stream elements, failures, and services through the bound constructor", () => { + it("infers Stream elements, failures, and services through Machine.invoke", () => { class StreamFailure { readonly _tag = "StreamFailure" } @@ -630,7 +622,7 @@ describe("Machine", () => { }) const handled = machine.handle({ down: { - invoke: machine.invoke({ + invoke: Machine.invoke({ id: "updates", stream: ({ state }) => { expect(state).type.toBe() @@ -735,20 +727,14 @@ describe("Machine", () => { machine.handle({ down: { - invoke: machine.invoke({ + invoke: Machine.invoke({ id: "dynamic", effect: ({ state }) => { expect(state).type.toBe() return load(state._tag) }, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless }, + onFailure: { target: Machine.targetless } }) } }) @@ -770,33 +756,24 @@ describe("Machine", () => { machine.handle({ down: { invoke: [ - machine.invoke({ + Machine.invoke({ id: "success", effect: ({ state }) => Effect.succeed(state._tag), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }), - machine.invoke({ + Machine.invoke({ id: "failure", effect: ({ state }) => Effect.fail(new LoadFailure()).pipe(Effect.annotateLogs("state", state._tag)), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onFailure: { target: Machine.targetless } }), - machine.invoke({ + Machine.invoke({ id: "never", effect: ({ state }) => Effect.never.pipe(Effect.annotateLogs("state", state._tag)) }), - machine.invoke({ + Machine.invoke({ id: "requirements", effect: ({ state }) => Effect.as(EntryRequirement, state._tag), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }) ] } @@ -804,13 +781,10 @@ describe("Machine", () => { const requirementsHandled = machine.handle({ down: { - invoke: machine.invoke({ + invoke: Machine.invoke({ id: "requirements-only", effect: ({ state }) => Effect.as(EntryRequirement, state._tag), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }) } }) @@ -875,20 +849,16 @@ describe("Machine", () => { }).handle({ down: { on: { - SignIn: Machine.transition({ - target: (to) => to.none(), - resolve: ({ event }) => { + SignIn: (to) => + to.none().resolve(({ event }) => { expect(event).type.toBe() return undefined - } - }), - SignInCompleted: Machine.transition({ - target: (to) => to.none(), - resolve: ({ event }) => { + }), + SignInCompleted: (to) => + to.none().resolve(({ event }) => { expect(event).type.toBe() return undefined - } - }) + }) } } }) @@ -954,13 +924,10 @@ describe("Machine", () => { }) machine.handle({ down: { - invoke: machine.invoke({ + invoke: Machine.invoke({ id: "erased-failure", effect: erasedFailure, - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onFailure: { target: Machine.targetless } }) } }) @@ -978,16 +945,14 @@ describe("Machine", () => { }).handle({ source: { on: { - SignIn: Machine.transition({ - target: (to) => to.full.target(), - resolve: ({ state, target }) => { + SignIn: (to) => + to.full.target().resolve(({ state, target }) => { const { _tag: _, ...fields } = state expect(target.from).type.toBeCallableWith({ ...fields, attempt: 1 }) expect(target.from).type.not.toBeCallableWith(fields) expect(target.from).type.not.toBeCallableWith({ ...fields, attempt: "invalid" }) return target.from({ ...fields, attempt: 1 }) - } - }) + }) } } }) @@ -1036,25 +1001,21 @@ describe("Machine", () => { typeof Child.machine, typeof Child > - const onSnapshot: NonNullable = Machine.transition({ - target: (to) => to.none(), - resolve: ({ snapshot }) => { + const onSnapshot: NonNullable = (to) => + to.none().resolve(({ snapshot }) => { expect(snapshot.state).type.toBe>() return undefined - } - }) - const onDone: ChildArgs["onDone"] = Machine.transition({ - target: (to) => to.none(), - resolve: ({ output, state }) => { + }) + const onDone: ChildArgs["onDone"] = (to) => + to.none().resolve(({ output, state }) => { expect(output).type.toBe() expect(state).type.toBe() return undefined - } - }) + }) parent.handle({ down: { - invoke: parent.invoke({ + invoke: Machine.invoke({ child: Child, input: { userId: "child" }, onSnapshot, @@ -1126,13 +1087,10 @@ describe("Machine", () => { auth: { states: { signedOut: { - invoke: machine.invoke({ + invoke: Machine.invoke({ id: "nested", effect: () => Effect.succeed(Option.some(1)), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }) } } @@ -1187,10 +1145,7 @@ describe("Machine", () => { }).handle({ down: { on: { - SignIn: Machine.transition({ - target: (to) => to.full.down(), - resolve: ({ target }) => target(new Down({})) - }) + SignIn: (to) => to.full.down().resolve(({ target }) => target(new Down({}))) } } }) @@ -1296,8 +1251,8 @@ describe("Machine", () => { machine.handle({ down: { on: { - SignIn: Machine.transition({ - branches: (to) => ({ + SignIn: (to) => + to.branches({ recognized: { title: "recognized user", target: to.full.down() @@ -1314,8 +1269,7 @@ describe("Machine", () => { title: "active user", target: to.none() } - }), - resolve: ({ event, select, state }) => { + }).resolve(({ event, select, state }) => { expect(event).type.toBe() expect(state).type.toBe() expect(select.recognized).type.toBeCallableWith(new Down({})) @@ -1330,9 +1284,7 @@ describe("Machine", () => { default: return select.recognized(new Down({})) } - }, - reenter: true - }) + }, { reenter: true }) } } }) @@ -1340,19 +1292,35 @@ describe("Machine", () => { machine.handle({ down: { on: { - SignIn: Machine.transition({ - declinable: true, - branches: (to) => ({ + SignIn: (to) => to.none().reenter() + }, + // @ts-expect-error! + always: (to) => to.none().reenter() + } + }) + + machine.handle({ + down: { + on: { + // @ts-expect-error! + SignIn: (to) => to.full.up() + } + } + }) + + machine.handle({ + down: { + on: { + SignIn: (to) => + to.branches({ accepted: { target: to.full.down() }, consumed: { target: to.none() } - }), - resolve: (context) => { + }).resolve((context) => { expect(context.decline()).type.toBe() if (context.event.userId === "decline") return context.decline() if (context.event.userId === "consume") return context.select.consumed() return context.select.accepted(new Down({})) - } - }) + }, { declinable: true }) } } }) @@ -1360,75 +1328,90 @@ describe("Machine", () => { machine.handle({ down: { on: { - SignIn: Machine.transition({ - target: (to) => to.none(), - resolve: (context) => { + SignIn: (to) => + to.none().resolve((context) => { expect(context).type.not.toHaveProperty("decline") return undefined - } - }) + }) } } }) - const declined = null as unknown as Machine.Machine.Declined - expect(Machine.transition).type.not.toBeCallableWith({ - target: (to: Machine.Machine.TargetSelector) => to.none(), - resolve: () => declined + machine.handle({ + down: { + on: { + // @ts-expect-error! + SignIn: (to) => to.none().resolve(({ decline }) => decline()) + } + } }) - expect(Machine.transition).type.not.toBeCallableWith({ - declinable: true as boolean, - target: (to: Machine.Machine.TargetSelector) => to.none(), - resolve: () => declined + const widenedDeclinable = true as boolean + machine.handle({ + down: { + on: { + // @ts-expect-error! + SignIn: (to) => to.none().resolve(() => undefined, { declinable: widenedDeclinable }) + } + } }) - const target = (to: Machine.Machine.TargetSelector) => to.none() - type BranchesInput = Machine.Machine.TransitionBranchesInput< - typeof UpStates.states, - readonly [typeof SignIn], - readonly [], - "down", - SignInContext, - never, - { readonly ignored: { readonly target: ReturnType } } - > - expect().type.not.toBeCallableWith({} as SignInContext, {} as never) - expect(Machine.transition).type.not.toBeCallableWith({ - branches: (to: Machine.Machine.TargetSelector) => ({ - ignored: { target: to.none() } - }), - resolve: () => undefined - }) - expect(Machine.transition).type.not.toBeCallableWith({ - branches: (to: Machine.Machine.TargetSelector) => ({ - ignored: { target: to.none() } - }), - resolve: ({ select }: { readonly select: { readonly ignored: () => Machine.Machine.NoTarget } }) => - select.ignored() - }) - expect(Machine.transition).type.not.toBeCallableWith({ - branches: (_to: Machine.Machine.TargetSelector) => ({}), - resolve: () => undefined - }) - expect(Machine.transition).type.not.toBeCallableWith({ - branches: (to: Machine.Machine.TargetSelector) => ({ - "": { target: to.none() } - }), - resolve: () => undefined - }) - expect(Machine.transition).type.not.toBeCallableWith({ - branches: (to: Machine.Machine.TargetSelector) => ({ - 0: { target: to.none() } - }), - resolve: () => undefined + machine.handle({ + down: { + on: { + // @ts-expect-error! + SignIn: (to) => to.branches({ ignored: { target: to.none() } }).resolve(() => undefined) + } + } + }) + machine.handle({ + down: { + on: { + SignIn: (to) => { + // @ts-expect-error! + return to.branches({}).resolve(() => { + throw new Error("unreachable") + }) + } + } + } + }) + machine.handle({ + down: { + on: { + SignIn: (to) => { + // @ts-expect-error! + return to.branches({ "": { target: to.none() } }).resolve(() => { + throw new Error("unreachable") + }) + } + } + } + }) + machine.handle({ + down: { + on: { + SignIn: (to) => { + // @ts-expect-error! + return to.branches({ 0: { target: to.none() } }).resolve(() => { + throw new Error("unreachable") + }) + } + } + } }) const symbolBranch = Symbol("branch") - expect(Machine.transition).type.not.toBeCallableWith({ - branches: (to: Machine.Machine.TargetSelector) => ({ - valid: { target: to.none() }, - [symbolBranch]: { target: to.none() } - }), - resolve: () => undefined + machine.handle({ + down: { + on: { + SignIn: (to) => { + // @ts-expect-error! + return to.branches({ + valid: { target: to.none() }, + [symbolBranch]: { target: to.none() } + }).resolve(({ select }) => select.valid()) + } + } + } }) }) @@ -1449,14 +1432,12 @@ describe("Machine", () => { states: { signedOut: { on: { - SignIn: Machine.transition({ - target: (to) => to.local.signedIn(), - resolve: ({ event, state, target }) => { + SignIn: (to) => + to.local.signedIn().resolve(({ event, state, target }) => { expect(event).type.toBe() expect(state).type.toBe() return target(new SignedIn({ userId: event.userId })) - } - }) + }) } } } @@ -1488,22 +1469,18 @@ describe("Machine", () => { } void id }, - always: Machine.transition({ - target: (to) => to.none(), - resolve: ({ event }) => { + always: (to) => + to.none().resolve(({ event }) => { expect(event).type.toBe() return undefined - } - }), + }), states: { auth: { states: { signedOut: { on: { - SignIn: Machine.transition({ - target: (to) => to.local.signedIn(), - resolve: ({ event, target }) => target(new SignedIn({ userId: event.userId })) - }) + SignIn: (to) => + to.local.signedIn().resolve(({ event, target }) => target(new SignedIn({ userId: event.userId }))) } } } @@ -1547,15 +1524,13 @@ describe("Machine", () => { up: { states: { auth: { - onDone: Machine.transition({ - target: (to) => to.full.down(), - resolve: ({ event, output, state, target }) => { + onDone: (to) => + to.full.down().resolve(({ event, output, state, target }) => { expect(event).type.toBe() expect(output).type.toBe() expect(state).type.toBe() return target(new Down({})) - } - }), + }), states: { signedIn: {} } @@ -1810,13 +1785,11 @@ describe("Machine", () => { machine.handle({ auth: { - onDone: Machine.transition({ - target: (to) => to.full.down(), - resolve: ({ output, target }) => { + onDone: (to) => + to.full.down().resolve(({ output, target }) => { expect(output).type.toBe() return target(new Down({})) - } - }), + }), states: { signedIn: { output: ({ state }) => state.userId @@ -1894,9 +1867,8 @@ describe("Machine", () => { machine.handle({ payment: { - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: ({ output }) => { + onDone: (to) => + to.none().resolve(({ output }) => { expect(output.status).type.toBe<"approved" | "declined">() if (output.status === "approved") { expect(output.authId).type.toBe() @@ -1904,8 +1876,7 @@ describe("Machine", () => { expect(output.reason).type.toBe() } return undefined - } - }), + }), states: { approved: { output: ({ state }) => ({ @@ -1986,14 +1957,12 @@ describe("Machine", () => { requestId: outputs.sync.requestId } }, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: ({ output }) => { + onDone: (to) => + to.none().resolve(({ output }) => { expect(output.userId).type.toBe() expect(output.requestId).type.toBe() return undefined - } - }), + }), states: { auth: { states: { diff --git a/typetest/machine/MachineReferences.tst.ts b/typetest/machine/MachineReferences.tst.ts index d39422e..769ab91 100644 --- a/typetest/machine/MachineReferences.tst.ts +++ b/typetest/machine/MachineReferences.tst.ts @@ -39,10 +39,7 @@ describe("machine reference event channels", () => { }).handle({ Idle: { on: { - Ping: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + Ping: { target: Machine.targetless } } } }) @@ -70,9 +67,8 @@ describe("machine reference event channels", () => { }).handle({ Idle: { on: { - Ping: Machine.transition({ - target: (to) => to.none(), - resolve: ({ parent, self }, enqueue) => { + Ping: (to) => + to.none().resolve(({ parent, self }, enqueue) => { expect(self.send).type.toBeCallableWith(Events.Ping()) expect(self.send).type.not.toBeCallableWith(InternalEvents.Local()) expect(enqueue.sendTo).type.toBeCallableWith(self, Events.Ping()) @@ -88,8 +84,7 @@ describe("machine reference event channels", () => { expect(enqueue.emit).type.toBeCallableWith(Emissions.ValuedPublished({ value: 1 })) expect(enqueue.emit).type.not.toBeCallableWith(Events.Ping()) return undefined - } - }) + }) } } }) @@ -104,7 +99,9 @@ describe("machine reference event channels", () => { resolve: ({ target }) => (target(new Idle({}))) } }) - compatible.invoke({ child: Child }) + compatible.handle({ + Idle: { invoke: Machine.invoke({ child: Child }) } + }) const incompatible = Machine.make({ states: states.states, @@ -171,17 +168,15 @@ describe("machine reference event channels", () => { } return Effect.void }, - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: ({ parent, self }, enqueue) => { + onDone: (to) => + to.none().resolve(({ parent, self }, enqueue) => { enqueue.sendTo(self, Events.Ping()) if (parent !== undefined) { enqueue.sendTo(parent, ParentEvents.ParentNotice({ value: 1 })) expect(enqueue.sendTo).type.not.toBeCallableWith(parent, Events.Ping()) } return undefined - } - }) + }) }) } }) @@ -199,17 +194,15 @@ describe("machine reference event channels", () => { invoke: Machine.invoke({ id: "notify-parent-failure", effect: () => Effect.fail("failed" as const), - onFailure: Machine.transition({ - target: (to) => to.none(), - resolve: ({ parent, self }, enqueue) => { + onFailure: (to) => + to.none().resolve(({ parent, self }, enqueue) => { enqueue.sendTo(self, Events.Ping()) if (parent !== undefined) { enqueue.sendTo(parent, ParentEvents.ParentNotice({ value: 1 })) expect(enqueue.sendTo).type.not.toBeCallableWith(parent, Events.Ping()) } return undefined - } - }) + }) }) } }) diff --git a/typetest/machine/Readiness.tst.ts b/typetest/machine/Readiness.tst.ts index e30bfb7..90b1d98 100644 --- a/typetest/machine/Readiness.tst.ts +++ b/typetest/machine/Readiness.tst.ts @@ -164,10 +164,7 @@ describe("executable machine readiness", () => { }, states: { Route: { - choice: Machine.transition({ - target: (to) => to.local.Idle(), - resolve: ({ target }) => target(new Idle({})) - }) + choice: (to) => to.local.Idle().resolve(({ target }) => target(new Idle({}))) }, Done: { output: ({ state }) => state.value diff --git a/typetest/machine/Resume.tst.ts b/typetest/machine/Resume.tst.ts index f3a680d..c342bd2 100644 --- a/typetest/machine/Resume.tst.ts +++ b/typetest/machine/Resume.tst.ts @@ -21,10 +21,7 @@ const machine = Machine.make({ }).handle({ Idle: { on: { - Tick: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ state, target }) => target(new Idle({ value: state.value + 1 })) - }) + Tick: (to) => to.full.Idle().resolve(({ state, target }) => target(new Idle({ value: state.value + 1 }))) } } }) diff --git a/typetest/machine/SnapshotContext.tst.ts b/typetest/machine/SnapshotContext.tst.ts index 8ee7726..f6c0c57 100644 --- a/typetest/machine/SnapshotContext.tst.ts +++ b/typetest/machine/SnapshotContext.tst.ts @@ -51,34 +51,28 @@ describe("Machine transition snapshot context", () => { Root: { states: { Left: { - onDone: Machine.transition({ - target: (to) => to.local.LeftIdle(), - resolve: ({ snapshot, target }) => { + onDone: (to) => + to.local.LeftIdle().resolve(({ snapshot, target }) => { expect(snapshot).type.toBe>() expect(States.matches).type.toBeCallableWith(snapshot, "Root.Right.RightIdle") expect(States.get).type.toBeCallableWith(snapshot, "Root.Right.RightIdle") expect(States.getSnapshot).type.toBeCallableWith(snapshot, "Root.Right.RightIdle") return target(new LeftIdle({})) - } - }), + }), states: { LeftIdle: { - always: Machine.transition({ - target: (to) => to.none(), - resolve: ({ snapshot }) => { + always: (to) => + to.none().resolve(({ snapshot }) => { expect(snapshot).type.toBe>() return undefined - } - }), + }), on: { - Advance: Machine.transition({ - target: (to) => to.local.LeftDone(), - resolve: ({ snapshot, target }) => { + Advance: (to) => + to.local.LeftDone().resolve(({ snapshot, target }) => { expect(snapshot).type.toBe>() expect(States.matches(snapshot, "Root.Right.RightIdle")).type.toBe() return target(new LeftDone({})) - } - }) + }) } } } @@ -115,13 +109,11 @@ describe("Machine transition snapshot context", () => { }, states: { Routing: { - choice: Machine.transition({ - target: (to) => to.local.Active(), - resolve: (context) => { + choice: (to) => + to.local.Active().resolve((context) => { expect(context).type.not.toHaveProperty("snapshot") return context.target(new Active({})) - } - }) + }) } } } diff --git a/typetest/machine/StructuralStates.tst.ts b/typetest/machine/StructuralStates.tst.ts index f49c84e..e3d7d1d 100644 --- a/typetest/machine/StructuralStates.tst.ts +++ b/typetest/machine/StructuralStates.tst.ts @@ -147,41 +147,36 @@ describe("structural active state types", () => { expect(state).type.toBe() }, on: { - Select: Machine.transition({ - target: (to) => { - expect(to.local).type.not.toHaveProperty("with") - return to.local.Loading() - }, - resolve: ({ containingState, ancestors, state, target }) => { + Select: (to) => { + expect(to.local).type.not.toHaveProperty("with") + return to.local.Loading().resolve(({ containingState, ancestors, state, target }) => { expect(state).type.toBe() expect(containingState).type.toBe() expect(ancestors).type.toBe<{}>() expect(target.from).type.toBeCallableWith({ url: "/song.mp3" }) expect(target.from).type.not.toBeCallableWith() return target.from({ url: "/song.mp3" }) - } - }) + }) + } } }, Loading: { on: { - Loaded: Machine.transition({ - target: (to) => to.local.Ready(), - resolve: ({ event, target }) => + Loaded: (to) => + to.local.Ready().resolve(({ event, target }) => target.from( { duration: event.duration }, (ready) => ready.Paused.from() ) - }) + ) } }, Ready: { states: { Paused: { on: { - Play: Machine.transition({ - target: (to) => to.local.with(), - resolve: ({ containingState, ancestors, state, target }) => { + Play: (to) => + to.local.with().resolve(({ containingState, ancestors, state, target }) => { expect(state).type.toBe() expect(containingState).type.toBe() expect(ancestors).type.toBe<{ readonly "player.transport.Ready": Ready }>() @@ -189,8 +184,7 @@ describe("structural active state types", () => { { duration: containingState.duration }, (ready) => ready.Playing.from({ position: 0 }) ) - } - }) + }) } } } diff --git a/typetest/testing/Coverage.tst.ts b/typetest/testing/Coverage.tst.ts index dce1168..11e89e8 100644 --- a/typetest/testing/Coverage.tst.ts +++ b/typetest/testing/Coverage.tst.ts @@ -21,10 +21,7 @@ describe("MachineTest coverage and observed graph", () => { }).handle({ idle: { on: { - Start: Machine.transition({ - target: (to) => to.full.done(), - resolve: ({ target }) => target(new Done({})) - }) + Start: (to) => to.full.done().resolve(({ target }) => target(new Done({}))) } }, done: {} diff --git a/typetest/testing/Exploration.tst.ts b/typetest/testing/Exploration.tst.ts index 69f3c7a..754dd55 100644 --- a/typetest/testing/Exploration.tst.ts +++ b/typetest/testing/Exploration.tst.ts @@ -22,14 +22,8 @@ describe("MachineTest exploration", () => { }).handle({ counter: { on: { - Increment: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - Internal: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + Increment: { target: Machine.targetless }, + Internal: { target: Machine.targetless } } } }) diff --git a/typetest/testing/Invariant.tst.ts b/typetest/testing/Invariant.tst.ts index 13dc25b..b534c97 100644 --- a/typetest/testing/Invariant.tst.ts +++ b/typetest/testing/Invariant.tst.ts @@ -20,14 +20,8 @@ describe("MachineTest invariants", () => { }).handle({ idle: { on: { - Tick: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - Internal: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + Tick: { target: Machine.targetless }, + Internal: { target: Machine.targetless } } } }) diff --git a/typetest/testing/MachineTest.tst.ts b/typetest/testing/MachineTest.tst.ts index e9ba05c..daee248 100644 --- a/typetest/testing/MachineTest.tst.ts +++ b/typetest/testing/MachineTest.tst.ts @@ -25,14 +25,8 @@ describe("MachineTest", () => { }).handle({ idle: { on: { - PublicEvent: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - InternalEvent: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + PublicEvent: { target: Machine.targetless }, + InternalEvent: { target: Machine.targetless } } } }) @@ -121,10 +115,7 @@ describe("MachineTest", () => { Effect.gen(function*() { yield* InvokeRequirement }), - onDone: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + onDone: { target: Machine.targetless } }) } }) diff --git a/typetest/testing/Probe.tst.ts b/typetest/testing/Probe.tst.ts index 0fcbbcc..f8d9cc4 100644 --- a/typetest/testing/Probe.tst.ts +++ b/typetest/testing/Probe.tst.ts @@ -22,14 +22,8 @@ describe("MachineTest probe", () => { }).handle({ State: { on: { - PublicEvent: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }), - InternalEvent: Machine.transition({ - target: (to) => to.none(), - resolve: () => undefined - }) + PublicEvent: { target: Machine.targetless }, + InternalEvent: { target: Machine.targetless } } } }) diff --git a/typetest/unstable/cluster/ClusterMachine.tst.ts b/typetest/unstable/cluster/ClusterMachine.tst.ts index 36cc2b4..fde6632 100644 --- a/typetest/unstable/cluster/ClusterMachine.tst.ts +++ b/typetest/unstable/cluster/ClusterMachine.tst.ts @@ -53,14 +53,9 @@ describe("ClusterMachine", () => { }).handle({ Count: { on: { - Increment: Machine.transition({ - target: (to) => to.full.Count(), - resolve: ({ event, state, target }) => target(new Count({ value: state.value + event.by })) - }), - Reset: Machine.transition({ - target: (to) => to.full.Count(), - resolve: ({ target }) => target(new Count({ value: 0 })) - }) + Increment: (to) => + to.full.Count().resolve(({ event, state, target }) => target(new Count({ value: state.value + event.by }))), + Reset: (to) => to.full.Count().resolve(({ target }) => target(new Count({ value: 0 }))) } } }) diff --git a/typetest/unstable/reactivity/AtomMachine.tst.ts b/typetest/unstable/reactivity/AtomMachine.tst.ts index f7a9e12..c213627 100644 --- a/typetest/unstable/reactivity/AtomMachine.tst.ts +++ b/typetest/unstable/reactivity/AtomMachine.tst.ts @@ -369,14 +369,8 @@ describe("AtomMachine", () => { }).handle({ Idle: { on: { - Tick: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ target }) => target(new Idle({})) - }), - InternalTick: Machine.transition({ - target: (to) => to.full.Idle(), - resolve: ({ target }) => target(new Idle({})) - }) + Tick: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({}))), + InternalTick: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({}))) } } })