Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const CounterDefinition = Machine.make({
id: "Counter",
states: States.states,
events: CounterEvent,
initial: (to) => to.Idle().resolve(({ target }) => target.from())
initial: (to) => to.Idle()
})

const Counter = CounterDefinition.handle({
Expand All @@ -81,7 +81,7 @@ const Counter = CounterDefinition.handle({
Running: {
on: {
Increment: (to) => to.full.Running().resolve(({ state, target }) => target.from({ count: state.count + 1 })),
Stop: (to) => to.full.Idle().resolve(({ target }) => target.from())
Stop: (to) => to.full.Idle()
}
}
})
Expand Down Expand Up @@ -235,7 +235,7 @@ const definition = Machine.make({
events: CommandEvent,
internalEvents: InternalEvent,
emittedEvents: Emissions,
initial: (to) => to.Idle().resolve(({ target }) => target.from())
initial: (to) => to.Idle()
})
```

Expand Down Expand Up @@ -347,7 +347,7 @@ const child = Machine.make({
states: ChildStates.states,
events: ChildEvents,
parent: Machine.parent(ParentEvents),
initial: (to) => to.Working().resolve(({ target }) => target.from())
initial: (to) => to.Working()
}).handle({
Working: {
on: {
Expand Down Expand Up @@ -398,9 +398,13 @@ paths. `parent` always means the owning machine target.
| `target.history` | Restoring a declared history node | The remembered configuration or its typed default |

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
builder. Return a bare selection such as `to.full.Idle()` when the selected
builder supports zero-argument construction; the machine applies the same
default construction as `target.from()`. This includes empty schemas and
schemas whose constructor fields are all optional or defaulted. TypeScript
rejects the bare form when state data or nested configuration is required.
Call `.resolve(...)` when construction depends on handler context or the
transition needs to enqueue commands. An absent handler ignores the trigger; `to.none` handles
it and retains queued commands, raised events, and emitted events without
selecting a destination. Concrete destinations stay narrowed inside their
resolver, and `to.branches({...})` gives the resolver only the declared named
Expand Down
21 changes: 12 additions & 9 deletions docs/agent-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ const States = Machine.states({
const machine = Machine.make({
states: States.states,
events: Machine.events(),
initial: (to) => to.Done().resolve(({ target }) => target.from())
initial: (to) => to.Done()
}).handle({
Done: {
output: () => "done"
Expand Down Expand Up @@ -483,11 +483,11 @@ checkout: {
Target it without a value:

```ts
Resume: (to) => to.history.checkout.exact.resolve(({ target }) => target())
Resume: (to) => to.history.checkout.exact
```

Each declared history leaf is a topology value; the resolver's selected
history builder remains callable to construct restoration evidence.
Each declared history leaf is a topology value and can be returned directly
when no resolver work is needed.

Deep history restores the complete remembered subtree and its decoded values.
Shallow history restores only parent and direct-child values. If the remembered
Expand Down Expand Up @@ -569,8 +569,11 @@ 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:
When no resolver is needed and the selected builder supports zero-argument
construction, return the selected target directly. This applies the same
default construction as `target.from()`; the compiler rejects the shorthand
when state data or nested configuration is required. Append `.reenter()` only
when restart semantics are intentional:

```ts
Finish: (to) => to.full.Done()
Expand Down Expand Up @@ -949,7 +952,7 @@ const definition = Machine.make({
states: States.states,
events: Events,
internalEvents: InternalEvents,
initial: (to) => to.Idle().resolve(({ target }) => target.from())
initial: (to) => to.Idle()
})
```

Expand Down Expand Up @@ -1117,7 +1120,7 @@ machine.handle({
Waiting: {
invoke: (from) =>
from.timer("clear-status", "3 seconds")
.onDone((to) => to.full.Clear().resolve(({ target }) => target()))
.onDone((to) => to.full.Clear())
}
})
```
Expand Down Expand Up @@ -1485,7 +1488,7 @@ reference model when correctness of the expected behavior matters.
Select the initial root separately from constructing its value:

```ts
initial: (to) => to.Idle().resolve(({ target }) => target.from())
initial: (to) => to.Idle()
```

### Invoked child expects events not accepted by the parent
Expand Down
10 changes: 3 additions & 7 deletions examples/platformer/src/machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,16 +331,12 @@ export const CharacterMachine = Machine.make({
states: {
AirJumpGroundLock: {
invoke: (from) =>
from.timer("ground-air-jump-unlock", "120 millis").onDone((to) =>
to.local.AirJumpReady().resolve(({ target }) => target.from())
),
from.timer("ground-air-jump-unlock", "120 millis").onDone((to) => to.local.AirJumpReady()),
on: {}
},
AirJumpWallLock: {
invoke: (from) =>
from.timer("wall-air-jump-unlock", "240 millis").onDone((to) =>
to.local.AirJumpReady().resolve(({ target }) => target.from())
),
from.timer("wall-air-jump-unlock", "240 millis").onDone((to) => to.local.AirJumpReady()),
on: {}
},
AirJumpReady: {
Expand All @@ -361,7 +357,7 @@ export const CharacterMachine = Machine.make({
},
Paused: {
on: {
Resume: (to) => to.history.Character.locomotion.Playing.resume.resolve(({ target }) => target())
Resume: (to) => to.history.Character.locomotion.Playing.resume
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions examples/playground/src/examples/microwave/machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const MicrowaveMachine = Machine.make({
states: {
Closed: {
on: {
DoorOpened: (to) => to.branch.Oven.Open().resolve(({ target }) => target.from())
DoorOpened: (to) => to.branch.Oven.Open()
},
states: {
Idle: {
Expand All @@ -56,7 +56,7 @@ export const MicrowaveMachine = Machine.make({
)
),
on: {
PowerPressed: (to) => to.local.Idle().resolve(({ target }) => target.from())
PowerPressed: (to) => to.local.Idle()
}
}
}
Expand Down
28 changes: 8 additions & 20 deletions examples/playground/src/examples/traffic-light/machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,42 +25,30 @@ export const TrafficLightMachine = Machine.make({
id: "TrafficLight",
states: TrafficLightStates.states,
events: TrafficLightEvents,
initial: (to) => to.Red().resolve(({ target }) => target.from())
initial: (to) => to.Red()
}).handle({
Red: {
invoke: (from) =>
from.timer("red-timer", trafficLightDurations.Red).onDone((to) =>
to.full.RedYellow().resolve(({ target }) => target.from())
),
invoke: (from) => from.timer("red-timer", trafficLightDurations.Red).onDone((to) => to.full.RedYellow()),
on: {
Reset: (to) => to.full.Red().resolve(({ target }) => target.from(), { reenter: true })
}
},
RedYellow: {
invoke: (from) =>
from.timer("red-yellow-timer", trafficLightDurations.RedYellow).onDone((to) =>
to.full.Green().resolve(({ target }) => target.from())
),
invoke: (from) => from.timer("red-yellow-timer", trafficLightDurations.RedYellow).onDone((to) => to.full.Green()),
on: {
Reset: (to) => to.full.Red().resolve(({ target }) => target.from())
Reset: (to) => to.full.Red()
}
},
Green: {
invoke: (from) =>
from.timer("green-timer", trafficLightDurations.Green).onDone((to) =>
to.full.Yellow().resolve(({ target }) => target.from())
),
invoke: (from) => from.timer("green-timer", trafficLightDurations.Green).onDone((to) => to.full.Yellow()),
on: {
Reset: (to) => to.full.Red().resolve(({ target }) => target.from())
Reset: (to) => to.full.Red()
}
},
Yellow: {
invoke: (from) =>
from.timer("yellow-timer", trafficLightDurations.Yellow).onDone((to) =>
to.full.Red().resolve(({ target }) => target.from())
),
invoke: (from) => from.timer("yellow-timer", trafficLightDurations.Yellow).onDone((to) => to.full.Red()),
on: {
Reset: (to) => to.full.Red().resolve(({ target }) => target.from())
Reset: (to) => to.full.Red()
}
}
})
6 changes: 3 additions & 3 deletions examples/playground/src/examples/turnstile/machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ export const TurnstileMachine = Machine.make({
id: "Turnstile",
states: TurnstileStates.states,
events: TurnstileEvents,
initial: (to) => to.Locked().resolve(({ target }) => target.from())
initial: (to) => to.Locked()
}).handle({
Locked: {
on: {
CoinInserted: (to) => to.full.Unlocked().resolve(({ target }) => target.from())
CoinInserted: (to) => to.full.Unlocked()
}
},
Unlocked: {
on: {
GatePushed: (to) => to.full.Locked().resolve(({ target }) => target.from())
GatePushed: (to) => to.full.Locked()
}
}
})
8 changes: 4 additions & 4 deletions examples/pokemon/src/machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const ReplaceChild = Machine.child("replace", ReplaceMachine)
export const machine = Machine.make({
states: States.states,
events: TeamEvents,
initial: (to) => to.Loading().resolve(({ target }) => target.from())
initial: (to) => to.Loading()
}).handle({
Loading: {
invoke: (from) =>
Expand All @@ -29,14 +29,14 @@ export const machine = Machine.make({
const service = yield* PokemonService
return yield* service.getRandomTeam()
})).onDone((to) => to.full.ActiveTeam().resolve(({ output, target }) => target.from({ team: output })))
.onFailure((to) => to.full.Failed().resolve(({ target }) => target.from()))
.onFailure((to) => to.full.Failed())
},
ActiveTeam: {
invoke: (
from
) => [
from.child(SelectionChild).onFailure((to) => to.full.Failed().resolve(({ target }) => target.from())),
from.child(ReplaceChild).onFailure((to) => to.full.Failed().resolve(({ target }) => target.from()))
from.child(SelectionChild).onFailure((to) => to.full.Failed()),
from.child(ReplaceChild).onFailure((to) => to.full.Failed())
],
on: {
ReplaceInTeam: (to) =>
Expand Down
4 changes: 2 additions & 2 deletions examples/pokemon/src/machines/replace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const ReplaceMachine = Machine.make({
events: ReplaceEvents,
internalEvents: ReplaceInternalEvents,
parent: Machine.parent(TeamEvents),
initial: (to) => to.Idle().resolve(({ target }) => target.from())
initial: (to) => to.Idle()
}).handle({
Idle: {
on: {
Expand All @@ -45,7 +45,7 @@ export const ReplaceMachine = Machine.make({
to.none.resolve(({ output }, enqueue) => {
enqueue.raise(output)
})
).onFailure((to) => to.full.Idle().resolve(({ target }) => target.from())),
).onFailure((to) => to.full.Idle()),
on: {
Replaced: (to) =>
to.full.Idle().resolve(({ event, parent, state, target }, enqueue) => {
Expand Down
2 changes: 1 addition & 1 deletion examples/pokemon/src/machines/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export const SelectionMachine = Machine.make({
to.none.resolve(({ output }, enqueue) => {
enqueue.raise(output)
})
).onFailure((to) => to.local.NoPokemon().resolve(({ target }) => target.from())),
).onFailure((to) => to.local.NoPokemon()),
on: {
SearchResult: (to) =>
to.branches({
Expand Down