diff --git a/.changeset/clear-state-construction.md b/.changeset/clear-state-construction.md new file mode 100644 index 0000000..db306cc --- /dev/null +++ b/.changeset/clear-state-construction.md @@ -0,0 +1,21 @@ +--- +"@typeonce/effect-machine": minor +--- + +Make state construction modes explicit and allow one topology target to replace a retained valued owner atomically. + +Valued builders are no longer callable. Replace `target(value)` and nested `builder(value, ...)` calls with `.decoded(value, ...)`; keep `.from(input, ...)` for schema make input. Plain state-update resolvers now expose the decoded owner as `current` and its construction builder as `owner`, replacing the previous `ancestors` plus `target` pattern. + +Declare a combined transition with `.updating(ownerSelector)`. The resolver must finish destination construction with `.update(...)`, so the owner replacement cannot be omitted: + +```ts +to.local.SavingPlan() + .updating(to.branch.Ready) + .resolve(({ current, owner, target }) => + target.from({ request }).update( + owner.decoded(new Ready({ ...current, notice: null })) + ) + ) +``` + +Transition inspection and retained microsteps now include an `updates` array naming replaced owners. diff --git a/README.md b/README.md index 183e3e6..63d8b47 100644 --- a/README.md +++ b/README.md @@ -149,16 +149,26 @@ parallel root. ### Construct state through builders -Use `.from(...)` when constructing a new state from fields: +Use `.from(...)` when constructing a new state from schema make input: ```ts target.from({ draft: event.draft }) ``` The machine runs these inputs through the state schema while planning. Schema -defaults, refinements, and tagged-class identity are therefore preserved, and -decode failures remain typed machine failures. Pass a value directly only when -it is already decoded. +defaults, transformations, refinements, and tagged-class identity are +therefore preserved, and decode failures remain typed machine failures. This +is the default construction path. + +Use `.decoded(...)` when the value is already a `Schema.Type`: + +```ts +target.decoded(new Ready({ notice: null })) +``` + +The machine still validates the value against the schema's type side. It does +not run encoded-input transformations again. State builders are not callable; +the method name always makes the construction mode visible. When sibling states share fields, remove the source discriminator and pass the remaining fields through the target schema: @@ -190,7 +200,7 @@ const States = Machine.states({ const definition = Machine.make({ states: States.states, events: Machine.events(), - initial: (to) => to.Form.initial.resolve(({ target }) => target((form) => form.Editing.from())) + initial: (to) => to.Form.initial.resolve(({ target }) => target.from((form) => form.Editing.from())) }) ``` @@ -428,9 +438,9 @@ compound scope without rebuilding its active child. Use handler source: ```ts -Increment: ; -;((to) => - to.branch.root.session.update(({ ancestors, target }) => target.from({ count: ancestors["root.session"].count + 1 }))) +const handlers = { + Increment: (to) => to.branch.root.session.update(({ current, owner }) => owner.from({ count: current.count + 1 })) +} ``` The update keeps the exact active descendants, their values, history records, @@ -438,8 +448,8 @@ completion outputs, and unrelated parallel regions. It runs no exit or entry actions and does not restart state-owned work. Eventless stabilization still runs, so an `always` transition can react to the new value. -`update` is callable when used directly and is also a static selection for a -named branch: +The plain update method remains useful when topology does not change. It is +also a static selection for a named branch: ```ts to.branches({ @@ -452,10 +462,69 @@ to.branches({ ) ``` -The resolver must return `target(value)` or `target.from(input)`. It may return -`decline()` only with `{ declinable: true }`. Pass `{ reenter: true }` on event -or invocation transitions when the handler source should exit and enter again. -Reentry applies to that source, not to the ancestor whose value changed. +### Change topology and a retained owner together + +When a transition enters another child and also replaces a valued ancestor +that stays active, declare both operations on the same target: + +```ts +const handlers = { + CreatePlan: (to) => + to.local.SavingPlan() + .updating(to.branch.Ready) + .resolve(({ current, event, owner, target }) => + target.from({ + request: { _tag: "Create", input: event.input } + }).update( + owner.decoded(new Ready({ ...current, notice: null })) + ) + ) +} +``` + +`to.local.SavingPlan()` selects topology. `.updating(to.branch.Ready)` names +the retained valued owner and makes its replacement mandatory: the resolver +does not type-check unless destination construction finishes with +`.update(...)`. `current` is that owner's decoded value from the +pre-transition snapshot. `target` constructs the destination; `owner` +constructs the complete replacement owner value. + +The topology change and owner replacement apply atomically in one microstep. +The owner does not exit or reenter, its work is not restarted, and destination +entry actions observe the new owner value. Eventless stabilization follows. +Only one retained owner may be replaced by a combined target. A `full` target, +or any target that exits the selected owner, does not expose `.updating`. +Combined updates use a direct resolver in this release; named branches continue +to support value-only updates. + +For a schema-less destination, construction remains explicit: + +```ts +to.local.Idle() + .updating(to.branch.Ready) + .resolve(({ current, output, owner, target }) => + target.from().update( + owner.decoded( + new Ready({ + ...current, + day: output, + notice: "Plan changed." + }) + ) + ) + ) +``` + +Both values derive from the same pre-transition snapshot and are validated +before lifecycle actions run. Competing transitions that write the same owner +conflict; document order and hierarchy select one writer rather than applying +last-write-wins behavior. + +The resolver must return `target.decoded(value)` or `target.from(input)`. It +may return `decline()` only with `{ declinable: true }`. Pass `{ reenter: true }` +on event or invocation transitions when the handler source should exit and +enter again. Reentry applies to that source, not to the ancestor whose value +changed. The selector omits `update` for schema-less scopes, atomic and final states, inactive branches, parallel sibling regions, and choice resolvers. Updating a diff --git a/docs/agent-guide.md b/docs/agent-guide.md index 79af6ba..8219f22 100644 --- a/docs/agent-guide.md +++ b/docs/agent-guide.md @@ -67,9 +67,12 @@ Each step has one job: Chain `.handle` from `Machine.make`. Do not store the intermediate definition when the module exports one machine implementation. -State builders construct the next snapshot. Use `.from(...)` when a state owns -data. The machine validates that input through the state schema while it plans -the transition. +State builders construct the next snapshot. Use `.from(...)` for schema make +input; defaults, transformations, and refinements run while the machine plans +the transition. Use `.decoded(...)` only for an existing `Schema.Type`. It is +validated against the type side without rerunning encoded transformations. +Valued state builders are not callable, so the construction mode is always +visible. Schema-less state construction uses `.from()`. The examples below show one modeling decision at a time. They omit unchanged state and event declarations already shown above. @@ -393,8 +396,8 @@ update selection instead of reconstructing its active descendants: ```ts Changed: (to) => - to.local.update(({ ancestors, target }) => - target.from({ revision: ancestors.document.revision + 1 }) + to.local.update(({ current, owner }) => + owner.from({ revision: current.revision + 1 }) ) ``` @@ -405,6 +408,34 @@ configuration. Neither runs lifecycle actions or restarts state-owned work by default. Use an event handled inside a parallel sibling when that sibling owns the value that must change. +When topology changes and one valued ancestor remains active but needs a new +value, declare the owner on the destination: + +```ts +CreatePlan: (to) => + to.local.SavingPlan() + .updating(to.branch.Ready) + .resolve(({ current, event, owner, target }) => + target.from({ request: event.input }).update( + owner.decoded(new Ready({ ...current, notice: null })) + ) + ) +``` + +`.updating(...)` accepts the retained state selector itself. It makes the +owner replacement mandatory at compile time: the resolver must finish a +destination construction with `.update(...)`. `current` is the owner's +decoded pre-transition value. `target` constructs the destination and `owner` +constructs the complete replacement value. + +Both instructions are validated and applied atomically. The retained owner is +not reentered, destination entry sees its new value, and eventless +stabilization runs afterward. Only local and branch targets that retain the +owner expose `.updating`; full targets do not. Combined targets support one +owner. Keep separate domain changes explicit by constructing the complete +owner value instead of relying on a partial merge helper. Combined updates use +a direct resolver; named branches support value-only updates. + ## Test paths and invariants Test the statechart as a graph. Send domain events, inspect reached states, and diff --git a/perf/runtime/counter.mjs b/perf/runtime/counter.mjs index 08cdb19..f9f39d2 100644 --- a/perf/runtime/counter.mjs +++ b/perf/runtime/counter.mjs @@ -52,7 +52,7 @@ export const counterMachine = Machine.make({ events: benchmarkApi.events(CounterEvent.cases.Increment, CounterEvent.cases.Finish), initial: benchmarkApi.initial({ target: (to) => to.Count(), - resolve: ({ target }) => target(CounterState.cases.Count.make({ value: 0 })) + resolve: ({ target }) => target.from(CounterState.cases.Count.make({ value: 0 })) }, () => CounterStates.initial.Count.from({ value: 0 })) }).handle({ Count: { diff --git a/perf/types/adapter-readiness-control.ts b/perf/types/adapter-readiness-control.ts index a5ef530..bca06f2 100644 --- a/perf/types/adapter-readiness-control.ts +++ b/perf/types/adapter-readiness-control.ts @@ -29,18 +29,18 @@ export const machine = Machine.make({ id: "perf-readiness", states: States.states, events: Machine.events(), - initial: (to) => to.Ready().resolve(({ target }) => target(Ready.make({}))) + initial: (to) => to.Ready().resolve(({ target }) => target.from(Ready.make({}))) }).handle({ Flow: { history: { recent: { - default: ({ target }) => target.Flow(Flow.make({}), (flow) => flow.Idle(Idle.make({}))) + default: ({ target }) => target.Flow.from(Flow.make({}), (flow) => flow.Idle.from(Idle.make({}))) } }, states: { Idle: {}, Route: { - choice: (to) => to.full.Ready().resolve(({ target }) => target(Ready.make({}))) + choice: (to) => to.full.Ready().resolve(({ target }) => target.from(Ready.make({}))) } } }, diff --git a/perf/types/composition-control.ts b/perf/types/composition-control.ts index 2705f2c..60fd7aa 100644 --- a/perf/types/composition-control.ts +++ b/perf/types/composition-control.ts @@ -67,13 +67,13 @@ export const machine = Machine.make({ events: Machine.events(), initial: (to) => to.App.initial.resolve(({ target }) => - target(App.make({}), (app) => - app.Workspace( + target.from(App.make({}), (app) => + app.Workspace.from( Workspace.make({}), (workspace) => workspace - .Editor(Editor.make({}), (editor) => editor.Editing(Editing.make({}))) - .Sync(Sync.make({}), (sync) => sync.Idle(SyncIdle.make({}))) + .Editor.from(Editor.make({}), (editor) => editor.Editing.from(Editing.make({}))) + .Sync.from(Sync.make({}), (sync) => sync.Idle.from(SyncIdle.make({}))) )) ) }) diff --git a/perf/types/composition.ts b/perf/types/composition.ts index e0cb96f..a4e0e84 100644 --- a/perf/types/composition.ts +++ b/perf/types/composition.ts @@ -31,13 +31,13 @@ const handled = machine.handle({ history: { recent: { default: ({ target }) => - target.App(App.make({}), (app) => - app.Workspace( + target.App.from(App.make({}), (app) => + app.Workspace.from( Workspace.make({}), (workspace) => workspace - .Editor(Editor.make({}), (editor) => editor.Editing(Editing.make({}))) - .Sync(Sync.make({}), (sync) => sync.Idle(SyncIdle.make({}))) + .Editor.from(Editor.make({}), (editor) => editor.Editing.from(Editing.make({}))) + .Sync.from(Sync.make({}), (sync) => sync.Idle.from(SyncIdle.make({}))) )) } }, @@ -69,13 +69,13 @@ const handled = machine.handle({ Route: { choice: (to) => to.full.App().resolve(({ target }) => - target(App.make({}), (app) => - app.Workspace( + target.from(App.make({}), (app) => + app.Workspace.from( Workspace.make({}), (workspace) => workspace - .Editor(Editor.make({}), (editor) => editor.Editing(Editing.make({}))) - .Sync(Sync.make({}), (sync) => sync.Idle(SyncIdle.make({}))) + .Editor.from(Editor.make({}), (editor) => editor.Editing.from(Editing.make({}))) + .Sync.from(Sync.make({}), (sync) => sync.Idle.from(SyncIdle.make({}))) )) ) } diff --git a/perf/types/definition-variants-control.ts b/perf/types/definition-variants-control.ts index 1078582..b4f4095 100644 --- a/perf/types/definition-variants-control.ts +++ b/perf/types/definition-variants-control.ts @@ -34,5 +34,6 @@ export const States = Machine.states({ export const machine = Machine.make({ states: States.states, events: Machine.events(Start, Finish), - initial: (to) => to.Flow.initial.resolve(({ target }) => target(Flow.make({}), (flow) => flow.Idle(Idle.make({})))) + initial: (to) => + to.Flow.initial.resolve(({ target }) => target.from(Flow.make({}), (flow) => flow.Idle.from(Idle.make({})))) }) diff --git a/perf/types/definition-variants.ts b/perf/types/definition-variants.ts index 4c8b981..a1b7ac5 100644 --- a/perf/types/definition-variants.ts +++ b/perf/types/definition-variants.ts @@ -11,21 +11,21 @@ const complete = machine.handle({ Flow: { history: { recent: { - default: ({ target }) => target.Flow(Flow.make({}), (flow) => flow.Idle(Idle.make({}))) + default: ({ target }) => target.Flow.from(Flow.make({}), (flow) => flow.Idle.from(Idle.make({}))) } }, states: { Route: { - choice: (to) => to.local.Idle().resolve(({ target }) => target(Idle.make({}))) + choice: (to) => to.local.Idle().resolve(({ target }) => target.from(Idle.make({}))) }, Idle: { on: { - Start: (to) => to.local.Running().resolve(({ target }) => target(Running.make({}))) + Start: (to) => to.local.Running().resolve(({ target }) => target.from(Running.make({}))) } }, Running: { on: { - Finish: (to) => to.local.Done().resolve(({ event, target }) => target(Done.make({ value: event.value }))) + Finish: (to) => to.local.Done().resolve(({ event, target }) => target.from(Done.make({ value: event.value }))) } }, Done: { @@ -40,7 +40,7 @@ const idleOnly = machine.handle({ states: { Idle: { on: { - Start: (to) => to.local.Running().resolve(({ target }) => target(Running.make({}))) + Start: (to) => to.local.Running().resolve(({ target }) => target.from(Running.make({}))) } } } @@ -52,7 +52,7 @@ const runningOnly = machine.handle({ states: { Running: { on: { - Finish: (to) => to.local.Done().resolve(({ event, target }) => target(Done.make({ value: event.value }))) + Finish: (to) => to.local.Done().resolve(({ event, target }) => target.from(Done.make({ value: event.value }))) } } } diff --git a/perf/types/dynamic-invoke-control.ts b/perf/types/dynamic-invoke-control.ts index a74c200..4f2ebf5 100644 --- a/perf/types/dynamic-invoke-control.ts +++ b/perf/types/dynamic-invoke-control.ts @@ -14,5 +14,5 @@ export const loadUser = (userId: string) => Effect.fail(new LoadError()).pipe(Ef export const machine = Machine.make({ states: States.states, events: Machine.events(), - initial: (to) => to.Loading().resolve(({ target }) => target(Loading.make({ userId: "user-1" }))) + initial: (to) => to.Loading().resolve(({ target }) => target.from(Loading.make({ userId: "user-1" }))) }) diff --git a/perf/types/exact-channels-control.ts b/perf/types/exact-channels-control.ts index b8f1e28..deff5fb 100644 --- a/perf/types/exact-channels-control.ts +++ b/perf/types/exact-channels-control.ts @@ -23,5 +23,5 @@ export const machine = Machine.make({ internalEvents: Machine.internalEvents(Loaded), emittedEvents: Machine.emittedEvents(Notice), input: Input, - initial: (to) => to.Idle().resolve(({ input, target }) => target(Idle.make({ value: input.seed }))) + initial: (to) => to.Idle().resolve(({ input, target }) => target.from(Idle.make({ value: input.seed }))) }) diff --git a/perf/types/exact-channels.ts b/perf/types/exact-channels.ts index 4032afb..ecd59a7 100644 --- a/perf/types/exact-channels.ts +++ b/perf/types/exact-channels.ts @@ -15,9 +15,9 @@ const complete = machine.handle({ Start: (to) => to.full.Done().resolve(({ event, target }, enqueue) => { enqueue.emit(Notice.make({ value: event.value })) - return target(Done.make({ value: event.value })) + return target.from(Done.make({ value: event.value })) }), - Loaded: (to) => to.full.Done().resolve(({ event, target }) => target(Done.make({ value: event.value }))) + Loaded: (to) => to.full.Done().resolve(({ event, target }) => target.from(Done.make({ value: event.value }))) } }, Done: { diff --git a/perf/types/handle.ts b/perf/types/handle.ts index 455a8db..2ceb7b3 100644 --- a/perf/types/handle.ts +++ b/perf/types/handle.ts @@ -17,17 +17,17 @@ const States = Machine.states(State.cases) const machine = Machine.make({ states: States.states, events: Machine.events(Event.cases.Start, Event.cases.Finish), - initial: (to) => to.Idle().resolve(({ target }) => target(State.cases.Idle.make({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.from(State.cases.Idle.make({}))) }).handle({ Idle: { on: { - Start: (to) => to.full.Running().resolve(({ target }) => target(State.cases.Running.make({}))) + Start: (to) => to.full.Running().resolve(({ target }) => target.from(State.cases.Running.make({}))) } }, Running: { on: { Finish: (to) => - to.full.Done().resolve(({ event, target }) => target(State.cases.Done.make({ value: event.value }))) + to.full.Done().resolve(({ event, target }) => target.from(State.cases.Done.make({ value: event.value }))) } }, Done: {} diff --git a/perf/types/make.ts b/perf/types/make.ts index 7a11071..7e7a3b4 100644 --- a/perf/types/make.ts +++ b/perf/types/make.ts @@ -17,7 +17,7 @@ const States = Machine.states(State.cases) const machine = Machine.make({ states: States.states, events: Machine.events(Event.cases.Start, Event.cases.Finish), - initial: (to) => to.Idle().resolve(({ target }) => target(State.cases.Idle.make({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.from(State.cases.Idle.make({}))) }) void machine diff --git a/perf/types/named-branches-control.ts b/perf/types/named-branches-control.ts index ea81768..3904d64 100644 --- a/perf/types/named-branches-control.ts +++ b/perf/types/named-branches-control.ts @@ -13,5 +13,5 @@ export const States = Machine.states(State.cases) export const machine = Machine.make({ states: States.states, events: Machine.events(Route), - initial: (to) => to.Idle().resolve(({ target }) => target(State.cases.Idle.make({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.from(State.cases.Idle.make({}))) }) diff --git a/perf/types/named-branches.ts b/perf/types/named-branches.ts index 4ddae2e..3ff2db1 100644 --- a/perf/types/named-branches.ts +++ b/perf/types/named-branches.ts @@ -21,25 +21,25 @@ const handled = machine.handle({ const value = event.value switch (value.length) { case 1: - return select.length1(State.cases.Text.make({ value })) + return select.length1.from(State.cases.Text.make({ value })) case 2: - return select.length2(State.cases.Count.make({ value: value.length })) + return select.length2.from(State.cases.Count.make({ value: value.length })) case 3: - return select.length3(State.cases.Text.make({ value })) + return select.length3.from(State.cases.Text.make({ value })) case 4: - return select.length4(State.cases.Count.make({ value: value.length })) + return select.length4.from(State.cases.Count.make({ value: value.length })) case 5: - return select.length5(State.cases.Text.make({ value })) + return select.length5.from(State.cases.Text.make({ value })) case 6: return select.length6() case 7: - return select.length7(State.cases.Count.make({ value: value.length })) + return select.length7.from(State.cases.Count.make({ value: value.length })) case 8: - return select.length8(State.cases.Text.make({ value: value.toUpperCase() })) + return select.length8.from(State.cases.Text.make({ value: value.toUpperCase() })) case 9: - return select.length9(State.cases.Count.make({ value: value.length })) + return select.length9.from(State.cases.Count.make({ value: value.length })) case 10: - return select.length10(State.cases.Idle.make({})) + return select.length10.from(State.cases.Idle.make({})) default: return select.unchanged() } diff --git a/scripts/fixtures/consumer/consumer.ts b/scripts/fixtures/consumer/consumer.ts index b75b39c..e96a86b 100644 --- a/scripts/fixtures/consumer/consumer.ts +++ b/scripts/fixtures/consumer/consumer.ts @@ -27,7 +27,7 @@ const machine = Machine.make({ states: States.states, events: PublicEvents, internalEvents: InternalEvents, - initial: (to) => to.Idle().resolve(({ target }) => target(State.cases.Idle.make({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(State.cases.Idle.make({}))) }).handle({ Idle: { on: { @@ -37,7 +37,7 @@ const machine = Machine.make({ 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.decoded(State.cases.Loading.make({}))) } }, Loading: { @@ -47,7 +47,7 @@ const machine = Machine.make({ ], on: { Loaded: (to) => - to.full.Done().resolve(({ event, target }) => target(State.cases.Done.make({ value: event.value }))) + to.full.Done().resolve(({ event, target }) => target.decoded(State.cases.Done.make({ value: event.value }))) } }, Done: {} diff --git a/scripts/fixtures/consumer/deep-bound.ts b/scripts/fixtures/consumer/deep-bound.ts index 10c68eb..c67ae44 100644 --- a/scripts/fixtures/consumer/deep-bound.ts +++ b/scripts/fixtures/consumer/deep-bound.ts @@ -49,7 +49,8 @@ const childMachine = Machine.make({ events: Machine.events(), parent: Machine.parent(ChildParentEvents), input: Schema.Struct({ value: Schema.String }), - initial: (to) => to.Done().resolve(({ input, target }) => target(ChildState.cases.Done.make({ value: input.value }))) + initial: (to) => + to.Done().resolve(({ input, target }) => target.decoded(ChildState.cases.Done.make({ value: input.value }))) }).handle({ Done: { entry: ({ parent, state }, enqueue) => { @@ -90,7 +91,7 @@ const definition = Machine.make({ internalEvents: Machine.internalEvents(Internal.cases.Loaded, Internal.cases.ChildCompleted), emittedEvents: Emissions, input: Schema.Struct({ seed: Schema.String }), - initial: (to) => to.Idle().resolve(({ input: { seed: _seed }, target }) => target(State.cases.Idle.make({}))) + initial: (to) => to.Idle().resolve(({ input: { seed: _seed }, target }) => target.decoded(State.cases.Idle.make({}))) }) const machine = definition.handle({ Idle: { @@ -98,12 +99,12 @@ const machine = definition.handle({ on: { Begin: (to) => to.full.Ready().resolve(({ target }) => - target( + target.decoded( State.cases.Ready.make({}), (ready) => - ready.Editor( + ready.Editor.decoded( State.cases.Editor.make({}), - (editor) => editor.Editing(State.cases.Editing.make({ value: "ready" })) + (editor) => editor.Editing.decoded(State.cases.Editing.make({ value: "ready" })) ) ) ) @@ -117,7 +118,7 @@ const machine = definition.handle({ on: { Save: (to) => to.local.Saving().resolve(({ event, target }) => - target(State.cases.Saving.make({ value: event.value })) + target.decoded(State.cases.Saving.make({ value: event.value })) ), Loaded: (to) => to.none } @@ -129,10 +130,12 @@ const machine = definition.handle({ 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 })) + return target.decoded(State.cases.Saving.make({ value: event.value })) }), ChildCompleted: (to) => - to.full.Done().resolve(({ event, target }) => target(State.cases.Done.make({ value: event.value }))) + to.full.Done().resolve(({ event, target }) => + target.decoded(State.cases.Done.make({ value: event.value })) + ) } } } diff --git a/src/Machine.ts b/src/Machine.ts index 23d4f41..48bf81f 100644 --- a/src/Machine.ts +++ b/src/Machine.ts @@ -286,7 +286,7 @@ export interface Definition< * on: { * Increment: (to) => * to.full.Count().resolve(({ event, state, target }) => - * target(new Count({ value: state.value + event.by }))) + * target.decoded(new Count({ value: state.value + event.by }))) * } * } * }) @@ -972,6 +972,16 @@ type FromMethod, Result> = { readonly from: FromCallable> } +type DecodedMethod, Result> = { + /** + * Constructs the selected state from an already-decoded schema value. The + * machine validates the value against the schema's type side while planning. + * + * @since 0.22.0 + */ + readonly decoded: (...args: Arguments) => Result +} + type ConstructionResult = Result | Machine.StateConstruction type UnwrapConstruction = Result extends Machine.StateConstruction ? Value : Result @@ -997,9 +1007,7 @@ type NodeBuilderMethod< > = Machine.NodeSchema extends never ? { readonly from: FromCallable } - : - & ((...args: Arguments) => Result) - & { readonly from: FromCallable } + : DecodedMethod & { readonly from: FromCallable } type NodeMethod< Node, @@ -1007,7 +1015,7 @@ type NodeMethod< Result, FromArguments extends ReadonlyArray > = Machine.NodeSchema extends never ? FromMethod - : ((...args: Arguments) => Result) & FromMethod + : DecodedMethod & FromMethod type NodeMethodWithInitial< Node, @@ -1020,7 +1028,7 @@ type NodeMethodWithInitial< readonly initial: InitialTargetFactory } : - & ((...args: Arguments) => Result) + & DecodedMethod & { readonly from: FromCallable> readonly initial: InitialTargetFactory @@ -1038,11 +1046,11 @@ interface InitialTargetFactory< Node, Path extends string > { - (...args: WithNodeValue): Machine.InitialTarget readonly from: FromCallable< WithNodeInput, Machine.StateConstruction> > + readonly decoded: (...args: WithNodeValue) => Machine.InitialTarget } type NodeConstructionSelectorFromCallable = Machine.NodeSchema extends never ? { @@ -1056,15 +1064,14 @@ type NestedTargetMethod = Machine.No readonly from: NodeConstructionSelectorFromCallable readonly initial: InitialTargetFactory } - : - & (>( + : { + readonly decoded: >( value: NodeValue, state: (builder: Builder) => Selected - ) => Selected) - & { - readonly from: NodeConstructionSelectorFromCallable - readonly initial: InitialTargetFactory - } + ) => Selected + readonly from: NodeConstructionSelectorFromCallable + readonly initial: InitialTargetFactory + } type ConstructionSelectorFromCallable = {} extends Input ? { >( @@ -1100,9 +1107,10 @@ type InitialSnapshotMethod< InitialSnapshotResult > : - & (( - ...args: InitialSnapshotArguments - ) => InitialSnapshotResult) + & DecodedMethod< + InitialSnapshotArguments, + InitialSnapshotResult + > & FromMethod< InitialSnapshotFromArguments, InitialSnapshotResult @@ -1242,9 +1250,10 @@ type FullSnapshotMethod< FullSnapshotResult > : - & (( - ...args: FullSnapshotArguments - ) => FullSnapshotResult) + & DecodedMethod< + FullSnapshotArguments, + FullSnapshotResult + > & FromMethod< FullSnapshotFromArguments, FullSnapshotResult @@ -1416,9 +1425,10 @@ type HistorySnapshotMethod< HistorySnapshotResult > : - & (( - ...args: HistorySnapshotArguments - ) => HistorySnapshotResult) + & DecodedMethod< + HistorySnapshotArguments, + HistorySnapshotResult + > & FromMethod< HistorySnapshotFromArguments, HistorySnapshotResult @@ -1645,20 +1655,19 @@ type LocalTargetBuilderForScope< * * @since 0.4.0 */ - readonly with: - & (>>( + readonly with: { + readonly decoded: >>( value: Machine.StateByIdentifier, state: ( builder: LocalTargetBuilderWithPrefix ) => Result - ) => Result) - & { - readonly from: ConstructionSelectorFromCallable< - Machine.SchemaByIdentifier["~type.make.in"], - LocalTargetBuilderWithPrefix, - LocalTargetResultWithPrefix - > - } + ) => Result + readonly from: ConstructionSelectorFromCallable< + Machine.SchemaByIdentifier["~type.make.in"], + LocalTargetBuilderWithPrefix, + LocalTargetResultWithPrefix + > + } } : {}) : {} @@ -3557,6 +3566,7 @@ export declare namespace Machine { readonly type: "direct" readonly target: Path | undefined readonly selection: TransitionTargetSelection + readonly updates: ReadonlyArray } | { readonly type: "branch" @@ -3564,6 +3574,7 @@ export declare namespace Machine { readonly title: string readonly target: Path | undefined readonly selection: TransitionTargetSelection + readonly updates: ReadonlyArray } /** The statically selected root entry for machine startup. */ @@ -3635,6 +3646,8 @@ export declare namespace Machine { * Choice microsteps retain each intermediate pseudo-state edge separately. */ readonly resolvedTarget: TargetPath | undefined + /** Retained valued owners replaced by this transition. */ + readonly updates: ReadonlyArray } /** @@ -4473,21 +4486,42 @@ export declare namespace Machine { States extends StateSchemas, StateId extends ValuedStateIdentifier > { - readonly [Topology.StateUpdateTypeId]: typeof Topology.StateUpdateTypeId - readonly path: StateId - readonly value: StateByIdentifier + readonly [Topology.StateUpdateTypeId]: { + readonly states: Types.Covariant + readonly owner: Types.Covariant + } + } + + /** + * Opaque result that combines one topology target with one retained owner + * value replacement in the same microstep. + * + * @category models + * @since 0.22.0 + */ + export interface CombinedTarget< + Result, + States extends StateSchemas, + Owner extends ValuedStateIdentifier + > { + readonly [Topology.CombinedTargetTypeId]: { + readonly result: Types.Covariant + readonly states: Types.Covariant + readonly owner: Types.Covariant + } } /** @internal */ type StateUpdateBuilder< States extends StateSchemas, StateId extends ValuedStateIdentifier - > = - & ((value: StateByIdentifier) => StateUpdate) - & FromMethod< + > = { + readonly decoded: (value: StateByIdentifier) => StateUpdate + readonly from: FromCallable< readonly [input: SchemaByIdentifier["~type.make.in"]], StateUpdate > + } /** * Opaque result returned by an explicitly targetless transition. @@ -4720,23 +4754,36 @@ export declare namespace Machine { export interface TargetSelection< out Result, out Path extends string | undefined = string | undefined, - out Kind extends Topology.TargetSelectionKind = Topology.TargetSelectionKind + out Kind extends Topology.TargetSelectionKind = Topology.TargetSelectionKind, + out Scope extends Topology.TargetSelectionScope | undefined = Topology.TargetSelectionScope | undefined > { readonly [Topology.TargetSelectionTypeId]: typeof Topology.TargetSelectionTypeId readonly kind: Kind - readonly scope: Topology.TargetSelectionScope | undefined + readonly scope: Scope readonly path: Path readonly "~effect/Machine/TargetSelectionResult"?: Types.Covariant } - type SelectionValue = - TargetSelection + type SelectionValue< + Builder, + Path extends string, + Kind extends Topology.TargetSelectionKind = "state", + Scope extends Topology.TargetSelectionScope | undefined = Topology.TargetSelectionScope | undefined + > = TargetSelection - type SelectionMethod = () => - SelectionValue + type SelectionMethod< + Builder, + Path extends string, + Kind extends Topology.TargetSelectionKind = "state", + Scope extends Topology.TargetSelectionScope | undefined = Topology.TargetSelectionScope | undefined + > = () => SelectionValue - type InitialSelectionMethod = Builder extends { readonly initial: infer Initial } ? { - readonly initial: SelectionValue + type InitialSelectionMethod< + Builder, + Path extends string, + Scope extends Topology.TargetSelectionScope + > = Builder extends { readonly initial: infer Initial } ? { + readonly initial: SelectionValue } : {} @@ -4765,25 +4812,28 @@ export declare namespace Machine { > = Node extends ChoiceStateNodeConfig ? SelectionMethod< Builder, Path, - "choice" + "choice", + Scope > : Node extends { readonly states: infer Children extends StateSchemas } ? - & SelectionMethod - & InitialSelectionMethod + & SelectionMethod + & InitialSelectionMethod & SelectionTreeWithPrefix - : SelectionMethod + : SelectionMethod /** @internal */ type StateUpdateSelectionForNode< AllStates extends StateSchemas, Node, - Path extends string + Path extends string, + Scope extends "local" | "branch" > = Node extends { readonly states: StateSchemas } ? NodeSchema extends never ? {} : { readonly update: SelectionValue< StateUpdateBuilder>>, Path, - "update" + "update", + Scope > } : {} @@ -4793,21 +4843,28 @@ export declare namespace Machine { AllStates extends StateSchemas, Node, Path extends string, - Rest extends string + Rest extends string, + Scope extends "local" | "branch" = "branch" > = - & StateUpdateSelectionForNode + & StateUpdateSelectionForNode & (Node extends { readonly states: infer Children extends StateSchemas } ? Rest extends `${infer Head}.${infer Tail}` ? Head extends keyof Children ? { readonly [Key in Head]: BranchUpdateSelectionPath< AllStates, Children[Head], JoinPath, - Tail + Tail, + Scope > } : {} : Rest extends keyof Children ? { - readonly [Key in Rest]: StateUpdateSelectionForNode> + readonly [Key in Rest]: StateUpdateSelectionForNode< + AllStates, + Children[Rest], + JoinPath, + Scope + > } : {} : {}) @@ -4818,9 +4875,9 @@ export declare namespace Machine { Path extends StateIdentifier, Builder > = Node extends { readonly states: StateSchemas } ? - & SelectionMethod - & InitialSelectionMethod - : SelectionMethod + & SelectionMethod + & InitialSelectionMethod + : SelectionMethod type FullTargetSelector = { readonly [Key in Extract, keyof FullTargetBuilder>]: FullSelectionNode< @@ -4846,7 +4903,7 @@ export declare namespace Machine { > & (Source extends ChoiceIdentifier ? {} : Source extends `${Key}.${infer Rest}` ? BranchUpdateSelectionPath - : StateUpdateSelectionForNode) + : StateUpdateSelectionForNode) } : {} : {} @@ -4860,12 +4917,12 @@ export declare namespace Machine { LocalTargetBuilder extends infer Builder ? & SelectionTreeWithPrefix & ("with" extends keyof Builder ? { - readonly with: SelectionValue + readonly with: SelectionValue } : {}) & (Source extends ChoiceIdentifier ? {} : Scope extends ValuedStateIdentifier ? { - readonly update: SelectionValue, Scope, "update"> + readonly update: SelectionValue, Scope, "update", "local"> } : {}) : {} @@ -4883,7 +4940,8 @@ export declare namespace Machine { SelectionValue< Builder[Key], JoinPath, - "history" + "history", + "full" > : States[Key] extends { readonly states: infer Children extends StateSchemas } ? HistorySelectionTree< AllStates, @@ -4908,7 +4966,7 @@ export declare namespace Machine { Source extends StateNodeIdentifier > { /** Handles the trigger without selecting a destination. */ - readonly none: SelectionValue["none"], never, "none"> + readonly none: SelectionValue["none"], never, "none", "local"> /** Selects a destination or updates the nearest active compound scope. */ readonly local: LocalTargetSelector /** Selects a destination or updates a valued active ancestor under the current root. */ @@ -4923,9 +4981,9 @@ export declare namespace Machine { type InitialTargetSelector = { readonly [Key in Extract, keyof InitialBuilder>]: States[Key] extends { readonly states: StateSchemas } ? { - readonly initial: SelectionValue[Key], Key, "initial"> + readonly initial: SelectionValue[Key], Key, "initial", "initial"> } - : SelectionMethod[Key], Key> + : SelectionMethod[Key], Key, "state", "initial"> } /** @@ -5334,6 +5392,11 @@ export declare namespace Machine { | HistoryTarget> | ChoiceTarget> | StateUpdate> + | CombinedTarget< + Target> | Snapshot, + States, + ValuedStateIdentifier + > | StateConstruction< | Snapshot | Target> @@ -5667,21 +5730,75 @@ export declare namespace Machine { Acceptance extends TransitionAcceptance = "required" > = TransitionBuilderInput - export type SelectionBuilder = Selection extends TargetSelection ? Builder : never - export type SelectionKind = Selection extends TargetSelection ? Kind : never - export type SelectionPath = Selection extends TargetSelection ? Path : never + export type SelectionBuilder = Selection extends TargetSelection ? Builder + : never + export type SelectionKind = Selection extends TargetSelection ? Kind : never + export type SelectionPath = Selection extends TargetSelection ? Path : never + type SelectionScope = Selection extends TargetSelection ? Scope : never export type TargetBuilderResult = | (Builder extends (...args: any) => infer Result ? Result : never) + | (Builder extends { readonly decoded: (...args: any) => infer Result } ? Result : never) | (Builder extends { readonly from: (...args: any) => infer Result } ? Result : never) export type SelectedTargetResult = SelectionBuilder extends infer Builder ? TargetBuilderResult : never + type RetainedUpdateOwner< + States extends StateSchemas, + Source extends StateNodeIdentifier, + Selection + > = Extract< + ParentStateIdentifier, + ParentStateIdentifier, string>> & ValuedStateIdentifier + > + + type RetainedOwnerSelector = () => TargetSelection + + /** + * Destination construction returned when a transition declares one retained + * valued owner with `.updating(...)`. + * + * @category models + * @since 0.22.0 + */ + export interface UpdatingStateConstruction< + Result, + States extends StateSchemas, + Owner extends ValuedStateIdentifier + > { + /** Combines the selected topology with the required owner replacement. */ + readonly update: ( + update: StateUpdate + ) => CombinedTarget, States, Owner> + } + + type UpdatingCallable< + Callable, + States extends StateSchemas, + Owner extends ValuedStateIdentifier + > = Callable extends { + (...args: infer Arguments1): infer Result1 + (...args: infer Arguments2): infer Result2 + } ? { + (...args: Arguments1): UpdatingStateConstruction + (...args: Arguments2): UpdatingStateConstruction + } + : Callable extends (...args: infer Arguments) => infer Result ? + (...args: Arguments) => UpdatingStateConstruction + : never + + type UpdatingTargetBuilder< + Builder, + States extends StateSchemas, + Owner extends ValuedStateIdentifier + > = { + readonly [Key in keyof Builder]: Key extends "from" | "decoded" ? UpdatingCallable + : Builder[Key] + } + 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< @@ -5691,6 +5808,31 @@ export declare namespace Machine { & Omit & (SelectionKind extends "none" ? {} : { readonly target: SelectionBuilder }) + type StateUpdateResolveContext< + States extends StateSchemas, + Context, + Owner extends ValuedStateIdentifier + > = Omit & { + /** Decoded owner value from the pre-transition snapshot. */ + readonly current: StateByIdentifier + /** Constructs the complete replacement for the selected owner. */ + readonly owner: StateUpdateBuilder + } + + type UpdatingTransitionResolveContext< + States extends StateSchemas, + Context, + Selection, + Owner extends ValuedStateIdentifier + > = Omit & { + /** Decoded owner value from the pre-transition snapshot. */ + readonly current: StateByIdentifier + /** Constructs the selected topology and requires `.update(...)`. */ + readonly target: UpdatingTargetBuilder, States, Owner> + /** Constructs the complete replacement for the retained owner. */ + readonly owner: StateUpdateBuilder + } + /** Context capability available only to explicitly declinable resolvers. */ export interface DeclineCapability { /** Declines this candidate and continues hierarchical transition selection. */ @@ -5721,25 +5863,27 @@ export declare namespace Machine { /** @internal */ type StateUpdateResolver< + States extends StateSchemas, Events extends ReadonlyArray, Emits extends ReadonlyArray, Context, - Selection + Owner extends ValuedStateIdentifier > = ( - context: TransitionResolveContext, + context: StateUpdateResolveContext, enqueue: Enqueue, EmitOf> - ) => SelectedTargetResult + ) => StateUpdate /** @internal */ type DeclinableStateUpdateResolver< + States extends StateSchemas, Events extends ReadonlyArray, Emits extends ReadonlyArray, Context, - Selection + Owner extends ValuedStateIdentifier > = ( - context: TransitionResolveContext & DeclineCapability, + context: StateUpdateResolveContext & DeclineCapability, enqueue: Enqueue, EmitOf> - ) => SelectedTargetResult | Declined + ) => StateUpdate | Declined /** One named destination declared by a branching transition. */ export interface TransitionBranchInput< @@ -5949,10 +6093,7 @@ export declare namespace Machine { > : {}) & { - /** - * Evaluates state construction and queued commands only after this - * transition has been selected. - */ + /** Evaluates state construction after this transition is selected. */ readonly resolve: & TransitionResolveRequired & ("declinable" extends Acceptance ? TransitionResolveDeclinable< @@ -5981,6 +6122,79 @@ export declare namespace Machine { } : {} : {}) + & (SelectionKind extends "state" ? + SelectionScope extends "local" | "branch" ? + RetainedUpdateOwner extends infer Owner extends ValuedStateIdentifier ? + [Owner] extends [never] ? {} + : { + /** Declares one valued owner retained by the selected topology. */ + readonly updating: ( + owner: RetainedOwnerSelector + ) => UpdatingTransitionTarget< + States, + Events, + Emits, + StateId, + Context, + Reenter, + Acceptance, + Selection, + SelectedOwner + > + } + : {} + : {} + : {}) + + /** A topology selection that requires one retained owner replacement. */ + export type UpdatingTransitionTarget< + States extends StateSchemas, + Events extends ReadonlyArray, + Emits extends ReadonlyArray, + StateId extends StateNodeIdentifier, + Context, + Reenter extends boolean, + Acceptance extends TransitionAcceptance, + Selection extends TargetSelection, + Owner extends ValuedStateIdentifier + > = Selection & { + /** @internal */ + readonly "~effect/Machine/UpdatingTransitionTarget": Owner + readonly resolve: + & (( + resolve: ( + context: UpdatingTransitionResolveContext, + enqueue: Enqueue, EmitOf> + ) => CombinedTarget>, States, Owner>, + options?: TransitionRequiredOptions + ) => BuiltTransition< + States, + Events, + Emits, + StateId, + Context, + Reenter, + CombinedTarget>, States, Owner>, + "required" + >) + & ("declinable" extends Acceptance ? ( + resolve: ( + context: UpdatingTransitionResolveContext & DeclineCapability, + enqueue: Enqueue, EmitOf> + ) => CombinedTarget>, States, Owner> | Declined, + options: TransitionDeclinableOptions + ) => BuiltTransition< + States, + Events, + Emits, + StateId, + Context, + Reenter, + CombinedTarget>, States, Owner> | Declined, + "declinable" + > + : {}) + } /** @internal */ interface StateUpdateTransitionRequired< @@ -5993,7 +6207,13 @@ export declare namespace Machine { Selection extends TargetSelection > { ( - resolve: StateUpdateResolver, + resolve: StateUpdateResolver< + States, + Events, + Emits, + Context, + Extract, ValuedStateIdentifier> + >, options?: TransitionRequiredOptions ): BuiltTransition< States, @@ -6018,7 +6238,13 @@ export declare namespace Machine { Selection extends TargetSelection > { ( - resolve: DeclinableStateUpdateResolver, + resolve: DeclinableStateUpdateResolver< + States, + Events, + Emits, + Context, + Extract, ValuedStateIdentifier> + >, options: TransitionDeclinableOptions ): BuiltTransition< States, @@ -8447,13 +8673,13 @@ interface Make { * const counter = Machine.make({ * states: States.states, * events: Events, - * initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) + * initial: (to) => to.Count().resolve(({ target }) => target.decoded(new Count({ value: 0 }))) * }).handle({ * Count: { * on: { * Increment: (to) => * to.full.Count().resolve(({ event, state, target }) => - * target(new Count({ value: state.value + event.by }))) + * target.decoded(new Count({ value: state.value + event.by }))) * } * } * }) @@ -8780,9 +9006,21 @@ type TransitionBranchRecordError = Extract +type InvalidUpdatingTransitionBranchKey = { + readonly [Key in keyof Branches]: Branches[Key] extends { + readonly target: { readonly "~effect/Machine/UpdatingTransitionTarget": string } + } ? Key + : never +}[keyof Branches] + type ValidateTransitionBranchRecord = [keyof Branches] extends [never] ? TransitionBranchRecordError<"Branch records must contain at least one branch"> - : [InvalidStaticTransitionBranchKey] extends [never] ? unknown + : [InvalidStaticTransitionBranchKey] extends [never] ? + [InvalidUpdatingTransitionBranchKey] extends [never] ? unknown + : TransitionBranchRecordError< + "Updating targets require a direct resolver", + InvalidUpdatingTransitionBranchKey + > : TransitionBranchRecordError< "Branch keys must be non-empty, non-index strings", InvalidStaticTransitionBranchKey diff --git a/src/internal/machine/executionPlan.ts b/src/internal/machine/executionPlan.ts index 874d64a..f72da91 100644 --- a/src/internal/machine/executionPlan.ts +++ b/src/internal/machine/executionPlan.ts @@ -47,7 +47,15 @@ import { validateDeclaredTransitionTarget } from "./planner.js" import { decodeEmitSync, decodeEventSync, decodeInputSync, decodeStateValueSync } from "./protocol.js" -import { isInitialTarget, isNoTarget, isSnapshot, isStateUpdate, isTarget, TargetSnapshotTypeId } from "./topology.js" +import { + isCombinedTarget, + isInitialTarget, + isNoTarget, + isSnapshot, + isStateUpdate, + isTarget, + TargetSnapshotTypeId +} from "./topology.js" interface IndexedExecutionDescriptor { readonly flat: boolean @@ -548,25 +556,28 @@ const collectIndexedEvaluatedTransition = ( selection.context, selection.transition.evaluate ) - const update = isStateUpdate(transitionResult.state) + const combined = isCombinedTarget(transitionResult.state) ? transitionResult.state : undefined + const transitionState = combined?.target ?? transitionResult.state + const stateUpdate = combined?.update ?? (isStateUpdate(transitionState) ? transitionState : undefined) + const update = stateUpdate !== undefined ? (() => { - const index = descriptor.indexByPath.get(transitionResult.state.path) + const index = descriptor.indexByPath.get(stateUpdate.path) const node = index === undefined ? undefined : descriptor.nodes[index] if ( index === undefined || node === undefined || state.active[index] !== 1 || node.schema === undefined || (node.type !== "compound" && node.type !== "parallel") ) { throw new Error( - `Machine state update owner "${transitionResult.state.path}" must be an active valued compound or parallel state` + `Machine state update owner "${stateUpdate.path}" must be an active valued compound or parallel state` ) } return { path: node.path, - value: decodeStateValueSync(machine, node, transitionResult.state.value) + value: decodeStateValueSync(machine, node, stateUpdate.value) } })() : undefined - const unresolvedTarget = update === undefined ? transitionResult.state : undefined + const unresolvedTarget = transitionState === undefined || isStateUpdate(transitionState) ? undefined : transitionState validateDeclaredTransitionTarget( selection.sourcePath, selection.trigger, @@ -585,13 +596,22 @@ const collectIndexedEvaluatedTransition = ( if (target !== undefined && !isTarget(target) && !isSnapshot(target)) { throw new Error("Machine expected indexed transition target to be a snapshot or target builder result") } - const next = target === undefined + let next = target === undefined ? update === undefined ? state : (() => { const next = copyOwnedIndexedState(state) next.values[descriptor.indexByPath.get(update.path)!] = update.value return next })() : normalizeIndexedTargetStateSync(machine, descriptor, state, target as any, selection.leafIndex) + if (combined !== undefined && update !== undefined) { + const ownerIndex = descriptor.indexByPath.get(update.path)! + if (next.active[ownerIndex] !== 1) { + throw new Error(`Machine combined target exited its updating owner "${update.path}"`) + } + const values = next.values.slice() + values[ownerIndex] = update.value + next = { ...next, values } + } const changed = selection.transition.reenter || !hasSameIndexedActive(state, next) const stabilize = changed || update !== undefined if (!changed) { @@ -656,7 +676,8 @@ const indexedMicrostep = ( branchIndex: transition.branchIndex, branchKey: transition.branchKey, target: transition.unresolvedTarget === undefined ? undefined : getTargetNodePath(transition.unresolvedTarget), - resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target) + resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target), + updates: transition.update === undefined ? [] : [transition.update.path] }) if (selections.length === 1) { const transition = collectIndexedEvaluatedTransition(machine, descriptor, state, selections[0]!) @@ -849,7 +870,8 @@ const planIndexedFlatState = ( branchIndex: transitionResult.branchIndex, branchKey: transitionResult.branchKey, target: target === undefined ? undefined : getTargetNodePath(target as any), - resolvedTarget: target === undefined ? undefined : getTargetNodePath(target as any) + resolvedTarget: target === undefined ? undefined : getTargetNodePath(target as any), + updates: [] }] } : undefined), diff --git a/src/internal/machine/initialization.ts b/src/internal/machine/initialization.ts index 02ab2a7..b3164c1 100644 --- a/src/internal/machine/initialization.ts +++ b/src/internal/machine/initialization.ts @@ -14,11 +14,19 @@ const getNode = (machine: Machine.Any, path: string): Machine.StateNode => { } const withFrom = unknown>(method: Method) => { - Object.defineProperty(method, "from", { + const builder = {} + Object.defineProperty(builder, "decoded", { + value: method, + enumerable: false + }) + Object.defineProperty(builder, "from", { value: (...args: ReadonlyArray) => method(Topology.makeStateInput(args.length === 0 ? {} : args[0])), enumerable: false }) - return method as Method & { readonly from: (...args: ReadonlyArray) => unknown } + return builder as { + readonly decoded: Method + readonly from: (...args: ReadonlyArray) => unknown + } } const makeCompletion = (values: Readonly>): object => { diff --git a/src/internal/machine/machine.ts b/src/internal/machine/machine.ts index 74e7d5a..8faf1aa 100644 --- a/src/internal/machine/machine.ts +++ b/src/internal/machine/machine.ts @@ -191,7 +191,7 @@ type InitialBuilderDescriptor = { const plainTargetSelection = (selection: Topology.TargetSelection): Topology.TargetSelection => selection.kind === "none" ? Topology.noneTargetSelection - : Topology.makeTargetSelection(selection.kind, selection.path, selection.scope) + : Topology.makeTargetSelection(selection.kind, selection.path, selection.scope, selection.updatePath) const transitionOptions = (options: unknown): { readonly reenter: boolean; readonly declinable: boolean } => { const configuration = typeof options === "object" && options !== null @@ -224,7 +224,22 @@ const decorateTransitionSelection = (selection: Topology.TargetSelection): Topol ...selection, resolve: (resolve: (context: any, enqueue: unknown) => unknown, options?: unknown) => makeDirectTransitionDescriptor(selection, resolve, options), - reenter: () => makeDirectTransitionDescriptor(selection, undefined, { reenter: true }) + reenter: () => makeDirectTransitionDescriptor(selection, undefined, { reenter: true }), + updating: (owner: unknown) => { + if (typeof owner !== "function") { + throw new Error("Machine updating owner must be a state selector") + } + const ownerSelection = owner() + if ( + !Topology.isTargetSelection(ownerSelection) || ownerSelection.kind !== "state" || + ownerSelection.scope !== "branch" + ) { + throw new Error("Machine updating owner must be addressed by one branch state selector") + } + return decorateTransitionSelection( + Topology.makeTargetSelection(selection.kind, selection.path, selection.scope, ownerSelection.path) + ) + } }) const decorateStateUpdateSelection = (selection: Topology.TargetSelection): Topology.TargetSelection => { @@ -360,6 +375,11 @@ const transitionTargetSelection = ( scope: selection.scope }) +const selectionUpdates = (selection: Topology.TargetSelection): ReadonlyArray => + selection.updatePath === undefined ? + selection.kind === "update" && selection.path !== undefined ? [selection.path] : [] + : [selection.updatePath] + const makeSelectionMethod = ( kind: Topology.TargetSelectionKind, path: string | undefined, @@ -476,6 +496,7 @@ const makeTargetSelector = ( const captureDefinitionBranch = ( branch: unknown, selector: unknown, + stateNodes: Machine.StateNodes, path: string, trigger: PropertyKey ): CapturedBranch => { @@ -489,9 +510,58 @@ const captureDefinitionBranch = ( if (!Topology.isTargetSelection(selection)) { throw new Error(`Machine transition for state "${path}" on "${String(trigger)}" must select exactly one target`) } + if (selection.updatePath !== undefined) { + const owner = stateNodes.byPath.get(selection.updatePath) + if ( + selection.kind !== "state" || (selection.scope !== "local" && selection.scope !== "branch") || + selection.path === undefined || owner === undefined || owner.schema === undefined || + (owner.type !== "compound" && owner.type !== "parallel") || + !Configuration.isDescendantOf(path, owner.path) || + !Configuration.isDescendantOf(selection.path, owner.path) + ) { + throw new Error( + `Machine updating owner "${selection.updatePath}" must be a valued ancestor retained by source "${path}" and target "${selection.path}"` + ) + } + } return { ...(branch as DefinitionBranch), selection } } +const makeUpdatingConstruction = ( + target: unknown, + ownerPath: string +): { readonly update: (update: unknown) => Topology.CombinedTarget } => + Object.freeze({ + update: (update: unknown) => { + if (!Topology.isStateUpdate(update) || update.path !== ownerPath) { + throw new Error(`Machine combined target must update its declared owner "${ownerPath}"`) + } + return Topology.makeCombinedTarget(target, update) + } + }) + +const makeUpdatingTargetBuilder = ( + builder: unknown, + ownerPath: string +): unknown => { + if (typeof builder !== "object" || builder === null) { + throw new Error("Machine combined target requires a state construction builder") + } + const updating: Record = {} + for (const property of Reflect.ownKeys(builder)) { + const descriptor = Object.getOwnPropertyDescriptor(builder, property) + if (descriptor === undefined) continue + if ( + (property === "from" || property === "decoded") && "value" in descriptor && typeof descriptor.value === "function" + ) { + const construct = descriptor.value + descriptor.value = (...args: ReadonlyArray) => makeUpdatingConstruction(construct(...args), ownerPath) + } + Object.defineProperty(updating, property, descriptor) + } + return Object.freeze(updating) +} + const getSelectionBuilder = ( target: Record, selection: Topology.TargetSelection, @@ -534,7 +604,7 @@ const getSelectionBuilder = ( ) { throw new Error(`Machine could not construct selected transition target "${selection.path}"`) } - return builder + return selection.updatePath === undefined ? builder : makeUpdatingTargetBuilder(builder, selection.updatePath) } const constructSelectedTarget = (builder: any): unknown => @@ -557,10 +627,18 @@ const validateResolvedSelection = ( } return } + if (selection.updatePath !== undefined) { + if (!Topology.isCombinedTarget(result) || result.update.path !== selection.updatePath) { + throw new Error(`Machine target updating "${selection.updatePath}" must return target construction .update(...)`) + } + } else if (Topology.isCombinedTarget(result)) { + throw new Error("Machine combined target requires an updating owner declaration") + } if (result === undefined) return - const resultPath = typeof result === "object" && result !== null && hasProperty(result, "path") && - typeof result.path === "string" - ? result.path + const target = Topology.isCombinedTarget(result) ? result.target : result + const resultPath = typeof target === "object" && target !== null && hasProperty(target, "path") && + typeof target.path === "string" + ? target.path : undefined const selectedNode = selection.path === undefined ? undefined : stateNodes.byPath.get(selection.path) const acceptsDescendant = (selection.scope === "local" || selection.scope === "branch") && @@ -587,6 +665,26 @@ const runCapturedBranch = ( const resolverContext = { ...context } if (branch.selection.kind === "none") delete resolverContext.target else resolverContext.target = selectedTarget + if (branch.selection.kind === "update") { + const ownerPath = branch.selection.path! + delete resolverContext.target + resolverContext.current = context.ancestors[ownerPath] ?? context.state + resolverContext.owner = getSelectionBuilder( + context.target, + makeStateUpdateSelection(ownerPath, branch.selection.scope === "local" ? "local" : "branch"), + stateNodes, + source + ) + } else if (branch.selection.updatePath !== undefined) { + const ownerPath = branch.selection.updatePath + resolverContext.current = context.ancestors[ownerPath] + resolverContext.owner = getSelectionBuilder( + context.target, + makeStateUpdateSelection(ownerPath, "branch"), + stateNodes, + source + ) + } if (branch.declinable === true) resolverContext.decline = Topology.makeDeclined const resolved = branch.resolve(resolverContext, enqueue) if (Topology.isDeclined(resolved)) { @@ -636,6 +734,9 @@ const captureNamedBranches = ( if (!Topology.isTargetSelection(target)) { throw new Error(`Machine transition branch "${key}" must select exactly one target`) } + if (target.updatePath !== undefined) { + throw new Error(`Machine transition branch "${key}" cannot declare an updating target`) + } if (title !== undefined && (typeof title !== "string" || title.length === 0)) { throw new Error(`Machine transition branch "${key}" title must be a non-empty string`) } @@ -795,14 +896,15 @@ const captureTransition = ( key: branch.key, title: branch.title, target: topologyTargetPath(branch.selection), - selection: transitionTargetSelection(branch.selection) + selection: transitionTargetSelection(branch.selection), + updates: selectionUpdates(branch.selection) }) ), evaluate, transition: (context: Record, enqueue: unknown) => evaluate(context, enqueue).result } } - const branch = captureDefinitionBranch(transition, selector, path, trigger) + const branch = captureDefinitionBranch(transition, selector, stateNodes, path, trigger) const evaluate = (context: Record, enqueue: unknown) => ({ result: runCapturedBranch(branch, context, enqueue, stateNodes, path), branchIndex: 0, @@ -815,7 +917,8 @@ const captureTransition = ( branches: [{ type: "direct" as const, target: topologyTargetPath(branch.selection), - selection: transitionTargetSelection(branch.selection) + selection: transitionTargetSelection(branch.selection), + updates: selectionUpdates(branch.selection) }], evaluate, transition: (context: Record, enqueue: unknown) => evaluate(context, enqueue).result @@ -1022,8 +1125,18 @@ const withFrom = ) = method: Method, kind: FromMethodKind, valued: boolean -): Method & { readonly from: (...args: ReadonlyArray) => unknown } => { - Object.defineProperty(method, "from", { +): { + readonly decoded?: Method + readonly from: (...args: ReadonlyArray) => unknown +} => { + const builder: Record = {} + if (valued) { + Object.defineProperty(builder, "decoded", { + value: method, + enumerable: false + }) + } + Object.defineProperty(builder, "from", { value: (...args: ReadonlyArray) => { if (!valued) { return method(undefined, ...args) @@ -1035,7 +1148,10 @@ const withFrom = ) = }, enumerable: false }) - return method as Method & { readonly from: (...args: ReadonlyArray) => unknown } + return builder as { + readonly decoded?: Method + readonly from: (...args: ReadonlyArray) => unknown + } } const withInitial = ( @@ -1518,13 +1634,13 @@ const makeInitialSelector = (stateNodes: Machine.StateNodes): unknown => { const getInitialSelectionBuilder = ( initialBuilder: Record, selection: Topology.TargetSelection -): (...args: ReadonlyArray) => unknown => { +): Record => { const path = selection.path if (path === undefined || path.includes(".")) { throw new Error("Machine initial target must select one top-level state") } const builder = initialBuilder[path] - if (typeof builder !== "function") { + if (typeof builder !== "object" || builder === null || typeof builder.from !== "function") { throw new Error(`Machine could not construct selected initial state "${path}"`) } return builder @@ -1537,7 +1653,7 @@ const captureInitialBranch = ( ): { readonly selection: Topology.TargetSelection readonly resolve?: (context: any) => unknown - readonly builder: (...args: ReadonlyArray) => unknown + readonly builder: Record } => { if (typeof definition !== "function") { throw new Error("Machine initial definition must be a target-first callback") diff --git a/src/internal/machine/planner.ts b/src/internal/machine/planner.ts index 190d3c7..62fdea3 100644 --- a/src/internal/machine/planner.ts +++ b/src/internal/machine/planner.ts @@ -47,6 +47,7 @@ import { getNode, type InitialTarget as InitialTargetInstruction, isChoiceTarget, + isCombinedTarget, isDeclined, isHistoryTarget, isInitialTarget, @@ -70,6 +71,7 @@ export type MicrostepPlan = { readonly branchKey: string | undefined readonly target: string | undefined readonly resolvedTarget: string | undefined + readonly updates: ReadonlyArray }> readonly commands: ReadonlyArray readonly raisedEvents: ReadonlyArray @@ -605,6 +607,7 @@ export type EvaluatedTransition }> } @@ -1234,6 +1237,7 @@ interface ResolvedChoiceTransition { readonly branchKey: string | undefined readonly target: string readonly resolvedTarget: string + readonly updates: ReadonlyArray } function resolveChoiceTarget( @@ -1312,7 +1316,8 @@ function resolveChoiceTarget( branchIndex: collected.branchIndex, branchKey: collected.branchKey, target: returnedPath, - resolvedTarget: nested?.target.path ?? returnedPath + resolvedTarget: nested?.target.path ?? returnedPath, + updates: [] }) commands.push(...collected.commands) raisedEvents.push(...collected.raisedEvents) @@ -1356,10 +1361,12 @@ const collectEvaluatedTransition = < if (transitionResult.declined) { throw new Error("Machine transition returned decline without declaring declinable: true") } - const unresolvedTarget = transitionResult.state === undefined - || isStateUpdate(transitionResult.state) + const combined = isCombinedTarget(transitionResult.state) ? transitionResult.state : undefined + const transitionState = combined?.target ?? transitionResult.state + const unresolvedTarget = transitionState === undefined + || isStateUpdate(transitionState) ? undefined - : transitionResult.state as + : transitionState as | Machine.Snapshot | Machine.Target> | Machine.HistoryTarget> @@ -1370,9 +1377,10 @@ const collectEvaluatedTransition = < selection.transition.targets, unresolvedTarget ) - const update = isStateUpdate(transitionResult.state) + const stateUpdate = combined?.update ?? (isStateUpdate(transitionState) ? transitionState : undefined) + const update = stateUpdate !== undefined ? (() => { - const node = getNode(machine, transitionResult.state.path) + const node = getNode(machine, stateUpdate.path) if ( !state.active.has(node.path) || node.schema === undefined || (node.type !== "compound" && node.type !== "parallel") @@ -1381,7 +1389,7 @@ const collectEvaluatedTransition = < } return { path: node.path, - value: decodeStateValueSync(machine, node, transitionResult.state.value) + value: decodeStateValueSync(machine, node, stateUpdate.value) } })() : undefined @@ -1497,6 +1505,15 @@ const collectEvaluatedTransition = < additionalTarget ) } + if (combined !== undefined && update !== undefined && !stateAfterTransition.active.has(update.path)) { + throw new Error(`Machine combined target exited its updating owner "${update.path}"`) + } + if (update !== undefined) { + stateAfterTransition = { + ...stateAfterTransition, + values: new Map(stateAfterTransition.values).set(update.path, update.value) + } + } const changed = selection.transition.reenter || !hasSameActivePaths(state, stateAfterTransition) const stabilize = changed || update !== undefined @@ -1915,7 +1932,8 @@ const microstep = < branchIndex: transition.branchIndex, branchKey: transition.branchKey, target: transition.unresolvedTarget === undefined ? undefined : getTargetNodePath(transition.unresolvedTarget), - resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target) + resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target), + updates: transition.update === undefined ? [] : [transition.update.path] }, ...transition.choiceTransitions ]) diff --git a/src/internal/machine/topology.ts b/src/internal/machine/topology.ts index 362cac9..bdfc083 100644 --- a/src/internal/machine/topology.ts +++ b/src/internal/machine/topology.ts @@ -31,6 +31,8 @@ export const TargetSelectionTypeId: unique symbol = Symbol("effect/Machine/Targe export const StateUpdateTypeId: unique symbol = Symbol("effect/Machine/StateUpdate") +export const CombinedTargetTypeId: unique symbol = Symbol("effect/Machine/CombinedTarget") + export const SelectedBranchTypeId: unique symbol = Symbol("effect/Machine/SelectedBranch") interface StateInput { @@ -85,6 +87,7 @@ export interface TargetSelection { readonly kind: TargetSelectionKind readonly scope: TargetSelectionScope | undefined readonly path: string | undefined + readonly updatePath?: string } /** One branch selection returned by a compiled branching transition. */ @@ -99,13 +102,15 @@ export interface SelectedBranch { export const makeTargetSelection = ( kind: TargetSelectionKind, path?: string, - scope?: TargetSelectionScope + scope?: TargetSelectionScope, + updatePath?: string ): TargetSelection => Object.freeze({ [TargetSelectionTypeId]: TargetSelectionTypeId as typeof TargetSelectionTypeId, kind, scope, - path + path, + ...(updatePath === undefined ? {} : { updatePath }) }) /** Source-independent definition-time selection for an explicitly targetless transition. */ @@ -128,6 +133,21 @@ export const makeStateUpdate = (path: string, value: unknown): StateUpdate => export const isStateUpdate = (u: unknown): u is StateUpdate => hasProperty(u, StateUpdateTypeId) +export interface CombinedTarget { + readonly [CombinedTargetTypeId]: typeof CombinedTargetTypeId + readonly target: unknown + readonly update: StateUpdate +} + +export const makeCombinedTarget = (target: unknown, update: StateUpdate): CombinedTarget => + Object.freeze({ + [CombinedTargetTypeId]: CombinedTargetTypeId, + target, + update + }) + +export const isCombinedTarget = (u: unknown): u is CombinedTarget => hasProperty(u, CombinedTargetTypeId) + export const makeSelectedBranch = ( owner: object, branchIndex: number, diff --git a/src/internal/testing/machine/finiteModel.ts b/src/internal/testing/machine/finiteModel.ts index d726eb6..d97ea80 100644 --- a/src/internal/testing/machine/finiteModel.ts +++ b/src/internal/testing/machine/finiteModel.ts @@ -1234,7 +1234,10 @@ const selectSnapshot = ( if (state.node._tag === "Choice") { return (builder[state.node.key] as () => unknown)() } - const method = builder[state.node.key] as (value: unknown, selector?: (builder: any) => unknown) => unknown + const method = builder[state.node.key].decoded as ( + value: unknown, + selector?: (builder: any) => unknown + ) => unknown const value = stateValue(state, path === requestedParts?.join(".") ? requestedValue : undefined) if (state.node._tag === "Atomic" || state.node._tag === "Final") return method(value) @@ -1473,14 +1476,15 @@ const makeHandlers = ( ...(Object.keys(history).length === 0 ? {} : { history }), ...(node._tag === "Compound" ? { - initialize: ({ builder }: any) => builder(stateValue(byPath.get(`${path}.${node.initial}`)!)) + initialize: ({ builder }: any) => builder.decoded(stateValue(byPath.get(`${path}.${node.initial}`)!)) } : { initialize: ({ builder }: any) => node.states .filter((child) => child._tag !== "History" && child._tag !== "Choice") .reduce( - (current: any, child) => current[child.key](stateValue(byPath.get(`${path}.${child.key}`)!)), + (current: any, child) => + current[child.key].decoded(stateValue(byPath.get(`${path}.${child.key}`)!)), builder ) }), diff --git a/src/internal/testing/machine/verification.ts b/src/internal/testing/machine/verification.ts index d7dd0da..17a4832 100644 --- a/src/internal/testing/machine/verification.ts +++ b/src/internal/testing/machine/verification.ts @@ -564,7 +564,6 @@ export const coverage = ( const exitHits = new Set() const transitionCoverage = makeTransitionCoverageCollector(machine) - const transitionDefinitions = Machine.transitionDefinitions(machine) const declaredEvents = publicEventTags(machine) const declaredEventTags = declaredEvents.tags @@ -632,13 +631,8 @@ export const coverage = ( hitPaths(microstep.exitPaths, exitHits) observeSnapshot(microstep.next) for (const retained of microstep.transitions) { - const definition = transitionDefinitions.find((candidate) => - candidate.source === retained.source && candidate.reenter === retained.reenter && - sameTransitionTrigger(candidate.trigger, retained.trigger) - ) - const branch = definition?.branches[retained.branchIndex] - if (branch?.selection.kind === "update") stateUpdates += 1 - else if (retained.target === undefined) targetlessTransitions += 1 + stateUpdates += retained.updates.length + if (retained.target === undefined && retained.updates.length === 0) targetlessTransitions += 1 if (retained.trigger.type === "event") eventTriggered += 1 else if (retained.trigger.type === "always") alwaysTriggered += 1 else if (retained.trigger.type === "done") doneTriggered += 1 @@ -1549,6 +1543,18 @@ export const verify = ( transition.target === undefined ? transition.source : String(transition.target) ) } + if ( + transition.updates.length !== branch.updates.length || + transition.updates.some((path, index) => path !== branch.updates[index]) + ) { + add( + "definitions.selection", + location, + `transition branch ${transition.branchIndex} from "${transition.source}" reported retained owner updates ` + + `${JSON.stringify(transition.updates)} instead of ${JSON.stringify(branch.updates)}`, + transition.source + ) + } return branch } @@ -1607,8 +1613,10 @@ export const verify = ( const resolvedTarget = transition.resolvedTarget === undefined ? undefined : String(transition.resolvedTarget) const targetNode = target === undefined ? undefined : byPath.get(target) let expected = target - let explanation = branches[index]?.selection.kind === "update" - ? `update owner "${String(branches[index]?.selection.path)}"` + let explanation = transition.updates.length > 0 + ? target === undefined + ? `update owner "${transition.updates.join("\", \"")}"` + : `target "${target}" and update owner "${transition.updates.join("\", \"")}"` : target === undefined ? "an unresolved targetless transition" : `target "${target}"` diff --git a/src/testing/MachineTest.ts b/src/testing/MachineTest.ts index d1259c4..eff7b91 100644 --- a/src/testing/MachineTest.ts +++ b/src/testing/MachineTest.ts @@ -1109,7 +1109,7 @@ export const Invariant: { * events: Machine.events(), * initial: { * target: (to) => to.Count(), - * resolve: ({ target }) => target(new Count({ value: 0 })) + * resolve: ({ target }) => target.decoded(new Count({ value: 0 })) * } * }).handle({ Count: {} }) * @@ -1444,14 +1444,14 @@ export type ExploreOptions to.Count(), - * resolve: ({ target }) => target(new Count({ value: 0 })) + * resolve: ({ target }) => target.decoded(new Count({ value: 0 })) * } * }).handle({ * Count: { * on: { * Increment: (to) => * to.full.Count().resolve(({ state, target }) => - * target(new Count({ value: state.value + 1 }))) + * target.decoded(new Count({ value: state.value + 1 }))) * } * } * }) diff --git a/src/unstable/reactivity/AtomMachine.ts b/src/unstable/reactivity/AtomMachine.ts index 2ea96f9..3e7d5f8 100644 --- a/src/unstable/reactivity/AtomMachine.ts +++ b/src/unstable/reactivity/AtomMachine.ts @@ -374,7 +374,7 @@ type ChildState = RefState to.Count(), - * resolve: ({ target }) => target(new Count({ value: 0 })) + * resolve: ({ target }) => target.decoded(new Count({ value: 0 })) * } * }).handle({ Count: {} }) * const machineAtom = AtomMachine.make(machine) diff --git a/test/internal/machine/activities.test.ts b/test/internal/machine/activities.test.ts index ad27817..e3c395e 100644 --- a/test/internal/machine/activities.test.ts +++ b/test/internal/machine/activities.test.ts @@ -18,7 +18,7 @@ const childMachine = Machine.make({ id: "document-worker", states: childStates.states, events: Machine.events(), - initial: (to) => to.ChildIdle().resolve(({ target }) => target(new ChildIdle({}))) + initial: (to) => to.ChildIdle().resolve(({ target }) => target.decoded(new ChildIdle({}))) }) const child = Machine.child("child", childMachine) @@ -29,7 +29,7 @@ const activityMachine = Machine.make({ id: "activity-inspection", states: activityStates.states, events: Machine.events(WorkSucceeded, WorkFailed, LoadTimedOut), - initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({}))) + initial: (to) => to.Loading().resolve(({ target }) => target.decoded(new Loading({}))) }).handle({ Loading: { invoke: ( @@ -168,7 +168,7 @@ describe("machine activity metadata", () => { const generated = Machine.make({ states: activityStates.states, events: Machine.events(LoadTimedOut), - initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({}))) + initial: (to) => to.Loading().resolve(({ target }) => target.decoded(new Loading({}))) }).handle({ Loading: { invoke: (from) => from.timer(id, durationMillis).onDone((to) => to.none) diff --git a/test/internal/machine/protocol.test.ts b/test/internal/machine/protocol.test.ts index ba82b12..7e7b6ad 100644 --- a/test/internal/machine/protocol.test.ts +++ b/test/internal/machine/protocol.test.ts @@ -41,7 +41,7 @@ describe("machine protocols", () => { states: states.states, events: Machine.events(PublicEvent), internalEvents: Machine.internalEvents(InternalEvent), - initial: (to) => to.ProtocolIdle().resolve(({ target }) => target(new ProtocolIdle({}))) + initial: (to) => to.ProtocolIdle().resolve(({ target }) => target.decoded(new ProtocolIdle({}))) }).handle({}) assert.strictEqual(Object.hasOwn(machine, "eventSchemas"), false) diff --git a/test/internal/machine/strategyDifferential.test.ts b/test/internal/machine/strategyDifferential.test.ts index c6f228a..a466c66 100644 --- a/test/internal/machine/strategyDifferential.test.ts +++ b/test/internal/machine/strategyDifferential.test.ts @@ -35,15 +35,15 @@ const makeFlatMachine = () => { return Machine.make({ states: states.states, events: Machine.events(Noop, Increment, Reenter, Finish), - initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) + initial: (to) => to.Count().resolve(({ target }) => target.decoded(new Count({ value: 0 }))) }).handle({ Count: { on: { Noop: (to) => to.none, Increment: (to) => - to.full.Count().resolve(({ state, target }) => target(new Count({ value: state.value + 1 }))), + to.full.Count().resolve(({ state, target }) => target.decoded(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 }))) + Finish: (to) => to.full.Done().resolve(({ state, target }) => target.decoded(new Done({ value: state.value }))) } }, Done: { output: ({ state }) => state.value } @@ -64,7 +64,7 @@ describe("machine planner and runtime strategies", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Select), - initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) + initial: (to) => to.Count().resolve(({ target }) => target.decoded(new Count({ value: 0 }))) }).handle({ Count: { on: { @@ -106,7 +106,7 @@ describe("machine planner and runtime strategies", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Select), - initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) + initial: (to) => to.Count().resolve(({ target }) => target.decoded(new Count({ value: 0 }))) }).handle({ Count: { on: { @@ -114,7 +114,7 @@ describe("machine planner and runtime strategies", () => { to.full.Count().resolve(({ event, state, target, decline }) => event.value < 0 ? decline() - : target(new Count({ value: state.value + event.value })), { declinable: true }) + : target.decoded(new Count({ value: state.value + event.value })), { declinable: true }) } } }) @@ -179,12 +179,12 @@ describe("machine planner and runtime strategies", () => { events: Machine.events(UpdateRegions, Compete, ExitRoot, ReenterUpdate), initial: (to) => to.Root.initial.resolve(({ target }) => - target( + target.decoded( new Root({ revision: 0 }), (root) => - root.Work(new Work({}), (work) => - work.Left(new Left({ value: 0 }), (left) => left.Leaf(new Leaf({}))) - .Right(new Right({ value: 0 }), (right) => right.Leaf(new Leaf({})))) + root.Work.decoded(new Work({}), (work) => + work.Left.decoded(new Left({ value: 0 }), (left) => left.Leaf.decoded(new Leaf({}))) + .Right.decoded(new Right({ value: 0 }), (right) => right.Leaf.decoded(new Leaf({})))) ) ) }).handle({ @@ -197,14 +197,12 @@ describe("machine planner and runtime strategies", () => { Leaf: { on: { UpdateRegions: (to) => - to.local.update(({ ancestors, target }) => - target(new Left({ value: ancestors["Root.Work.Left"].value + 1 })) - ), - Compete: (to) => to.branch.Root.update(({ target }) => target(new Root({ revision: 1 }))), - ExitRoot: (to) => to.branch.Root.update(({ target }) => target(new Root({ revision: 3 }))), + to.local.update(({ current, owner }) => owner.decoded(new Left({ value: current.value + 1 }))), + Compete: (to) => to.branch.Root.update(({ owner }) => owner.decoded(new Root({ revision: 1 }))), + ExitRoot: (to) => to.branch.Root.update(({ owner }) => owner.decoded(new Root({ revision: 3 }))), ReenterUpdate: (to) => to.local.update( - ({ ancestors, target }) => target(new Left({ value: ancestors["Root.Work.Left"].value + 1 })), + ({ current, owner }) => owner.decoded(new Left({ value: current.value + 1 })), { reenter: true } ) } @@ -216,11 +214,9 @@ describe("machine planner and runtime strategies", () => { Leaf: { on: { UpdateRegions: (to) => - to.local.update(({ ancestors, target }) => - target(new Right({ value: ancestors["Root.Work.Right"].value + 2 })) - ), - Compete: (to) => to.branch.Root.update(({ target }) => target(new Root({ revision: 2 }))), - ExitRoot: (to) => to.full.Outside().resolve(({ target }) => target(new Outside({}))) + to.local.update(({ current, owner }) => owner.decoded(new Right({ value: current.value + 2 }))), + Compete: (to) => to.branch.Root.update(({ owner }) => owner.decoded(new Root({ revision: 2 }))), + ExitRoot: (to) => to.full.Outside().resolve(({ target }) => target.decoded(new Outside({}))) } } } @@ -258,6 +254,59 @@ describe("machine planner and runtime strategies", () => { }) }) + it.effect("matches generic and indexed planning for a topology target with a retained owner update", () => { + class Ready extends Schema.TaggedClass("StrategyCombinedReady")("Ready", { + revision: Schema.Number + }) {} + class Idle extends Schema.TaggedClass("StrategyCombinedIdle")("Idle", {}) {} + class Saving extends Schema.TaggedClass("StrategyCombinedSaving")("Saving", { + request: Schema.String + }) {} + class Save extends Schema.TaggedClass("StrategyCombinedSave")("Save", { + request: Schema.String + }) {} + const states = Machine.states({ + Ready: { + schema: Ready, + initial: "Idle", + states: { Idle, Saving } + } + }) + const machine = Machine.make({ + states: states.states, + events: Machine.events(Save), + initial: (to) => + to.Ready.initial.resolve(({ target }) => + target.decoded(new Ready({ revision: 0 }), (ready) => ready.Idle.decoded(new Idle({}))) + ) + }).handle({ + Ready: { + states: { + Idle: { + on: { + Save: (to) => + to.local.Saving() + .updating(to.branch.Ready) + .resolve(({ current, event, owner, target }) => + target.decoded(new Saving({ request: event.request })).update( + owner.decoded(new Ready({ revision: current.revision + 1 })) + ) + ) + } + }, + Saving: {} + } + } + }) + + return verifyPlannerStrategies({ + machine, + events: [new Save({ request: "plan" })], + expected: "indexed-hierarchical", + label: "combined retained owner update" + }) + }) + it.effect("retains indexed execution microstep evidence without widening frozen execution values", () => Effect.gen(function*() { const machine = makeFlatMachine() @@ -295,9 +344,9 @@ describe("machine planner and runtime strategies", () => { events: Machine.events(Advance), initial: (to) => to.Root.initial.resolve(({ target }) => - target( + target.decoded( new Root({}), - (root) => root.Left(new Left({ value: 0 })).Right(new Right({ value: 0 })) + (root) => root.Left.decoded(new Left({ value: 0 })).Right.decoded(new Right({ value: 0 })) ) ) }).handle({ @@ -306,13 +355,17 @@ describe("machine planner and runtime strategies", () => { Left: { on: { Advance: (to) => - to.branch.Root.Left().resolve(({ state, target }) => target(new Left({ value: state.value + 1 }))) + to.branch.Root.Left().resolve(({ state, target }) => + target.decoded(new Left({ value: state.value + 1 })) + ) } }, Right: { on: { Advance: (to) => - to.branch.Root.Right().resolve(({ state, target }) => target(new Right({ value: state.value + 10 }))) + to.branch.Root.Right().resolve(({ state, target }) => + target.decoded(new Right({ value: state.value + 10 })) + ) } } } @@ -344,11 +397,11 @@ describe("machine planner and runtime strategies", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Enter), - initial: (to) => to.Outside().resolve(({ target }) => target(new Outside({}))) + initial: (to) => to.Outside().resolve(({ target }) => target.decoded(new Outside({}))) }).handle({ Outside: { on: { - Enter: (to) => to.full.Opened.initial.resolve(({ target }) => target(new Opened({}))) + Enter: (to) => to.full.Opened.initial.resolve(({ target }) => target.decoded(new Opened({}))) } }, Opened: { @@ -424,10 +477,10 @@ describe("machine planner and runtime strategies", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({}))) }).handle({ Idle: { - always: (to) => to.full.Ready().resolve(({ target }) => target(new Ready({}))) + always: (to) => to.full.Ready().resolve(({ target }) => target.decoded(new Ready({}))) }, Ready: {} }) @@ -481,7 +534,7 @@ describe("machine planner and runtime strategies", () => { events: Machine.events(), input: Input, initial: (to) => - to.Complete().resolve(({ input: input, target }) => target(new Complete({ value: input.value }))) + to.Complete().resolve(({ input: input, target }) => target.decoded(new Complete({ value: input.value }))) }).handle({ Complete: { output: ({ state }) => state.value } }) @@ -549,7 +602,7 @@ describe("machine planner and runtime strategies", () => { seen.push(element) }) ).onDone((to) => - to.full.StreamDone().resolve(({ target }) => target(new StreamDone({ values: [...seen] }))) + to.full.StreamDone().resolve(({ target }) => target.decoded(new StreamDone({ values: [...seen] }))) ) }, StreamDone: { output: ({ state }) => state.values } @@ -570,14 +623,14 @@ describe("machine planner and runtime strategies", () => { const definition = Machine.make({ states: states.states, events: Machine.events(Event), - initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) + initial: (to) => to.Count().resolve(({ target }) => target.decoded(new Count({ value: 0 }))) }) const events = definition.events const machine = definition.handle({ Count: { on: { Set: (to) => - to.full.Count().resolve(({ event, target }) => target(new Count({ value: event.value.length }))) + to.full.Count().resolve(({ event, target }) => target.decoded(new Count({ value: event.value.length }))) } } }) @@ -621,7 +674,7 @@ describe("machine planner and runtime strategies", () => { states: states.states, events: Events, emittedEvents: Emissions, - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({}))) }).handle({ Idle: { on: { @@ -667,7 +720,7 @@ describe("machine planner and runtime strategies", () => { states: states.states, events: Machine.events(), emittedEvents: Emissions, - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({}))) }).handle({ Idle: { entry: (_, enqueue) => { @@ -793,17 +846,17 @@ describe("machine planner and runtime strategies", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Load, Loaded), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({}))) }).handle({ Idle: { on: { - Load: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({}))) + Load: (to) => to.full.Loading().resolve(({ target }) => target.decoded(new Loading({}))) } }, Loading: { invoke: (from) => from.effect("load", () => Effect.succeed(new Loaded({ value: "complete" }))).onDone((to) => - to.full.Success().resolve(({ output, target }) => target(new Success({ value: output.value }))) + to.full.Success().resolve(({ output, target }) => target.decoded(new Success({ value: output.value }))) ) }, Success: { output: ({ state }) => state.value } @@ -839,12 +892,12 @@ describe("machine planner and runtime strategies", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({}))) + initial: (to) => to.Loading().resolve(({ target }) => target.decoded(new Loading({}))) }).handle({ Loading: { invoke: (from) => from.effect("load", () => Effect.fail("unavailable")).onFailure((to) => - to.full.Failed().resolve(({ error, target }) => target(new Failed({ error }))) + to.full.Failed().resolve(({ error, target }) => target.decoded(new Failed({ error }))) ) }, Failed: { output: ({ state }) => state.error } @@ -879,7 +932,7 @@ describe("machine planner and runtime strategies", () => { states: childStates.states, events: Machine.events(), parent: Machine.parent(ParentEvents), - initial: (to) => to.ChildIdle().resolve(({ target }) => target(new ChildIdle({}))) + initial: (to) => to.ChildIdle().resolve(({ target }) => target.decoded(new ChildIdle({}))) }).handle({ ChildIdle: { invoke: (from) => @@ -895,12 +948,12 @@ describe("machine planner and runtime strategies", () => { const parentMachine = Machine.make({ states: parentStates.states, events: ParentEvents, - initial: (to) => to.ParentWaiting().resolve(({ target }) => target(new ParentWaiting({}))) + initial: (to) => to.ParentWaiting().resolve(({ target }) => target.decoded(new ParentWaiting({}))) }).handle({ ParentWaiting: { invoke: (from) => from.child(Child).onFailure((to) => to.none), on: { - ChildReady: (to) => to.full.ParentDone().resolve(({ target }) => target(new ParentDone({}))) + ChildReady: (to) => to.full.ParentDone().resolve(({ target }) => target.decoded(new ParentDone({}))) } }, ParentDone: { output: () => "received" } @@ -930,7 +983,7 @@ describe("machine planner and runtime strategies", () => { const definition = Machine.make({ states: states.states, events: Machine.events(Reenter, Stale), - initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ epoch: 0 }))) + initial: (to) => to.Loading().resolve(({ target }) => target.decoded(new Loading({ epoch: 0 }))) }) const machine = definition.handle({ Loading: { @@ -961,16 +1014,19 @@ describe("machine planner and runtime strategies", () => { unchanged: { target: to.none } }).resolve(({ snapshot, select }) => snapshot.state === "stale" - ? select.stale(new Failed({})) + ? select.stale.decoded(new Failed({})) : select.unchanged() ) ), on: { 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({}))) + to.full.Loading().resolve( + ({ state, target }) => target.decoded(new Loading({ epoch: state.epoch + 1 })), + { + reenter: true + } + ), + Stale: (to) => to.full.Failed().resolve(({ target }) => target.decoded(new Failed({}))) } }, Failed: {} @@ -1007,7 +1063,7 @@ describe("machine planner and runtime strategies", () => { const childMachine = Machine.make({ states: { ChildIdle }, events: Machine.events(), - initial: (to) => to.ChildIdle().resolve(({ target }) => target(new ChildIdle({}))) + initial: (to) => to.ChildIdle().resolve(({ target }) => target.decoded(new ChildIdle({}))) }).handle({ ChildIdle: {} }) const Child = Machine.childFamily(childMachine) class Commissioning extends Schema.TaggedClass("StrategyDynamicCommissioning")( @@ -1018,7 +1074,7 @@ describe("machine planner and runtime strategies", () => { const machine = Machine.make({ states: { Commissioning, Operating }, events: Machine.events(), - initial: (to) => to.Commissioning().resolve(({ target }) => target(new Commissioning({}))) + initial: (to) => to.Commissioning().resolve(({ target }) => target.decoded(new Commissioning({}))) }).handle({ Commissioning: { invoke: (from) => diff --git a/test/machine/ActivityLifecycleModel.test.ts b/test/machine/ActivityLifecycleModel.test.ts index 8366ed4..9e63849 100644 --- a/test/machine/ActivityLifecycleModel.test.ts +++ b/test/machine/ActivityLifecycleModel.test.ts @@ -73,11 +73,11 @@ describe("machine activity lifecycle model", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Enter, Leave, Restart), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({}))) }).handle({ Idle: { on: { - Enter: (to) => to.full.Active().resolve(({ target }) => target(new Active({}))) + Enter: (to) => to.full.Active().resolve(({ target }) => target.decoded(new Active({}))) } }, Active: { @@ -87,8 +87,9 @@ describe("machine activity lifecycle model", () => { logic: probe.logic("active", { _tag: "Blocked" }) }).onDone((to) => to.none).onFailure((to) => to.none), on: { - Leave: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({}))), - Restart: (to) => to.full.Active().resolve(({ target }) => target(new Active({})), { reenter: true }) + Leave: (to) => to.full.Idle().resolve(({ target }) => target.decoded(new Idle({}))), + Restart: (to) => + to.full.Active().resolve(({ target }) => target.decoded(new Active({})), { reenter: true }) } } }) @@ -143,14 +144,16 @@ describe("machine activity lifecycle model", () => { states: states.states, events: Machine.events(), internalEvents: Machine.internalEvents(Completed), - initial: (to) => to.Active().resolve(({ target }) => target(new Active({}))) + initial: (to) => to.Active().resolve(({ target }) => target.decoded(new Active({}))) }).handle({ Active: { invoke: (from) => from.logic("immediate", { address: Machine.childAddress("immediate"), logic: probe.immediate("immediate", (epoch) => new Completed({ epoch })) - }).onDone((to) => to.full.Done().resolve(({ output, target }) => target(new Done({ epoch: output.epoch })))) + }).onDone((to) => + to.full.Done().resolve(({ output, target }) => target.decoded(new Done({ epoch: output.epoch }))) + ) .onFailure((to) => to.none) }, Done: { @@ -178,7 +181,7 @@ describe("machine activity lifecycle model", () => { states: states.states, events: Machine.events(Restart, QueueBarrier), internalEvents: Machine.internalEvents(Completed), - initial: (to) => to.Active().resolve(({ target }) => target(new EpochActive({ acknowledged: 0 }))) + initial: (to) => to.Active().resolve(({ target }) => target.decoded(new EpochActive({ acknowledged: 0 }))) }).handle({ Active: { invoke: (from) => @@ -192,14 +195,15 @@ describe("machine activity lifecycle model", () => { on: { Restart: (to) => to.full.Active().resolve( - ({ state, target }) => target(new EpochActive({ acknowledged: state.acknowledged })), + ({ state, target }) => target.decoded(new EpochActive({ acknowledged: state.acknowledged })), { reenter: true } ), QueueBarrier: (to) => to.full.Active().resolve(({ state, target }) => - target(new EpochActive({ acknowledged: state.acknowledged + 1 })) + target.decoded(new EpochActive({ acknowledged: state.acknowledged + 1 })) ), - Completed: (to) => to.full.Done().resolve(({ event, target }) => target(new Done({ epoch: event.epoch }))) + Completed: (to) => + to.full.Done().resolve(({ event, target }) => target.decoded(new Done({ epoch: event.epoch }))) } }, Done: {} @@ -304,7 +308,7 @@ describe("machine activity lifecycle model", () => { logic: probe.logic("left", { _tag: "Blocked" }) }).onDone((to) => to.none).onFailure((to) => to.none), on: { - LeaveLeft: (to) => to.local.idle().resolve(({ target }) => target(new LeftIdle({}))) + LeaveLeft: (to) => to.local.idle().resolve(({ target }) => target.decoded(new LeftIdle({}))) } } } @@ -355,7 +359,7 @@ describe("machine activity lifecycle model", () => { states: states.states, events: Machine.events(Leave), internalEvents: Machine.internalEvents(TimerFired), - initial: (to) => to.Active().resolve(({ target }) => target(new Active({}))) + initial: (to) => to.Active().resolve(({ target }) => target.decoded(new Active({}))) }).handle({ Idle: {}, Active: { @@ -367,11 +371,11 @@ describe("machine activity lifecycle model", () => { logic: probe.logic("timed", { _tag: "Blocked" }) }).onDone((to) => to.none).onFailure((to) => to.none), from.timer("deadline", "1 hour").onDone((to) => - to.full.Done().resolve(({ target }) => target(new Done({ epoch: -1 }))) + to.full.Done().resolve(({ target }) => target.decoded(new Done({ epoch: -1 }))) ) ], on: { - Leave: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({}))) + Leave: (to) => to.full.Idle().resolve(({ target }) => target.decoded(new Idle({}))) } }, Done: {} @@ -398,7 +402,7 @@ describe("machine activity lifecycle model", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: (to) => to.Active().resolve(({ target }) => target(new Active({}))) + initial: (to) => to.Active().resolve(({ target }) => target.decoded(new Active({}))) }).handle({ Active: { invoke: ( @@ -444,7 +448,7 @@ describe("machine activity lifecycle model", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: (to) => to.Active().resolve(({ target }) => target(new Active({}))) + initial: (to) => to.Active().resolve(({ target }) => target.decoded(new Active({}))) }).handle({ Active: { invoke: ( diff --git a/test/machine/Annotations.test.ts b/test/machine/Annotations.test.ts index 67373af..f30740e 100644 --- a/test/machine/Annotations.test.ts +++ b/test/machine/Annotations.test.ts @@ -48,7 +48,9 @@ const machine = Machine.make({ states: States.states, events: Machine.events(), initial: (to) => - to.Workflow.initial.resolve(({ target }) => target(new Workflow({}), (workflow) => workflow.Idle(new Idle({})))) + to.Workflow.initial.resolve(({ target }) => + target.decoded(new Workflow({}), (workflow) => workflow.Idle.decoded(new Idle({}))) + ) }) describe("Machine state annotations", () => { diff --git a/test/machine/AnnotationsVisualization.test.ts b/test/machine/AnnotationsVisualization.test.ts index f039296..29cfc5a 100644 --- a/test/machine/AnnotationsVisualization.test.ts +++ b/test/machine/AnnotationsVisualization.test.ts @@ -35,7 +35,9 @@ const machine = Machine.make({ states: States.states, events: Machine.events(), initial: (to) => - to.Workflow.initial.resolve(({ target }) => target(new Workflow({}), (workflow) => workflow.Idle(new Idle({})))) + to.Workflow.initial.resolve(({ target }) => + target.decoded(new Workflow({}), (workflow) => workflow.Idle.decoded(new Idle({}))) + ) }) const renderMachine = makeTextRenderer< diff --git a/test/machine/Choice.test.ts b/test/machine/Choice.test.ts index 53dcd3d..2d884e9 100644 --- a/test/machine/Choice.test.ts +++ b/test/machine/Choice.test.ts @@ -25,7 +25,8 @@ let branchFactoryCalls = 0 const machine = Machine.make({ states: States.states, events: Machine.events(Recheck), - initial: (to) => to.Flow.initial.resolve(({ target }) => target(new Flow({ score: 80 }), (flow) => flow.Routing())) + initial: (to) => + to.Flow.initial.resolve(({ target }) => target.decoded(new Flow({ score: 80 }), (flow) => flow.Routing())) }).handle({ Flow: { states: { @@ -41,21 +42,21 @@ const machine = Machine.make({ }).resolve(({ containingState, select }) => { const score = containingState.score return score === 100 - ? select.perfect(new Approved({})) + ? select.perfect.decoded(new Approved({})) : score < 0 - ? select.negative(new Rejected({})) + ? select.negative.decoded(new Rejected({})) : score === 0 - ? select.zero(new Rejected({})) + ? select.zero.decoded(new Rejected({})) : score >= 70 - ? select.passing(new Approved({})) - : select.failing(new Rejected({})) + ? select.passing.decoded(new Approved({})) + : select.failing.decoded(new Rejected({})) }) } }, Approved: { on: { Recheck: (to) => - to.branch.Flow.initial.resolve(({ event, target }) => target(new Flow({ score: event.score }))) + to.branch.Flow.initial.resolve(({ event, target }) => target.decoded(new Flow({ score: event.score }))) } } } @@ -84,35 +85,40 @@ describe("Machine choice pseudo-states", () => { key: "perfect", title: "Score is perfect", target: "Flow.Approved", - selection: { path: "Flow.Approved", kind: "state", scope: "local" } + selection: { path: "Flow.Approved", kind: "state", scope: "local" }, + updates: [] }, { type: "branch", key: "negative", title: "Score is negative", target: "Flow.Rejected", - selection: { path: "Flow.Rejected", kind: "state", scope: "local" } + selection: { path: "Flow.Rejected", kind: "state", scope: "local" }, + updates: [] }, { type: "branch", key: "zero", title: "Score is zero", target: "Flow.Rejected", - selection: { path: "Flow.Rejected", kind: "state", scope: "local" } + selection: { path: "Flow.Rejected", kind: "state", scope: "local" }, + updates: [] }, { type: "branch", key: "passing", title: "Score is at least 70", target: "Flow.Approved", - selection: { path: "Flow.Approved", kind: "state", scope: "local" } + selection: { path: "Flow.Approved", kind: "state", scope: "local" }, + updates: [] }, { type: "branch", key: "failing", title: "failing", target: "Flow.Rejected", - selection: { path: "Flow.Rejected", kind: "state", scope: "local" } + selection: { path: "Flow.Rejected", kind: "state", scope: "local" }, + updates: [] } ]) assert.deepStrictEqual(Machine.stateNodes(machine).map(({ path, type }) => ({ path, type })), [ @@ -152,7 +158,7 @@ describe("Machine choice pseudo-states", () => { states: states.states, events: Machine.events(), initial: (to) => - to.Flow.initial.resolve(({ target }) => target(new Flow({ score: 80 }), (flow) => flow.First())) + to.Flow.initial.resolve(({ target }) => target.decoded(new Flow({ score: 80 }), (flow) => flow.First())) }).handle({ Flow: { states: { @@ -160,7 +166,7 @@ describe("Machine choice pseudo-states", () => { choice: (to) => to.local.Second().resolve(({ target }) => target()) }, Second: { - choice: (to) => to.local.Approved().resolve(({ target }) => target(new Approved({}))) + choice: (to) => to.local.Approved().resolve(({ target }) => target.decoded(new Approved({}))) } } } @@ -200,7 +206,7 @@ describe("Machine choice pseudo-states", () => { states: states.states, events: Machine.events(), initial: (to) => - to.Flow.initial.resolve(({ target }) => target(new Flow({ score: 80 }), (flow) => flow.First())) + to.Flow.initial.resolve(({ target }) => target.decoded(new Flow({ score: 80 }), (flow) => flow.First())) }).handle({ Flow: { states: { @@ -238,9 +244,9 @@ describe("Machine choice pseudo-states", () => { events: Machine.events(), initial: (to) => to.Flow.initial.resolve(({ target }) => - target( + target.decoded( new Flow({ score: 10 }), - (flow) => flow.Approved(new Approved({})) + (flow) => flow.Approved.decoded(new Approved({})) ) ) }).handle({ @@ -250,7 +256,7 @@ describe("Machine choice pseudo-states", () => { always: (to) => to.local.Routing().resolve(({ target }) => target()) }, Routing: { - choice: (to) => to.local.Rejected().resolve(({ target }) => target(new Rejected({}))) + choice: (to) => to.local.Rejected().resolve(({ target }) => target.decoded(new Rejected({}))) } } } @@ -294,13 +300,16 @@ describe("Machine choice pseudo-states", () => { states: states.states, events: Machine.events(), initial: (to) => - to.Flow.initial.resolve(({ target }) => target(new Flow({ score: 0 }), (flow) => flow.Done(new Done({})))) + to.Flow.initial.resolve(({ target }) => + target.decoded(new Flow({ score: 0 }), (flow) => flow.Done.decoded(new Done({}))) + ) }).handle({ Flow: { - onDone: (to) => to.full.Flow().resolve(({ state, target }) => target(state, (flow) => flow.Routing())), + onDone: (to) => + to.full.Flow().resolve(({ state, target }) => target.decoded(state, (flow) => flow.Routing())), states: { Routing: { - choice: (to) => to.local.Rejected().resolve(({ target }) => target(new Rejected({}))) + choice: (to) => to.local.Rejected().resolve(({ target }) => target.decoded(new Rejected({}))) } } } @@ -353,12 +362,12 @@ describe("Machine choice pseudo-states", () => { events: Machine.events(), initial: (to) => to.Board.initial.resolve(({ target }) => - target( + target.decoded( new Board({}), (board) => board - .Left(new Left({}), (left) => left.Routing()) - .Right(new Right({}), (right) => right.Routing()) + .Left.decoded(new Left({}), (left) => left.Routing()) + .Right.decoded(new Right({}), (right) => right.Routing()) ) ) }).handle({ @@ -367,14 +376,14 @@ describe("Machine choice pseudo-states", () => { Left: { states: { Routing: { - choice: (to) => to.local.Ready().resolve(({ target }) => target(new Ready({}))) + choice: (to) => to.local.Ready().resolve(({ target }) => target.decoded(new Ready({}))) } } }, Right: { states: { Routing: { - choice: (to) => to.local.Ready().resolve(({ target }) => target(new RightReady({}))) + choice: (to) => to.local.Ready().resolve(({ target }) => target.decoded(new RightReady({}))) } } } @@ -414,18 +423,21 @@ describe("Machine choice pseudo-states", () => { states: states.states, events: Machine.events(Leave, Resume), initial: (to) => - to.Flow.initial.resolve(({ target }) => target(new Flow({ score: 1 }), (flow) => flow.Active(new Active({})))) + to.Flow.initial.resolve(({ target }) => + target.decoded(new Flow({ score: 1 }), (flow) => flow.Active.decoded(new Active({}))) + ) }).handle({ Flow: { history: { Recent: { - default: ({ target }) => target.Flow(new Flow({ score: 0 }), (flow) => flow.Active(new Active({}))) + default: ({ target }) => + target.Flow.decoded(new Flow({ score: 0 }), (flow) => flow.Active.decoded(new Active({}))) } }, states: { Active: { on: { - Leave: (to) => to.full.Outside().resolve(({ target }) => target(new Outside({}))) + Leave: (to) => to.full.Outside().resolve(({ target }) => target.decoded(new Outside({}))) } }, Routing: { @@ -436,7 +448,7 @@ describe("Machine choice pseudo-states", () => { Outside: { on: { Resume: (to) => - to.full.Flow().resolve(({ target }) => target(new Flow({ score: 2 }), (flow) => flow.Routing())) + to.full.Flow().resolve(({ target }) => target.decoded(new Flow({ score: 2 }), (flow) => flow.Routing())) } } }) @@ -469,7 +481,7 @@ describe("Machine choice pseudo-states", () => { states: states.states, events: Machine.events(), initial: (to) => - to.Flow.initial.resolve(({ target }) => target(new Flow({ score: 1 }), (flow) => flow.Routing())) + to.Flow.initial.resolve(({ target }) => target.decoded(new Flow({ score: 1 }), (flow) => flow.Routing())) }).handle({ Flow: { history: { @@ -515,17 +527,17 @@ describe("Machine choice pseudo-states", () => { const historyChoice = Machine.make({ states: states.states, events: Machine.events(Resume), - initial: (to) => to.Outside().resolve(({ target }) => target(new Outside({}))) + initial: (to) => to.Outside().resolve(({ target }) => target.decoded(new Outside({}))) }).handle({ Flow: { history: { Recent: { - default: ({ target }) => target.Flow(new Flow({ score: 1 }), (flow) => flow.Routing()) + default: ({ target }) => target.Flow.decoded(new Flow({ score: 1 }), (flow) => flow.Routing()) } }, states: { Routing: { - choice: (to) => to.local.Active().resolve(({ target }) => target(new Active({}))) + choice: (to) => to.local.Active().resolve(({ target }) => target.decoded(new Active({}))) } } }, @@ -572,35 +584,40 @@ describe("Machine choice pseudo-states", () => { key: "perfect", title: "Score is perfect", target: "Flow.Approved", - selection: { path: "Flow.Approved", kind: "state", scope: "local" } + selection: { path: "Flow.Approved", kind: "state", scope: "local" }, + updates: [] }, { type: "branch", key: "negative", title: "Score is negative", target: "Flow.Rejected", - selection: { path: "Flow.Rejected", kind: "state", scope: "local" } + selection: { path: "Flow.Rejected", kind: "state", scope: "local" }, + updates: [] }, { type: "branch", key: "zero", title: "Score is zero", target: "Flow.Rejected", - selection: { path: "Flow.Rejected", kind: "state", scope: "local" } + selection: { path: "Flow.Rejected", kind: "state", scope: "local" }, + updates: [] }, { type: "branch", key: "passing", title: "Score is at least 70", target: "Flow.Approved", - selection: { path: "Flow.Approved", kind: "state", scope: "local" } + selection: { path: "Flow.Approved", kind: "state", scope: "local" }, + updates: [] }, { type: "branch", key: "failing", title: "failing", target: "Flow.Rejected", - selection: { path: "Flow.Rejected", kind: "state", scope: "local" } + selection: { path: "Flow.Rejected", kind: "state", scope: "local" }, + updates: [] } ] } diff --git a/test/machine/DeepHandlers.test.ts b/test/machine/DeepHandlers.test.ts index 8559db5..3c60dd3 100644 --- a/test/machine/DeepHandlers.test.ts +++ b/test/machine/DeepHandlers.test.ts @@ -135,7 +135,7 @@ const machine = Machine.make({ on: { Advance: (to) => to.local.done().resolve(({ event, target }) => - target(new DeepDone({ value: event.value })) + target.decoded(new DeepDone({ value: event.value })) ) } }, diff --git a/test/machine/DynamicChildren.test.ts b/test/machine/DynamicChildren.test.ts index 99e011a..b039658 100644 --- a/test/machine/DynamicChildren.test.ts +++ b/test/machine/DynamicChildren.test.ts @@ -37,14 +37,14 @@ describe("dynamic child machines", () => { parent: Machine.parent(PlantOwnerEvents), initial: (to) => to.PlantActive().resolve(({ input, target }) => - target(new PlantActive({ id: input.id, produced: input.production })) + target.decoded(new PlantActive({ id: input.id, produced: input.production })) ) }).handle({ PlantActive: { on: { Produce: (to) => to.full.PlantActive().resolve(({ event, state, target }) => - target(new PlantActive({ ...state, produced: state.produced + event.amount })) + target.decoded(new PlantActive({ ...state, produced: state.produced + event.amount })) ), Report: (to) => to.none.resolve(({ parent, state }, enqueue) => { @@ -73,7 +73,8 @@ describe("dynamic child machines", () => { states: parentStates.states, events: Machine.events(PlantOwnerEvents, Grow, Decommission), input: Schema.Array(PlantInput), - initial: (to) => to.Commissioning().resolve(({ input, target }) => target(new Commissioning({ plants: input }))) + initial: (to) => + to.Commissioning().resolve(({ input, target }) => target.decoded(new Commissioning({ plants: input }))) }).handle({ Commissioning: { invoke: (from) => @@ -82,16 +83,20 @@ describe("dynamic child machines", () => { state.plants, (input) => children.spawn(Plant(input.id), { input }), { discard: true } - )).onDone((to) => to.full.Operating().resolve(({ target }) => target(new Operating({ reports: 0 })))) + )).onDone((to) => + to.full.Operating().resolve(({ target }) => target.decoded(new Operating({ reports: 0 }))) + ) .onFailure((to) => to.none) }, Operating: { on: { PlantReported: (to) => - to.full.Operating().resolve(({ state, target }) => target(new Operating({ reports: state.reports + 1 }))), + to.full.Operating().resolve(({ state, target }) => + target.decoded(new Operating({ reports: state.reports + 1 })) + ), Grow: (to) => to.full.Commissioning().resolve(({ event, target }) => - target(new Commissioning({ plants: event.plants })) + target.decoded(new Commissioning({ plants: event.plants })) ), Decommission: (to) => to.none.resolve(({ event }, enqueue) => { @@ -198,7 +203,7 @@ describe("dynamic child machines", () => { const childMachine = Machine.make({ states: { ChildIdle }, events: Machine.events(), - initial: (to) => to.ChildIdle().resolve(({ target }) => target(new ChildIdle({}))) + initial: (to) => to.ChildIdle().resolve(({ target }) => target.decoded(new ChildIdle({}))) }).handle({ ChildIdle: {} }) const Child = Machine.childFamily(childMachine) class Starting extends Schema.TaggedClass("DynamicDuplicateStarting")("Starting", {}) {} @@ -209,7 +214,7 @@ describe("dynamic child machines", () => { const parentMachine = Machine.make({ states: { Starting, DuplicateRejected }, events: Machine.events(), - initial: (to) => to.Starting().resolve(({ target }) => target(new Starting({}))) + initial: (to) => to.Starting().resolve(({ target }) => target.decoded(new Starting({}))) }).handle({ Starting: { invoke: (from) => @@ -238,7 +243,8 @@ describe("dynamic child machines", () => { states: { WorkerIdle }, events: Machine.events(), input: Input, - initial: (to) => to.WorkerIdle().resolve(({ input, target }) => target(new WorkerIdle({ id: input.id }))) + initial: (to) => + to.WorkerIdle().resolve(({ input, target }) => target.decoded(new WorkerIdle({ id: input.id }))) }).handle({ WorkerIdle: {} }) const Worker = Machine.childFamily(workerMachine) let scoped: Machine.ChildMachine.Ref> | undefined @@ -264,7 +270,7 @@ describe("dynamic child machines", () => { const parentMachine = Machine.make({ states: { Running }, events: Machine.events(), - initial: (to) => to.Running().resolve(({ target }) => target(new Running({}))) + initial: (to) => to.Running().resolve(({ target }) => target.decoded(new Running({}))) }).handle({ Running: { invoke: (from) => from.logic("supervisor", { address: Supervisor, logic: supervisorLogic }) @@ -297,12 +303,14 @@ describe("dynamic child machines", () => { const unitMachine = Machine.make({ states: { UnitActive }, events: Machine.events(Increment), - initial: (to) => to.UnitActive().resolve(({ target }) => target(new UnitActive({ count: 0 }))) + initial: (to) => to.UnitActive().resolve(({ target }) => target.decoded(new UnitActive({ count: 0 }))) }).handle({ UnitActive: { on: { Increment: (to) => - to.full.UnitActive().resolve(({ state, target }) => target(new UnitActive({ count: state.count + 1 }))) + to.full.UnitActive().resolve(({ state, target }) => + target.decoded(new UnitActive({ count: state.count + 1 })) + ) } } }) @@ -312,7 +320,7 @@ describe("dynamic child machines", () => { const parentMachine = Machine.make({ states: { Managing, Ready }, events: Machine.events(), - initial: (to) => to.Managing().resolve(({ target }) => target(new Managing({}))) + initial: (to) => to.Managing().resolve(({ target }) => target.decoded(new Managing({}))) }).handle({ Managing: { invoke: (from) => diff --git a/test/machine/History.test.ts b/test/machine/History.test.ts index 392b8d3..e04cacf 100644 --- a/test/machine/History.test.ts +++ b/test/machine/History.test.ts @@ -127,9 +127,9 @@ const makeCheckoutMachine = ( initial: ((to: any) => initial.path === "checkout" ? to.checkout.initial.resolve(({ target }: any) => - target( + target.decoded( new Checkout({ orderId: "initial" }), - (checkout: any) => checkout.shipping(new Shipping({ address: "initial" })) + (checkout: any) => checkout.shipping.decoded(new Shipping({ address: "initial" })) ) ) : to.support().resolve(() => initial)) as any @@ -156,9 +156,9 @@ const makeCheckoutMachine = ( } }, on: { - Leave: (to) => to.full.support().resolve(({ target }) => target(new Support({ ticket: "ticket-1" }))), + Leave: (to) => to.full.support().resolve(({ target }) => target.decoded(new Support({ ticket: "ticket-1" }))), GoShipping: (to) => - to.local.shipping().resolve(({ event, target }) => target(new Shipping({ address: event.address }))), + to.local.shipping().resolve(({ event, target }) => target.decoded(new Shipping({ address: event.address }))), ReenterHistory: (to) => to.history.checkout.exact.resolve(({ target }) => target(), { reenter: true }) }, states: { @@ -166,9 +166,9 @@ const makeCheckoutMachine = ( on: { EnterVerifying: (to) => to.local.payment().resolve(({ target }) => - target( + target.decoded( new Payment({ attempt: 2 }), - (payment) => payment.verifying(new Verifying({ challengeId: "challenge-7" })) + (payment) => payment.verifying.decoded(new Verifying({ challengeId: "challenge-7" })) ) ) } @@ -182,7 +182,7 @@ const makeCheckoutMachine = ( }, initialize: ({ state, builder }) => { onInitialize?.() - return builder(new CardEntry({ cardNumber: `fresh-${state.attempt}` })) + return builder.decoded(new CardEntry({ cardNumber: `fresh-${state.attempt}` })) }, states: { verifying: { @@ -303,61 +303,67 @@ const makeWorkspaceMachine = (initialized: Array) => events: Machine.events(LeaveWorkspace, ResumeWorkspaceShallow, ResumeWorkspaceDeep), initial: (to) => to.workspace.initial.resolve(({ target }) => - target(new Workspace({ id: "initial" }), (workspace) => + target.decoded(new Workspace({ id: "initial" }), (workspace) => workspace - .editor(new Editor({ documentId: "initial" }), (editor) => editor.writing(new Writing({ draft: "" }))) - .sidebar(new Sidebar({ width: 0 }), (sidebar) => sidebar.files(new Files({ directory: "/" })))) + .editor.decoded( + new Editor({ documentId: "initial" }), + (editor) => editor.writing.decoded(new Writing({ draft: "" })) + ) + .sidebar.decoded( + new Sidebar({ width: 0 }), + (sidebar) => sidebar.files.decoded(new Files({ directory: "/" })) + )) ) }).handle({ workspace: { history: { recent: { default: ({ target }) => - target.workspace( + target.workspace.decoded( new Workspace({ id: "fallback" }), (workspace) => workspace - .editor( + .editor.decoded( new Editor({ documentId: "fallback" }), - (editor) => editor.writing(new Writing({ draft: "" })) + (editor) => editor.writing.decoded(new Writing({ draft: "" })) ) - .sidebar( + .sidebar.decoded( new Sidebar({ width: 200 }), - (sidebar) => sidebar.files(new Files({ directory: "/" })) + (sidebar) => sidebar.files.decoded(new Files({ directory: "/" })) ) ) }, exact: { default: ({ target }) => - target.workspace( + target.workspace.decoded( new Workspace({ id: "fallback" }), (workspace) => workspace - .editor( + .editor.decoded( new Editor({ documentId: "fallback" }), - (editor) => editor.writing(new Writing({ draft: "" })) + (editor) => editor.writing.decoded(new Writing({ draft: "" })) ) - .sidebar( + .sidebar.decoded( new Sidebar({ width: 200 }), - (sidebar) => sidebar.files(new Files({ directory: "/" })) + (sidebar) => sidebar.files.decoded(new Files({ directory: "/" })) ) ) } }, on: { - LeaveWorkspace: (to) => to.full.away().resolve(({ target }) => target(new Away({}))) + LeaveWorkspace: (to) => to.full.away().resolve(({ target }) => target.decoded(new Away({}))) }, states: { editor: { initialize: ({ state, builder }) => { initialized.push("editor") - return builder(new Writing({ draft: `fresh:${state.documentId}` })) + return builder.decoded(new Writing({ draft: `fresh:${state.documentId}` })) } }, sidebar: { initialize: ({ state, builder }) => { initialized.push("sidebar") - return builder(new Files({ directory: `/fresh/${state.width}` })) + return builder.decoded(new Files({ directory: `/fresh/${state.width}` })) } } } @@ -416,15 +422,15 @@ const nestedHistoryMachine = Machine.make({ events: Machine.events(RestoreEditor, DefaultEditor), initial: (to) => to.workspace.initial.resolve(({ target }) => - target( + target.decoded( new Workspace({ id: "workspace-1" }), (workspace) => workspace - .editor( + .editor.decoded( new Editor({ documentId: "document-1" }), - (editor) => editor.writing(new Writing({ draft: "" })) + (editor) => editor.writing.decoded(new Writing({ draft: "" })) ) - .sidebar(new Search({ query: "untouched" })) + .sidebar.decoded(new Search({ query: "untouched" })) ) ) }).handle({ @@ -434,15 +440,15 @@ const nestedHistoryMachine = Machine.make({ history: { exact: { default: ({ target }) => - target.workspace( + target.workspace.decoded( new Workspace({ id: "fallback-workspace" }), (workspace) => workspace - .editor( + .editor.decoded( new Editor({ documentId: "fallback" }), - (editor) => editor.writing(new Writing({ draft: "" })) + (editor) => editor.writing.decoded(new Writing({ draft: "" })) ) - .sidebar(new Search({ query: "fallback" })) + .sidebar.decoded(new Search({ query: "fallback" })) ) } }, diff --git a/test/machine/InitialEntry.test.ts b/test/machine/InitialEntry.test.ts index 2df186b..3264141 100644 --- a/test/machine/InitialEntry.test.ts +++ b/test/machine/InitialEntry.test.ts @@ -41,7 +41,7 @@ const makeMachine = () => Machine.make({ states: States.states, events: Machine.events(Open, OpenInvalid), - initial: (to) => to.closed().resolve(({ target }) => target(new Closed({}))) + initial: (to) => to.closed().resolve(({ target }) => target.decoded(new Closed({}))) }).handle({ closed: { on: { @@ -74,11 +74,11 @@ const makeParallelMachine = () => Machine.make({ states: ParallelStates.states, events: Machine.events(EnterDashboard), - initial: (to) => to.outside().resolve(({ target }) => target(new Outside({}))) + initial: (to) => to.outside().resolve(({ target }) => target.decoded(new Outside({}))) }).handle({ outside: { on: { - EnterDashboard: (to) => to.full.dashboard.initial.resolve(({ target }) => target(new Dashboard({}))) + EnterDashboard: (to) => to.full.dashboard.initial.resolve(({ target }) => target.decoded(new Dashboard({}))) } }, dashboard: { @@ -107,17 +107,17 @@ const makeChoiceMachine = () => Machine.make({ states: ChoiceStates.states, events: Machine.events(EnterFlow), - initial: (to) => to.outside().resolve(({ target }) => target(new Outside({}))) + initial: (to) => to.outside().resolve(({ target }) => target.decoded(new Outside({}))) }).handle({ outside: { on: { - EnterFlow: (to) => to.full.flow.initial.resolve(({ target }) => target(new Flow({}))) + EnterFlow: (to) => to.full.flow.initial.resolve(({ target }) => target.decoded(new Flow({}))) } }, flow: { states: { routing: { - choice: (to) => to.local.approved().resolve(({ target }) => target(new Approved({}))) + choice: (to) => to.local.approved().resolve(({ target }) => target.decoded(new Approved({}))) } } } @@ -135,7 +135,7 @@ const makeStructuralMachine = () => Machine.make({ states: StructuralStates.states, events: Machine.events(EnterFlow), - initial: (to) => to.outside().resolve(({ target }) => target(new Outside({}))) + initial: (to) => to.outside().resolve(({ target }) => target.decoded(new Outside({}))) }).handle({ outside: { on: { @@ -162,7 +162,7 @@ const makeNestedMachine = () => Machine.make({ states: NestedStates.states, events: Machine.events(OpenLocal, OpenBranch), - initial: (to) => to.root.initial.resolve(({ target }) => target.from((root) => root.closed(new Closed({})))) + initial: (to) => to.root.initial.resolve(({ target }) => target.from((root) => root.closed.decoded(new Closed({})))) }).handle({ root: { states: { @@ -292,7 +292,8 @@ describe("declared initial entry", () => { branchIndex: 0, branchKey: undefined, target: "flow", - resolvedTarget: "flow" + resolvedTarget: "flow", + updates: [] }, { source: "flow.routing", trigger: { type: "choice" }, @@ -300,7 +301,8 @@ describe("declared initial entry", () => { branchIndex: 0, branchKey: undefined, target: "flow.approved", - resolvedTarget: "flow.approved" + resolvedTarget: "flow.approved", + updates: [] }]) })) diff --git a/test/machine/Inspection.test.ts b/test/machine/Inspection.test.ts index cce14d5..9750215 100644 --- a/test/machine/Inspection.test.ts +++ b/test/machine/Inspection.test.ts @@ -39,10 +39,10 @@ const machine = Machine.make({ events: Machine.events(), initial: (to) => to.root.initial.resolve(({ target }) => - target(new Root({}), (root) => + target.decoded(new Root({}), (root) => root - .flow(new Flow({}), (flow) => flow.idle(new Idle({}))) - .side(new Side({}))) + .flow.decoded(new Flow({}), (flow) => flow.idle.decoded(new Idle({}))) + .side.decoded(new Side({}))) ) }) @@ -60,12 +60,12 @@ const ChoiceStates = Machine.states({ const choiceMachine = Machine.make({ states: ChoiceStates.states, events: Machine.events(), - initial: (to) => to.Flow.initial.resolve(({ target }) => target(new ChoiceFlow({}), (flow) => flow.Routing())) + initial: (to) => to.Flow.initial.resolve(({ target }) => target.decoded(new ChoiceFlow({}), (flow) => flow.Routing())) }).handle({ Flow: { states: { Routing: { - choice: (to) => to.local.Ready().resolve(({ target }) => target(new Ready({}))) + choice: (to) => to.local.Ready().resolve(({ target }) => target.decoded(new Ready({}))) } } } diff --git a/test/machine/Invoke.test.ts b/test/machine/Invoke.test.ts index f45d586..c642edd 100644 --- a/test/machine/Invoke.test.ts +++ b/test/machine/Invoke.test.ts @@ -80,7 +80,7 @@ describe("inline invoke", () => { const definition = Machine.make({ states: states.states, events: Machine.events(Add), - initial: (to) => to.Collecting().resolve(({ target }) => target(new Collecting({ values: [] }))) + initial: (to) => to.Collecting().resolve(({ target }) => target.decoded(new Collecting({ values: [] }))) }) const machine = definition.handle({ Collecting: { @@ -90,12 +90,14 @@ describe("inline invoke", () => { enqueue.raise(new Add({ value: element })) }) ).onDone((to) => - to.full.Complete().resolve(({ state, target }) => target(new Complete({ value: state.values.join(",") }))) + to.full.Complete().resolve(({ state, target }) => + target.decoded(new Complete({ value: state.values.join(",") })) + ) ), on: { Add: (to) => to.full.Collecting().resolve(({ event, state, target }) => - target(new Collecting({ values: [...state.values, event.value] })) + target.decoded(new Collecting({ values: [...state.values, event.value] })) ) } }, @@ -111,7 +113,8 @@ describe("inline invoke", () => { branches: [{ type: "direct", target: "Collecting", - selection: { path: "Collecting", kind: "state", scope: "full" } + selection: { path: "Collecting", kind: "state", scope: "full" }, + updates: [] }] }, { @@ -122,7 +125,8 @@ describe("inline invoke", () => { branches: [{ type: "direct", target: undefined, - selection: { path: undefined, kind: "none", scope: "local" } + selection: { path: undefined, kind: "none", scope: "local" }, + updates: [] }] }, { @@ -133,7 +137,8 @@ describe("inline invoke", () => { branches: [{ type: "direct", target: "Complete", - selection: { path: "Complete", kind: "state", scope: "full" } + selection: { path: "Complete", kind: "state", scope: "full" }, + updates: [] }] } ]) @@ -161,7 +166,7 @@ describe("inline invoke", () => { Loading: { invoke: (from) => from.stream("updates", () => Stream.fail("offline")).onDone((to) => to.none).onFailure((to) => - to.full.Failed().resolve(({ error, target }) => target(new Failed({ message: error }))) + to.full.Failed().resolve(({ error, target }) => target.decoded(new Failed({ message: error }))) ) }, Complete: {}, @@ -229,7 +234,9 @@ describe("inline invoke", () => { ).onDone((to) => to.none), on: { FinishStream: (to) => - to.full.Complete().resolve(({ event, target }) => target(new Complete({ value: String(event.value) }))) + to.full.Complete().resolve(({ event, target }) => + target.decoded(new Complete({ value: String(event.value) })) + ) } }, Complete: {}, @@ -258,7 +265,7 @@ describe("inline invoke", () => { Loading: { invoke: (from) => from.effect("load", () => Effect.succeed("ready")).onDone((to) => - to.full.Complete().resolve(({ output, target }) => target(new Complete({ value: output }))) + to.full.Complete().resolve(({ output, target }) => target.decoded(new Complete({ value: output }))) ) }, Complete: {}, @@ -273,7 +280,8 @@ describe("inline invoke", () => { branches: [{ type: "direct", target: "Complete", - selection: { path: "Complete", kind: "state", scope: "full" } + selection: { path: "Complete", kind: "state", scope: "full" }, + updates: [] }] }]) @@ -292,7 +300,7 @@ describe("inline invoke", () => { Loading: { invoke: (from) => from.effect("load", () => Effect.fail("offline")).onFailure((to) => - to.full.Failed().resolve(({ error, target }) => target(new Failed({ message: error }))) + to.full.Failed().resolve(({ error, target }) => target.decoded(new Failed({ message: error }))) ) }, Complete: {}, diff --git a/test/machine/LiveInspection.test.ts b/test/machine/LiveInspection.test.ts index af16479..aee6e94 100644 --- a/test/machine/LiveInspection.test.ts +++ b/test/machine/LiveInspection.test.ts @@ -19,14 +19,14 @@ const machine = Machine.make({ states: states.states, events: Events, emittedEvents: Emissions, - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({}))) }).handle({ Idle: { on: { Increment: (to) => to.full.Idle().resolve(({ event, target }, enqueue) => { enqueue.emit(Emissions.Notice({ value: event.by })) - return target(new Idle({})) + return target.decoded(new Idle({})) }) } } @@ -85,7 +85,8 @@ describe("Machine live inspection", () => { branchIndex: 0, branchKey: undefined, target: "Idle", - resolvedTarget: "Idle" + resolvedTarget: "Idle", + updates: [] }]) } @@ -128,7 +129,7 @@ describe("Machine live inspection", () => { id: "activity-root", states: states.states, events: Machine.events(), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({}))) }).handle({ Idle: { invoke: (from) => from.effect("worker", () => Effect.never) @@ -177,7 +178,7 @@ describe("Machine live inspection", () => { id: "stream-activity-root", states: states.states, events: Machine.events(), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({}))) }).handle({ Idle: { invoke: (from) => from.stream("updates", () => Stream.never).onDone((to) => to.none) @@ -219,7 +220,7 @@ describe("Machine live inspection", () => { states: childStates.states, events: ChildEvents, parent: Machine.parent(ParentEvents), - initial: (to) => to.ChildIdle().resolve(({ target }) => target(new ChildIdle({}))) + initial: (to) => to.ChildIdle().resolve(({ target }) => target.decoded(new ChildIdle({}))) }).handle({ ChildIdle: { on: { @@ -240,12 +241,12 @@ describe("Machine live inspection", () => { id: "parent-machine", states: parentStates.states, events: Machine.events(ParentEvents), - initial: (to) => to.ParentIdle().resolve(({ target }) => target(new ParentIdle({}))) + initial: (to) => to.ParentIdle().resolve(({ target }) => target.decoded(new ParentIdle({}))) }).handle({ ParentIdle: { invoke: (from) => from.child(Child), on: { - ChildReady: (to) => to.full.ParentDone().resolve(({ target }) => target(new ParentDone({}))) + ChildReady: (to) => to.full.ParentDone().resolve(({ target }) => target.decoded(new ParentDone({}))) } }, ParentDone: {} diff --git a/test/machine/LocalTargetWith.test.ts b/test/machine/LocalTargetWith.test.ts index dc39b3d..03c5351 100644 --- a/test/machine/LocalTargetWith.test.ts +++ b/test/machine/LocalTargetWith.test.ts @@ -57,7 +57,8 @@ describe("local compound target selection", () => { branches: [{ type: "direct", target: "search", - selection: { path: "search", kind: "state", scope: "local" } + selection: { path: "search", kind: "state", scope: "local" }, + updates: [] }] }, { source: "search.Updated", @@ -67,7 +68,8 @@ describe("local compound target selection", () => { branches: [{ type: "direct", target: "search.Idle", - selection: { path: "search.Idle", kind: "state", scope: "local" } + selection: { path: "search.Idle", kind: "state", scope: "local" }, + updates: [] }] }]) @@ -142,7 +144,8 @@ describe("local compound target selection", () => { branches: [{ type: "direct", target: "search", - selection: { path: "search", kind: "state", scope: "local" } + selection: { path: "search", kind: "state", scope: "local" }, + updates: [] }] }]) diff --git a/test/machine/Machine.test.ts b/test/machine/Machine.test.ts index 7b13082..aab14d1 100644 --- a/test/machine/Machine.test.ts +++ b/test/machine/Machine.test.ts @@ -108,7 +108,7 @@ describe("Machine", () => { const definition = Machine.make({ states: states.states, events: Machine.events(Ping), - initial: (to) => to.Stable().resolve(({ target }) => target(new Stable({}))) + initial: (to) => to.Stable().resolve(({ target }) => target.decoded(new Stable({}))) }) const handlingPing = definition.handle({ Stable: { on: { Ping: (to) => to.none } } }) const ignoringPing = definition.handle({ Stable: {} }) @@ -132,7 +132,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Ping), - initial: (to) => to.Stable().resolve(({ target }) => target(new Stable({}))) + initial: (to) => to.Stable().resolve(({ target }) => target.decoded(new Stable({}))) }).handle({ Stable: { on: { @@ -157,7 +157,8 @@ describe("Machine", () => { branches: [{ type: "direct", target: undefined, - selection: { path: undefined, kind: "none", scope: "local" } + selection: { path: undefined, kind: "none", scope: "local" }, + updates: [] }] }]) @@ -224,7 +225,7 @@ describe("Machine", () => { const definition = Machine.make({ states: states.states, events: Machine.events(Ping), - initial: (to) => to.Stable().resolve(({ target }) => target(new Stable({}))) + initial: (to) => to.Stable().resolve(({ target }) => target.decoded(new Stable({}))) }) const machine = definition.handle({ Stable: { @@ -238,7 +239,7 @@ describe("Machine", () => { declarations = captured return to.branches(captured).resolve(({ event, select }) => event.route - ? select.refresh(new Stable({})) + ? select.refresh.decoded(new Stable({})) : select.unchanged(), { reenter: true }) } } @@ -258,13 +259,15 @@ describe("Machine", () => { key: "unchanged", title: "unchanged", target: undefined, - selection: { path: undefined, kind: "none", scope: "local" } + selection: { path: undefined, kind: "none", scope: "local" }, + updates: [] }, { type: "branch", key: "refresh", title: "Refresh stable state", target: "Stable", - selection: { path: "Stable", kind: "state", scope: "full" } + selection: { path: "Stable", kind: "state", scope: "full" }, + updates: [] }] }]) @@ -285,7 +288,7 @@ describe("Machine", () => { Machine.make({ states: { Stable }, events: Machine.events(Ping), - initial: (to) => to.Stable().resolve(({ target }) => target(new Stable({}))) + initial: (to) => to.Stable().resolve(({ target }) => target.decoded(new Stable({}))) }) const handle = (branches: (to: any) => object) => () => makeDefinition().handle({ @@ -317,7 +320,7 @@ describe("Machine", () => { const machine = Machine.make({ states: { Stable }, events: Machine.events(Capture, Reuse), - initial: (to) => to.Stable().resolve(({ target }) => target(new Stable({}))) + initial: (to) => to.Stable().resolve(({ target }) => target.decoded(new Stable({}))) }).handle({ Stable: { on: { @@ -502,7 +505,8 @@ describe("Machine", () => { states: states.states, events: Machine.events(Submit), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }) const planned = yield* Machine.planInitial(machine, { userId: "user-1" }) @@ -516,7 +520,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({ userId: "user-1" }))) }) assert.strictEqual(Machine.isMachine(machine), true) @@ -530,7 +534,7 @@ describe("Machine", () => { const definition = Machine.make({ states: states.states, events: Machine.events(Convert), - initial: (to) => to.Submit().resolve(({ target }) => target(new Submit({ value: "loaded" }))) + initial: (to) => to.Submit().resolve(({ target }) => target.decoded(new Submit({ value: "loaded" }))) }) const machine = definition.handle({ Submit: { @@ -560,13 +564,14 @@ describe("Machine", () => { states: states.states, events: Machine.events(Submit), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { Submit: (to) => to.full.Loading().resolve(({ target }) => { - return target(new Loading({ requestId: "request-1" })) + return target.decoded(new Loading({ requestId: "request-1" })) }) } } @@ -587,7 +592,7 @@ describe("Machine", () => { const machine = Machine.make({ states: defined.states, events: Machine.events(Submit), - initial: (to) => to.idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) + initial: (to) => to.idle().resolve(({ target }) => target.decoded(new Idle({ userId: "user-1" }))) }) const planned = yield* Machine.planInitial(machine) @@ -711,9 +716,9 @@ describe("Machine", () => { events: Machine.events(Authorize), initial: (to) => to.payment.initial.resolve(({ target }) => - target( + target.decoded( payment, - (payment) => payment.entering(entering) + (payment) => payment.entering.decoded(entering) ) ) }) @@ -762,17 +767,17 @@ describe("Machine", () => { events: Machine.events(ReserveInventory), initial: (to) => to.fulfillment.initial.resolve(({ target }) => - target( + target.decoded( fulfillment, (fulfillment) => fulfillment - .inventory( + .inventory.decoded( inventory, - (inventory) => inventory.checking(checking) + (inventory) => inventory.checking.decoded(checking) ) - .shipping( + .shipping.decoded( shipping, - (shipping) => shipping.quoting(quoting) + (shipping) => shipping.quoting.decoded(quoting) ) ) ) @@ -1510,7 +1515,9 @@ describe("Machine", () => { events: Machine.events(NonEmptySubmit), input: NonEmptyInput, initial: (to) => - to.NonEmptyIdle().resolve(({ input: input, target }) => target(new NonEmptyIdle({ userId: input.userId }))) + to.NonEmptyIdle().resolve(({ input: input, target }) => + target.decoded(new NonEmptyIdle({ userId: input.userId })) + ) }) const error = yield* Effect.flip(Machine.planInitial(machine, { userId: "" as any })) @@ -1525,7 +1532,9 @@ describe("Machine", () => { states: states.states, events: Machine.events(NonEmptySubmit), initial: (to) => - to.NonEmptyIdle().resolve(({ target }) => target(unsafeTagged({ _tag: "NonEmptyIdle", userId: "" }))) + to.NonEmptyIdle().resolve(({ target }) => + target.decoded(unsafeTagged({ _tag: "NonEmptyIdle", userId: "" })) + ) }) const error = yield* Effect.flip(Machine.planInitial(machine)) @@ -1539,11 +1548,12 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(NonEmptySubmit), - initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) + initial: (to) => + to.NonEmptyIdle().resolve(({ target }) => target.decoded(new NonEmptyIdle({ userId: "user-1" }))) }).handle({ NonEmptyIdle: { on: { - NonEmptySubmit: (to) => to.full.NonEmptyIdle().resolve(({ state, target }) => target(state)) + NonEmptySubmit: (to) => to.full.NonEmptyIdle().resolve(({ state, target }) => target.decoded(state)) } } }) @@ -1565,11 +1575,12 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(NonEmptySubmit), - initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) + initial: (to) => + to.NonEmptyIdle().resolve(({ target }) => target.decoded(new NonEmptyIdle({ userId: "user-1" }))) }).handle({ NonEmptyIdle: { on: { - NonEmptySubmit: (to) => to.full.NonEmptyIdle().resolve(({ state, target }) => target(state)) + NonEmptySubmit: (to) => to.full.NonEmptyIdle().resolve(({ state, target }) => target.decoded(state)) } } }) @@ -1600,13 +1611,14 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(NonEmptySubmit), - initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) + initial: (to) => + to.NonEmptyIdle().resolve(({ target }) => target.decoded(new NonEmptyIdle({ userId: "user-1" }))) }).handle({ NonEmptyIdle: { on: { NonEmptySubmit: (to) => to.full.NonEmptyLoading().resolve(({ target }) => - target(unsafeTagged({ _tag: "NonEmptyLoading", requestId: "" })) + target.decoded(unsafeTagged({ _tag: "NonEmptyLoading", requestId: "" })) ) } } @@ -1629,13 +1641,14 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(NonEmptySubmit), - initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) + initial: (to) => + to.NonEmptyIdle().resolve(({ target }) => target.decoded(new NonEmptyIdle({ userId: "user-1" }))) }).handle({ NonEmptyIdle: { on: { NonEmptySubmit: (to) => to.full.NonEmptyIdle().resolve(({ target }) => - target(unsafeTagged({ _tag: "NonEmptyIdle", userId: "" })) + target.decoded(unsafeTagged({ _tag: "NonEmptyIdle", userId: "" })) ) } } @@ -1666,12 +1679,15 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(NonEmptySubmit), - initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) + initial: (to) => + to.NonEmptyIdle().resolve(({ target }) => target.decoded(new NonEmptyIdle({ userId: "user-1" }))) }).handle({ NonEmptyIdle: { on: { NonEmptySubmit: (to) => - to.full.done().resolve(({ event, target }) => target(new NonEmptyDone({ requestId: event.value }))) + to.full.done().resolve(({ event, target }) => + target.decoded(new NonEmptyDone({ requestId: event.value })) + ) } }, done: { @@ -1714,12 +1730,12 @@ describe("Machine", () => { events: Machine.events(), initial: (to) => to.all.initial.resolve(({ target }) => - target( + target.decoded( new ParallelRoot({ id: "all" }), (all) => all - .left(new ParallelLeftDone({ id: "left" })) - .right(new ParallelRightDone({ id: "right" })) + .left.decoded(new ParallelLeftDone({ id: "left" })) + .right.decoded(new ParallelRightDone({ id: "right" })) ) ) }).handle({ @@ -1739,7 +1755,8 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(NonEmptySubmit), - initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) + initial: (to) => + to.NonEmptyIdle().resolve(({ target }) => target.decoded(new NonEmptyIdle({ userId: "user-1" }))) }) const error = yield* Effect.flip( @@ -1763,7 +1780,7 @@ describe("Machine", () => { id: "Counter", states: states.states, events: Machine.events(), - initial: (to) => to.count().resolve(({ target }) => target(new EncodedCount({ count: 1 }))) + initial: (to) => to.count().resolve(({ target }) => target.decoded(new EncodedCount({ count: 1 }))) }) const planned = yield* Machine.planInitial(machine) @@ -1812,17 +1829,17 @@ describe("Machine", () => { events: Machine.events(), initial: (to) => to.fulfillment.initial.resolve(({ target }) => - target( + target.decoded( new Fulfillment({ id: "fulfillment-1" }), (fulfillment) => fulfillment - .inventory( + .inventory.decoded( new Inventory({ warehouse: "warehouse-1" }), - (inventory) => inventory.checking(new CheckingInventory({ sku: "sku-1" })) + (inventory) => inventory.checking.decoded(new CheckingInventory({ sku: "sku-1" })) ) - .shipping( + .shipping.decoded( new Shipping({ address: "Main Street" }), - (shipping) => shipping.quoting(new QuotingShipping({ postalCode: "12345" })) + (shipping) => shipping.quoting.decoded(new QuotingShipping({ postalCode: "12345" })) ) ) ) @@ -1863,12 +1880,12 @@ describe("Machine", () => { events: Machine.events(), initial: (to) => to.all.initial.resolve(({ target }) => - target( + target.decoded( new ParallelRoot({ id: "all" }), (all) => all - .left(new ParallelLeftDone({ id: "left" })) - .right(new ParallelRightDone({ id: "right" })) + .left.decoded(new ParallelLeftDone({ id: "left" })) + .right.decoded(new ParallelRightDone({ id: "right" })) ) ) }).handle({ @@ -1909,12 +1926,12 @@ describe("Machine", () => { events: Machine.events(), initial: (to) => to.all.initial.resolve(({ target }) => - target( + target.decoded( new ParallelRoot({ id: "all" }), (all) => all - .left(new ParallelLeftDone({ id: "left" })) - .right(new ParallelRightDone({ id: "right" })) + .left.decoded(new ParallelLeftDone({ id: "left" })) + .right.decoded(new ParallelRightDone({ id: "right" })) ) ) }).handle({ @@ -1945,7 +1962,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: (to) => to.done().resolve(({ target }) => target(new ParallelLeftDone({ id: "done" }))) + initial: (to) => to.done().resolve(({ target }) => target.decoded(new ParallelLeftDone({ id: "done" }))) }).handle({ done: { output: () => undefined } }) @@ -1964,7 +1981,8 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) + initial: (to) => + to.NonEmptyIdle().resolve(({ target }) => target.decoded(new NonEmptyIdle({ userId: "user-1" }))) }) const error = yield* Machine.encodeSnapshot(machine, { @@ -1981,7 +1999,8 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) + initial: (to) => + to.NonEmptyIdle().resolve(({ target }) => target.decoded(new NonEmptyIdle({ userId: "user-1" }))) }) const error = yield* Machine.encodeSnapshot(machine, { @@ -1999,7 +2018,8 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: (to) => to.NonEmptyIdle().resolve(({ target }) => target(new NonEmptyIdle({ userId: "user-1" }))) + initial: (to) => + to.NonEmptyIdle().resolve(({ target }) => target.decoded(new NonEmptyIdle({ userId: "user-1" }))) }) const error = yield* Machine.decodeSnapshot(machine, { @@ -2030,9 +2050,9 @@ describe("Machine", () => { events: Machine.events(), initial: (to) => to.payment.initial.resolve(({ target }) => - target( + target.decoded( new Payment({ id: "payment-1" }), - (payment) => payment.entering(new EnteringPayment({ amount: 1 })) + (payment) => payment.entering.decoded(new EnteringPayment({ amount: 1 })) ) ) }) @@ -2059,13 +2079,14 @@ describe("Machine", () => { }, events: Machine.events(Submit), input: Input, - initial: (to) => to.idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ idle: { on: { Submit: (to) => to.full.loading().resolve(({ event, state, target }) => - target(new Loading({ requestId: `${state.userId}:${event.value}` })) + target.decoded(new Loading({ requestId: `${state.userId}:${event.value}` })) ) } } @@ -2092,16 +2113,17 @@ describe("Machine", () => { b: Duplicate }, events: Machine.events(Submit, Reset), - initial: (to) => to.a().resolve(({ target }) => target(new Duplicate({ value: "a" }))) + initial: (to) => to.a().resolve(({ target }) => target.decoded(new Duplicate({ value: "a" }))) }).handle({ a: { on: { - Submit: (to) => to.full.b().resolve(({ event, target }) => target(new Duplicate({ value: event.value }))) + Submit: (to) => + to.full.b().resolve(({ event, target }) => target.decoded(new Duplicate({ value: event.value }))) } }, b: { on: { - Reset: (to) => to.full.a().resolve(({ target }) => target(new Duplicate({ value: "reset" }))) + Reset: (to) => to.full.a().resolve(({ target }) => target.decoded(new Duplicate({ value: "reset" }))) } } }) @@ -2132,11 +2154,12 @@ describe("Machine", () => { b: Duplicate }, events: Machine.events(Submit), - initial: (to) => to.a().resolve(({ target }) => target(new Duplicate({ value: "a" }))) + initial: (to) => to.a().resolve(({ target }) => target.decoded(new Duplicate({ value: "a" }))) }).handle({ a: { on: { - Submit: (to) => to.full.b().resolve(({ event, target }) => target(new Duplicate({ value: event.value }))) + Submit: (to) => + to.full.b().resolve(({ event, target }) => target.decoded(new Duplicate({ value: event.value }))) } } }) @@ -2164,12 +2187,12 @@ describe("Machine", () => { } }, events: Machine.events(Submit), - initial: (to) => to.idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) + initial: (to) => to.idle().resolve(({ target }) => target.decoded(new Idle({ userId: "user-1" }))) }).handle({ idle: { on: { Submit: (to) => - to.full.success().resolve(({ event, target }) => target(new Success({ requestId: event.value }))) + to.full.success().resolve(({ event, target }) => target.decoded(new Success({ requestId: event.value }))) } } }) @@ -2215,7 +2238,8 @@ describe("Machine", () => { }).handle({ payment: { on: { - Authorize: (to) => to.full.failed().resolve(({ target }) => target(new Failed({ message: "parent" }))) + Authorize: (to) => + to.full.failed().resolve(({ target }) => target.decoded(new Failed({ message: "parent" }))) }, states: { entering: { @@ -2224,7 +2248,7 @@ describe("Machine", () => { to.local.authorized().resolve(({ event, containingState, ancestors, target }) => { assert.deepStrictEqual(containingState, payment) assert.deepStrictEqual(ancestors, { payment }) - return target(new AuthorizedPayment({ code: event.code })) + return target.decoded(new AuthorizedPayment({ code: event.code })) }) } } @@ -2272,7 +2296,8 @@ describe("Machine", () => { }).handle({ payment: { on: { - Authorize: (to) => to.full.failed().resolve(({ target }) => target(new Failed({ message: "parent" }))) + Authorize: (to) => + to.full.failed().resolve(({ target }) => target.decoded(new Failed({ message: "parent" }))) }, states: { entering: { @@ -2283,7 +2308,7 @@ describe("Machine", () => { consume: { target: to.none } }).resolve(({ event, select, decline }, enqueue) => { if (event.code === "child") { - return select.authorize(new AuthorizedPayment({ code: event.code })) + return select.authorize.decoded(new AuthorizedPayment({ code: event.code })) } if (event.code === "consume") return select.consume() enqueue.emit(new Notice({})) @@ -2336,11 +2361,11 @@ describe("Machine", () => { events: Machine.events(), initial: (to) => to.workflow.initial.resolve(({ target }) => - target(new Workflow({}), (workflow) => workflow.waiting(new Waiting({ ready: false }))) + target.decoded(new Workflow({}), (workflow) => workflow.waiting.decoded(new Waiting({ ready: false }))) ) }).handle({ workflow: { - always: (to) => to.full.finished().resolve(({ target }) => target(new Finished({}))), + always: (to) => to.full.finished().resolve(({ target }) => target.decoded(new Finished({}))), states: { waiting: { always: (to) => @@ -2363,7 +2388,7 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Ping), - initial: (to) => to.Stable().resolve(({ target }) => target(new Stable({}))) + initial: (to) => to.Stable().resolve(({ target }) => target.decoded(new Stable({}))) }).handle({ Stable: { on: { @@ -2399,7 +2424,7 @@ describe("Machine", () => { events: Machine.events(), initial: (to) => to.workflow.initial.resolve(({ target }) => - target(new Workflow({}), (workflow) => workflow.complete(new Complete({}))) + target.decoded(new Workflow({}), (workflow) => workflow.complete.decoded(new Complete({}))) ) }).handle({ workflow: { @@ -2439,7 +2464,7 @@ describe("Machine", () => { events: Machine.events(Ping), initial: (to) => to.root.initial.resolve(({ target }) => - target(new Root({}), (root) => root.left(new Left({})).right(new Right({}))) + target.decoded(new Root({}), (root) => root.left.decoded(new Left({})).right.decoded(new Right({}))) ) }).handle({ root: { @@ -2447,7 +2472,7 @@ describe("Machine", () => { Ping: (to) => to.full.finished().resolve(({ target }) => { parentCalls++ - return target(new Finished({})) + return target.decoded(new Finished({})) }) }, states: { @@ -2513,14 +2538,14 @@ describe("Machine", () => { }).handle({ payment: { on: { - Reset: (to) => to.full.failed().resolve(({ target }) => target(new Failed({ message: "reset" }))) + Reset: (to) => to.full.failed().resolve(({ target }) => target.decoded(new Failed({ message: "reset" }))) }, states: { entering: { on: { Authorize: (to) => to.local.authorized().resolve(({ event, target }) => - target(new AuthorizedPayment({ code: event.code })) + target.decoded(new AuthorizedPayment({ code: event.code })) ) } }, @@ -2578,7 +2603,7 @@ describe("Machine", () => { }).handle({ payment: { on: { - Reset: (to) => to.full.idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) + Reset: (to) => to.full.idle().resolve(({ target }) => target.decoded(new Idle({ userId: "user-1" }))) } } }) @@ -2627,23 +2652,23 @@ describe("Machine", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Submit), - initial: (to) => to.idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) + initial: (to) => to.idle().resolve(({ target }) => target.decoded(new Idle({ userId: "user-1" }))) }).handle({ idle: { on: { Submit: (to) => to.full.fulfillment().resolve(({ event, target }) => - target( + target.decoded( new Fulfillment({ id: event.value }), (fulfillment) => fulfillment - .inventory( + .inventory.decoded( new Inventory({ warehouse: "warehouse-1" }), - (inventory) => inventory.reserved(new InventoryReserved({ reservationId: event.value })) + (inventory) => inventory.reserved.decoded(new InventoryReserved({ reservationId: event.value })) ) - .shipping( + .shipping.decoded( new Shipping({ address: "Main Street" }), - (shipping) => shipping.quoted(new ShippingQuoted({ quoteId: event.value })) + (shipping) => shipping.quoted.decoded(new ShippingQuoted({ quoteId: event.value })) ) ) ) @@ -2723,9 +2748,9 @@ describe("Machine", () => { events: Machine.events(Submit), initial: (to) => to.workflow.initial.resolve(({ target }) => - target( + target.decoded( workflow, - (workflow) => workflow.idle(new Idle({ userId: "user-1" })) + (workflow) => workflow.idle.decoded(new Idle({ userId: "user-1" })) ) ) }).handle({ @@ -2735,17 +2760,18 @@ describe("Machine", () => { on: { Submit: (to) => to.local.fulfillment().resolve(({ event, target }) => - target( + target.decoded( new Fulfillment({ id: event.value }), (fulfillment) => fulfillment - .inventory( + .inventory.decoded( new Inventory({ warehouse: "warehouse-1" }), - (inventory) => inventory.reserved(new InventoryReserved({ reservationId: event.value })) + (inventory) => + inventory.reserved.decoded(new InventoryReserved({ reservationId: event.value })) ) - .shipping( + .shipping.decoded( new Shipping({ address: "Main Street" }), - (shipping) => shipping.quoted(new ShippingQuoted({ quoteId: event.value })) + (shipping) => shipping.quoted.decoded(new ShippingQuoted({ quoteId: event.value })) ) ) ) @@ -2850,12 +2876,12 @@ describe("Machine", () => { on: { Submit: (to) => to.branch.app.flow.fulfillment().resolve(({ event, target }) => - target( + target.decoded( new Fulfillment({ id: event.value }), (fulfillment) => fulfillment - .inventory(new Inventory({ warehouse: "warehouse-1" })) - .shipping(new Shipping({ address: "Main Street" })) + .inventory.decoded(new Inventory({ warehouse: "warehouse-1" })) + .shipping.decoded(new Shipping({ address: "Main Street" })) ) ) } @@ -2929,17 +2955,17 @@ describe("Machine", () => { events: Machine.events(ReserveInventory), initial: (to) => to.fulfillment.initial.resolve(({ target }) => - target( + target.decoded( fulfillment, (fulfillment) => fulfillment - .inventory( + .inventory.decoded( inventory, - (inventory) => inventory.checking(new CheckingInventory({ sku: "sku-1" })) + (inventory) => inventory.checking.decoded(new CheckingInventory({ sku: "sku-1" })) ) - .shipping( + .shipping.decoded( shipping, - (shipping) => shipping.quoting(quoting) + (shipping) => shipping.quoting.decoded(quoting) ) ) ) @@ -2952,7 +2978,7 @@ describe("Machine", () => { on: { ReserveInventory: (to) => to.local.reserved().resolve(({ event, target }) => - target(new InventoryReserved({ reservationId: event.reservationId })) + target.decoded(new InventoryReserved({ reservationId: event.reservationId })) ) } } @@ -3020,17 +3046,17 @@ describe("Machine", () => { events: Machine.events(ReserveInventory), initial: (to) => to.fulfillment.initial.resolve(({ target }) => - target( + target.decoded( fulfillment, (fulfillment) => fulfillment - .inventory( + .inventory.decoded( new Inventory({ warehouse: "warehouse-1" }), - (inventory) => inventory.checking(new CheckingInventory({ sku: "sku-1" })) + (inventory) => inventory.checking.decoded(new CheckingInventory({ sku: "sku-1" })) ) - .shipping( + .shipping.decoded( shipping, - (shipping) => shipping.quoting(quoting) + (shipping) => shipping.quoting.decoded(quoting) ) ) ) @@ -3043,10 +3069,10 @@ describe("Machine", () => { on: { ReserveInventory: (to) => to.branch.fulfillment.inventory().resolve(({ event, target }) => - target( + target.decoded( nextInventory, (inventory) => - inventory.reserved(new InventoryReserved({ reservationId: event.reservationId })) + inventory.reserved.decoded(new InventoryReserved({ reservationId: event.reservationId })) ) ) } @@ -3116,17 +3142,17 @@ describe("Machine", () => { events: Machine.events(ReserveInventory), initial: (to) => to.fulfillment.initial.resolve(({ target }) => - target( + target.decoded( fulfillment, (fulfillment) => fulfillment - .inventory( + .inventory.decoded( inventory, - (inventory) => inventory.checking(new CheckingInventory({ sku: "sku-1" })) + (inventory) => inventory.checking.decoded(new CheckingInventory({ sku: "sku-1" })) ) - .shipping( + .shipping.decoded( shipping, - (shipping) => shipping.quoting(quoting) + (shipping) => shipping.quoting.decoded(quoting) ) ) ) @@ -3139,10 +3165,10 @@ describe("Machine", () => { on: { ReserveInventory: (to) => to.branch.fulfillment.inventory().resolve(({ event, target }) => - target( + target.decoded( nextInventory, (inventory) => - inventory.reserved(new InventoryReserved({ reservationId: event.reservationId })) + inventory.reserved.decoded(new InventoryReserved({ reservationId: event.reservationId })) ) ) } @@ -3213,17 +3239,17 @@ describe("Machine", () => { events: Machine.events(ReserveInventory), initial: (to) => to.fulfillment.initial.resolve(({ target }) => - target( + target.decoded( fulfillment, (fulfillment) => fulfillment - .inventory( + .inventory.decoded( inventory, - (inventory) => inventory.checking(new CheckingInventory({ sku: "sku-1" })) + (inventory) => inventory.checking.decoded(new CheckingInventory({ sku: "sku-1" })) ) - .shipping( + .shipping.decoded( shipping, - (shipping) => shipping.quoting(quoting) + (shipping) => shipping.quoting.decoded(quoting) ) ) ) @@ -3236,13 +3262,15 @@ describe("Machine", () => { on: { ReserveInventory: (to) => to.branch.fulfillment().resolve(({ event, target }) => - target( + target.decoded( nextFulfillment, (fulfillment) => - fulfillment.inventory( + fulfillment.inventory.decoded( nextInventory, (inventory) => - inventory.reserved(new InventoryReserved({ reservationId: event.reservationId })) + inventory.reserved.decoded( + new InventoryReserved({ reservationId: event.reservationId }) + ) ) ) ) @@ -3311,12 +3339,12 @@ describe("Machine", () => { events: Machine.events(ReserveInventory), initial: (to) => to.payment.initial.resolve(({ target }) => - target( + target.decoded( payment, (payment) => - payment.inventory( + payment.inventory.decoded( inventory, - (inventory) => inventory.checking(new CheckingInventory({ sku: "sku-1" })) + (inventory) => inventory.checking.decoded(new CheckingInventory({ sku: "sku-1" })) ) ) ) @@ -3329,9 +3357,9 @@ describe("Machine", () => { on: { ReserveInventory: (to) => to.branch.payment.shipping().resolve(({ event, target }) => - target( + target.decoded( shipping, - (shipping) => shipping.quoted(new ShippingQuoted({ quoteId: event.reservationId })) + (shipping) => shipping.quoted.decoded(new ShippingQuoted({ quoteId: event.reservationId })) ) ) } @@ -3390,14 +3418,15 @@ describe("Machine", () => { }).handle({ payment: { on: { - Reset: (to) => to.local.entering().resolve(({ target }) => target(new EnteringPayment({ amount: 0 }))) + Reset: (to) => + to.local.entering().resolve(({ target }) => target.decoded(new EnteringPayment({ amount: 0 }))) }, states: { entering: { on: { Authorize: (to) => to.local.authorized().resolve(({ event, target }) => - target(new AuthorizedPayment({ code: event.code })) + target.decoded(new AuthorizedPayment({ code: event.code })) ) } }, @@ -3501,14 +3530,14 @@ describe("Machine", () => { }).handle({ payment: { on: { - Reset: (to) => to.full.idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) + Reset: (to) => to.full.idle().resolve(({ target }) => target.decoded(new Idle({ userId: "user-1" }))) }, states: { entering: { on: { Authorize: (to) => to.local.authorized().resolve(({ event, target }) => - target(new AuthorizedPayment({ code: event.code })) + target.decoded(new AuthorizedPayment({ code: event.code })) ) } }, @@ -3587,20 +3616,20 @@ describe("Machine", () => { }).handle({ checkout: { on: { - Reset: (to) => to.full.failed().resolve(({ target }) => target(new Failed({ message: "reset" }))) + Reset: (to) => to.full.failed().resolve(({ target }) => target.decoded(new Failed({ message: "reset" }))) }, states: { inventory: { onDone: (to) => to.branch.checkout.shipped().resolve(({ output, target }) => - target(new ShippingQuoted({ quoteId: String(output) })) + target.decoded(new ShippingQuoted({ quoteId: String(output) })) ), states: { checking: { on: { ReserveInventory: (to) => to.local.reserved().resolve(({ event, target }) => - target(new InventoryReserved({ reservationId: event.reservationId })) + target.decoded(new InventoryReserved({ reservationId: event.reservationId })) ) } }, @@ -3696,7 +3725,7 @@ describe("Machine", () => { on: { ReserveInventory: (to) => to.local.reserved().resolve(({ event, target }) => - target(new InventoryReserved({ reservationId: event.reservationId })) + target.decoded(new InventoryReserved({ reservationId: event.reservationId })) ) } } @@ -3818,7 +3847,7 @@ describe("Machine", () => { on: { ReserveInventory: (to) => to.local.reserved().resolve(({ event, target }) => - target(new InventoryReserved({ reservationId: event.reservationId })) + target.decoded(new InventoryReserved({ reservationId: event.reservationId })) ) } }, @@ -3833,7 +3862,7 @@ describe("Machine", () => { on: { ReserveInventory: (to) => to.local.quoted().resolve(({ event, target }) => - target(new ShippingQuoted({ quoteId: event.reservationId })) + target.decoded(new ShippingQuoted({ quoteId: event.reservationId })) ) } }, @@ -3960,7 +3989,7 @@ describe("Machine", () => { on: { ReserveInventory: (to) => to.local.reserved().resolve(({ event, target }) => - target(new InventoryReserved({ reservationId: event.reservationId })) + target.decoded(new InventoryReserved({ reservationId: event.reservationId })) ) } }, @@ -3974,7 +4003,9 @@ describe("Machine", () => { quoting: { on: { Resolve: (to) => - to.local.quoted().resolve(({ target }) => target(new ShippingQuoted({ quoteId: "quote-1" }))) + to.local.quoted().resolve(({ target }) => + target.decoded(new ShippingQuoted({ quoteId: "quote-1" })) + ) } }, quoted: { @@ -4086,7 +4117,7 @@ describe("Machine", () => { on: { ReserveInventory: (to) => to.local.reserved().resolve(({ event, target }) => - target(new InventoryReserved({ reservationId: event.reservationId })) + target.decoded(new InventoryReserved({ reservationId: event.reservationId })) ) } } @@ -4098,7 +4129,7 @@ describe("Machine", () => { on: { ReserveInventory: (to) => to.local.quoted().resolve(({ event, target }) => - target(new ShippingQuoted({ quoteId: event.reservationId })) + target.decoded(new ShippingQuoted({ quoteId: event.reservationId })) ) } } @@ -4197,7 +4228,7 @@ describe("Machine", () => { ReserveInventory: (to) => to.local.reserved().resolve(({ event, target }, enqueue) => { enqueue.raise(new Resolve({})) - return target( + return target.decoded( new InventoryReserved({ reservationId: event.reservationId }) @@ -4212,7 +4243,9 @@ describe("Machine", () => { quoting: { on: { Resolve: (to) => - to.local.quoted().resolve(({ target }) => target(new ShippingQuoted({ quoteId: "raised" }))) + to.local.quoted().resolve(({ target }) => + target.decoded(new ShippingQuoted({ quoteId: "raised" })) + ) } } } @@ -4250,7 +4283,7 @@ describe("Machine", () => { const machine = Machine.make({ states: { Idle }, events: Machine.events(Submit), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({ userId: "user-1" }))) }) const actor = yield* Machine.start(machine) @@ -4264,11 +4297,13 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } } }) @@ -4292,16 +4327,18 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit, Reset), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } }, Loading: { on: { - Reset: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) + Reset: (to) => to.full.Idle().resolve(({ target }) => target.decoded(new Idle({ userId: "user-1" }))) } } }) @@ -4321,11 +4358,13 @@ describe("Machine", () => { }, events: Machine.events(Submit), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))) + Submit: (to) => + to.full.Success().resolve(({ target }) => target.decoded(new Success({ requestId: "request-1" }))) } }, Success: {} @@ -4343,11 +4382,13 @@ describe("Machine", () => { states: { Idle, Success: SuccessOutput }, events: Machine.events(Submit), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))) + Submit: (to) => + to.full.Success().resolve(({ target }) => target.decoded(new Success({ requestId: "request-1" }))) } }, Success: { @@ -4373,11 +4414,13 @@ describe("Machine", () => { states: { Idle, Success: SuccessOutput }, events: Machine.events(Submit), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))) + Submit: (to) => + to.full.Success().resolve(({ target }) => target.decoded(new Success({ requestId: "request-1" }))) } }, Success: { @@ -4400,7 +4443,7 @@ describe("Machine", () => { const machine = Machine.make({ states: { Success: SuccessOutput }, events: Machine.events(Submit), - initial: (to) => to.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))) + initial: (to) => to.Success().resolve(({ target }) => target.decoded(new Success({ requestId: "request-1" }))) }).handle({ Success: { output: ({ state }) => { @@ -4434,7 +4477,7 @@ describe("Machine", () => { const machine = Machine.make({ states: { Success: SuccessOutput }, events: Machine.events(Submit), - initial: (to) => to.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))) + initial: (to) => to.Success().resolve(({ target }) => target.decoded(new Success({ requestId: "request-1" }))) }).handle({ Success: { output: ({ state }) => { @@ -4464,11 +4507,13 @@ describe("Machine", () => { }, events: Machine.events(Submit), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))) + Submit: (to) => + to.full.Success().resolve(({ target }) => target.decoded(new Success({ requestId: "request-1" }))) } }, Success: {} @@ -4498,12 +4543,14 @@ describe("Machine", () => { }, events: Machine.events(Submit, Reset), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - 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" }))) + Submit: (to) => + to.full.Success().resolve(({ target }) => target.decoded(new Success({ requestId: "request-1" }))), + Reset: (to) => to.full.Idle().resolve(({ target }) => target.decoded(new Idle({ userId: "user-2" }))) } }, Success: {} @@ -4530,11 +4577,12 @@ describe("Machine", () => { const machine = Machine.make({ states: { Idle, Loading }, events: Machine.events(Submit), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({ userId: "user-1" }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } } }) @@ -4558,11 +4606,13 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } } }) @@ -4589,7 +4639,8 @@ describe("Machine", () => { }, events: Machine.events(Submit), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Success: {} }) @@ -4608,7 +4659,8 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { @@ -4631,11 +4683,13 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } } }) @@ -4675,11 +4729,13 @@ describe("Machine", () => { states: { Idle, Success: SuccessOutput }, events: Machine.events(Submit), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))) + Submit: (to) => + to.full.Success().resolve(({ target }) => target.decoded(new Success({ requestId: "request-1" }))) } }, Success: { @@ -4710,11 +4766,13 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit, Reset), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } } }) @@ -4734,18 +4792,20 @@ describe("Machine", () => { states: { Idle, Loading, Success: SuccessOutput }, events: Machine.events(Submit, RequestSucceeded), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }) const machine = definition.handle({ Idle: { on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } }, Loading: { invoke: (from) => from.effect("request", () => Effect.succeed("done:request-1")).onDone((to) => - to.full.Success().resolve(({ output, target }) => target(new Success({ requestId: output }))) + to.full.Success().resolve(({ output, target }) => target.decoded(new Success({ requestId: output }))) ) }, Success: { @@ -4775,18 +4835,20 @@ describe("Machine", () => { states: { Idle, Loading, Success: SuccessOutput }, events: Machine.events(Submit, RequestSucceeded), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }) const machine = definition.handle({ Idle: { on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } }, Loading: { invoke: (from) => from.effect("request", () => Effect.succeed("done:request-1")).onDone((to) => - to.full.Success().resolve(({ output, target }) => target(new Success({ requestId: output }))) + to.full.Success().resolve(({ output, target }) => target.decoded(new Success({ requestId: output }))) ) }, Success: { @@ -4816,14 +4878,14 @@ describe("Machine", () => { const childMachine = Machine.make({ states: childStates.states, events: Machine.events(), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "child" }))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({ userId: "child" }))) }) const Child = Machine.child("shared-child", childMachine) const parentStates = Machine.states({ Loading }) const parentMachine = Machine.make({ states: parentStates.states, events: Machine.events(), - initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "parent" }))) + initial: (to) => to.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "parent" }))) }).handle({ Loading: { invoke: (from) => from.child(Child) @@ -4867,14 +4929,14 @@ describe("Machine", () => { const childMachine = Machine.make({ states: childStates.states, events: Machine.events(), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "child" }))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({ userId: "child" }))) }).handle({ Idle: {} }) const Child = Machine.child("owned-child", childMachine) const parentStates = Machine.states({ Loading }) const parentMachine = Machine.make({ states: parentStates.states, events: Machine.events(), - initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "parent" }))) + initial: (to) => to.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "parent" }))) }).handle({ Loading: { invoke: (from) => from.child(Child) } }) @@ -4904,7 +4966,7 @@ describe("Machine", () => { initial: (to) => to.Idle().resolve(({ input, target }) => { starts += 1 - return target(new Idle({ userId: input.userId })) + return target.decoded(new Idle({ userId: input.userId })) }) }).handle({ Idle: {} }) const Child = Machine.child("input-child", childMachine) @@ -4912,7 +4974,7 @@ describe("Machine", () => { const parentMachine = Machine.make({ states: parentStates.states, events: Machine.events(), - initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "parent" }))) + initial: (to) => to.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "parent" }))) }).handle({ Loading: { invoke: (from) => from.child(Child, { input: { userId: "configured" } }) @@ -4947,7 +5009,8 @@ describe("Machine", () => { const childMachine = Machine.make({ states: childStates.states, events: Machine.events(), - initial: (to) => to.Success().resolve(({ target }) => target(new Success({ requestId: "child-output" }))) + initial: (to) => + to.Success().resolve(({ target }) => target.decoded(new Success({ requestId: "child-output" }))) }).handle({ Success: { output: ({ state }) => state.requestId } }) @@ -4959,12 +5022,12 @@ describe("Machine", () => { const parentMachine = Machine.make({ states: parentStates.states, events: Machine.events(ChildFinished), - initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "parent" }))) + initial: (to) => to.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "parent" }))) }).handle({ Loading: { invoke: (from) => from.child(Child).onDone((to) => - to.full.Success().resolve(({ output, target }) => target(new Success({ requestId: output }))) + to.full.Success().resolve(({ output, target }) => target.decoded(new Success({ requestId: output }))) ) }, Success: { output: ({ state }) => state.requestId } @@ -4982,7 +5045,8 @@ describe("Machine", () => { states: states.states, events: Machine.events(), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: {} }) @@ -5011,14 +5075,14 @@ describe("Machine", () => { const child = Machine.make({ states: childStates.states, events: Machine.events(), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "child" }))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({ userId: "child" }))) }) const Child = Machine.child("child-machine", child) const parentStates = Machine.states({ Loading }) const parent = Machine.make({ states: parentStates.states, events: Machine.events(), - initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + initial: (to) => to.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) }).handle({ Loading: { invoke: (from) => [from.child(Child), from.child(Child)] @@ -5044,7 +5108,7 @@ describe("Machine", () => { const parent = Machine.make({ states: parentStates.states, events: Machine.events(), - initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + initial: (to) => to.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) }).handle({ Loading: { invoke: ( @@ -5071,17 +5135,19 @@ describe("Machine", () => { states: { Idle, Loading, Failed: FailedOutput }, events: Machine.events(Submit, RequestFailed), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } }, Loading: { invoke: (from) => from.effect("request", () => Effect.fail(error)).onFailure((to) => - to.full.Failed().resolve(({ error, target }) => target(new Failed({ message: error.message }))) + to.full.Failed().resolve(({ error, target }) => target.decoded(new Failed({ message: error.message }))) ) }, Failed: { @@ -5111,17 +5177,18 @@ describe("Machine", () => { states: { Idle, Loading, Success: SuccessOutput }, events: Machine.events(Submit), internalEvents: Machine.internalEvents(RequestSucceeded), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({ userId: "user-1" }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } }, Loading: { invoke: (from) => from.effect("request", () => Effect.succeed("loaded")).onDone((to) => - to.full.Success().resolve(({ output, target }) => target(new Success({ requestId: output }))) + to.full.Success().resolve(({ output, target }) => target.decoded(new Success({ requestId: output }))) ) }, Success: { @@ -5141,7 +5208,7 @@ describe("Machine", () => { const machine = Machine.make({ states: { Loading, Success: SuccessOutput }, events: Machine.events(RequestSucceeded), - initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + initial: (to) => to.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) }).handle({ Loading: { invoke: (from) => @@ -5160,7 +5227,7 @@ describe("Machine", () => { }).onFailure((to) => to.none), on: { RequestSucceeded: (to) => - to.full.Success().resolve(({ event, target }) => target(new Success({ requestId: event.value }))) + to.full.Success().resolve(({ event, target }) => target.decoded(new Success({ requestId: event.value }))) } }, Success: { @@ -5180,12 +5247,12 @@ describe("Machine", () => { const machine = Machine.make({ states: { Idle, Loading, Success: SuccessOutput }, events: Machine.events(Resolve, RequestSucceeded), - initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + initial: (to) => to.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) }).handle({ Idle: { on: { RequestSucceeded: (to) => - to.full.Success().resolve(({ event, target }) => target(new Success({ requestId: event.value }))) + to.full.Success().resolve(({ event, target }) => target.decoded(new Success({ requestId: event.value }))) } }, Loading: { @@ -5204,7 +5271,7 @@ describe("Machine", () => { }) }).onFailure((to) => to.none), on: { - Resolve: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({ userId: "resolved" }))) + Resolve: (to) => to.full.Idle().resolve(({ target }) => target.decoded(new Idle({ userId: "resolved" }))) } }, Success: { @@ -5235,12 +5302,12 @@ describe("Machine", () => { states: { Loading, Failed: FailedOutput }, events: Machine.events(), internalEvents: Machine.internalEvents(RequestSucceeded, RequestFailed), - initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + initial: (to) => to.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) }).handle({ Loading: { invoke: (from) => from.effect("request", () => Effect.fail(failure)).onFailure((to) => - to.full.Failed().resolve(({ error, target }) => target(new Failed({ message: error.message }))) + to.full.Failed().resolve(({ error, target }) => target.decoded(new Failed({ message: error.message }))) ) }, Failed: { @@ -5262,12 +5329,12 @@ describe("Machine", () => { states: { Loading, Success: SuccessOutput }, events: Machine.events(), internalEvents: Machine.internalEvents(RequestSucceeded), - initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + initial: (to) => to.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) }).handle({ Loading: { invoke: (from) => from.effect("request", () => requiredMessage).onDone((to) => - to.full.Success().resolve(({ output, target }) => target(new Success({ requestId: output }))) + to.full.Success().resolve(({ output, target }) => target.decoded(new Success({ requestId: output }))) ) }, Success: { @@ -5291,12 +5358,12 @@ describe("Machine", () => { states: { Loading, Success: SuccessOutput }, events: Machine.events(), internalEvents: Machine.internalEvents(RequestSucceeded), - initial: (to) => to.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + initial: (to) => to.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) }).handle({ Loading: { invoke: (from) => from.timer("timeout", "1 hour").onDone((to) => - to.full.Success().resolve(({ target }) => target(new Success({ requestId: "timeout" }))) + to.full.Success().resolve(({ target }) => target.decoded(new Success({ requestId: "timeout" }))) ) }, Success: { @@ -5317,7 +5384,8 @@ describe("Machine", () => { states: { Idle, Loading, Success: SuccessOutput }, events: Machine.events(Submit, RequestProgress), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }) const onSnapshot: Machine.Machine.InvokeTransition< Machine.Machine.States, @@ -5337,12 +5405,13 @@ describe("Machine", () => { > > = (to) => to.full.Success().resolve(({ id, snapshot, target }) => - target(new Success({ requestId: `${id}:${snapshot.state}` })) + target.decoded(new Success({ requestId: `${id}:${snapshot.state}` })) ) const machine = definition.handle({ Idle: { on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } }, Loading: { @@ -5380,11 +5449,13 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } }, Loading: { @@ -5415,7 +5486,8 @@ describe("Machine", () => { states: { Idle, Loading, Success: SuccessOutput }, events: Machine.events(Submit, RequestProgress), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }) const onSnapshot: Machine.Machine.InvokeTransition< Machine.Machine.States, @@ -5439,13 +5511,14 @@ describe("Machine", () => { unchanged: { target: to.none } }).resolve(({ snapshot, select }) => snapshot.state === "ready" - ? select.ready(new Success({ requestId: snapshot.state })) + ? select.ready.decoded(new Success({ requestId: snapshot.state })) : select.unchanged() ) const machine = definition.handle({ Idle: { on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } }, Loading: { @@ -5499,11 +5572,13 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } }, Loading: { @@ -5553,20 +5628,23 @@ describe("Machine", () => { states: { Idle, Loading, Success: SuccessOutput }, events: Machine.events(Submit, Resolve, RequestSucceeded), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } }, Loading: { invoke: (from) => from.logic("request", { address: Machine.childAddress("stopping-request"), logic: childLogic }), on: { - Resolve: (to) => to.full.Success().resolve(({ target }) => target(new Success({ requestId: "request-1" }))), + Resolve: (to) => + to.full.Success().resolve(({ target }) => target.decoded(new Success({ requestId: "request-1" }))), RequestSucceeded: (to) => - to.full.Success().resolve(({ event, target }) => target(new Success({ requestId: event.value }))) + to.full.Success().resolve(({ event, target }) => target.decoded(new Success({ requestId: event.value }))) } }, Success: { @@ -5664,7 +5742,7 @@ describe("Machine", () => { on: { Authorize: (to) => to.local.authorized().resolve(({ event, target }) => - target(new AuthorizedPayment({ code: event.code })) + target.decoded(new AuthorizedPayment({ code: event.code })) ) } }, @@ -5797,7 +5875,7 @@ describe("Machine", () => { checking: { on: { ReserveInventory: (to) => - to.full.success().resolve(({ target }) => target(new Success({ requestId: "done" }))) + to.full.success().resolve(({ target }) => target.decoded(new Success({ requestId: "done" }))) } } } @@ -5903,16 +5981,19 @@ describe("Machine", () => { states: { Idle, Loading }, events: Machine.events(Submit), input: Input, - initial: (to) => to.Idle().resolve(({ input: input, target }) => target(new Idle({ userId: input.userId }))) + initial: (to) => + to.Idle().resolve(({ input: input, target }) => target.decoded(new Idle({ userId: input.userId }))) }).handle({ Idle: { - always: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))), + always: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))), on: { - Submit: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + Submit: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) } }, Loading: { - always: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) + always: (to) => to.full.Idle().resolve(({ target }) => target.decoded(new Idle({ userId: "user-1" }))) } }) @@ -5932,13 +6013,14 @@ describe("Machine", () => { id: "InitialLoopMachine", states: { Idle, Loading }, events: Machine.events(), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({ userId: "user-1" }))) }).handle({ Idle: { - always: (to) => to.full.Loading().resolve(({ target }) => target(new Loading({ requestId: "request-1" }))) + always: (to) => + to.full.Loading().resolve(({ target }) => target.decoded(new Loading({ requestId: "request-1" }))) }, Loading: { - always: (to) => to.full.Idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) + always: (to) => to.full.Idle().resolve(({ target }) => target.decoded(new Idle({ userId: "user-1" }))) } }) @@ -5972,15 +6054,15 @@ describe("Machine", () => { id: "CompletionLoopMachine", states: states.states, events: Machine.events(Submit), - initial: (to) => to.idle().resolve(({ target }) => target(new Idle({ userId: "user-1" }))) + initial: (to) => to.idle().resolve(({ target }) => target.decoded(new Idle({ userId: "user-1" }))) }).handle({ idle: { on: { Submit: (to) => to.full.flow().resolve(({ target }) => - target( + target.decoded( new Loading({ requestId: "request-1" }), - (flow) => flow.done(new Success({ requestId: "request-1" })) + (flow) => flow.done.decoded(new Success({ requestId: "request-1" })) ) ) } @@ -5988,9 +6070,9 @@ describe("Machine", () => { flow: { onDone: (to) => to.full.flow().resolve(({ state, target }) => - target( + target.decoded( state, - (flow) => flow.done(new Success({ requestId: state.requestId })) + (flow) => flow.done.decoded(new Success({ requestId: state.requestId })) ) ) } @@ -6042,9 +6124,10 @@ describe("Machine", () => { events: Machine.events(AdvanceCounters), initial: (to) => to.running.initial.resolve(({ target }) => - target( + target.decoded( new CounterRunning({}), - (running) => running.left(new LeftCounter({ value: 0 })).right(new RightCounter({ value: 0 })) + (running) => + running.left.decoded(new LeftCounter({ value: 0 })).right.decoded(new RightCounter({ value: 0 })) ) ) }).handle({ @@ -6054,7 +6137,7 @@ describe("Machine", () => { on: { AdvanceCounters: (to) => to.branch.running.left().resolve(({ state, target }) => - target(new LeftCounter({ value: state.value + 1 })) + target.decoded(new LeftCounter({ value: state.value + 1 })) ) } }, @@ -6062,7 +6145,7 @@ describe("Machine", () => { on: { AdvanceCounters: (to) => to.branch.running.right().resolve(({ state, target }) => - target(new RightCounter({ value: state.value + 1 })) + target.decoded(new RightCounter({ value: state.value + 1 })) ) } } @@ -6075,7 +6158,7 @@ describe("Machine", () => { return Machine.make({ states: states.states, events: Machine.events(ConcurrentPing), - initial: (to) => to.ConcurrentIdle().resolve(({ target }) => target(new ConcurrentIdle({}))) + initial: (to) => to.ConcurrentIdle().resolve(({ target }) => target.decoded(new ConcurrentIdle({}))) }).handle({ ConcurrentIdle: { on: { diff --git a/test/machine/MachineReferences.test.ts b/test/machine/MachineReferences.test.ts index aa2faed..3d36ea1 100644 --- a/test/machine/MachineReferences.test.ts +++ b/test/machine/MachineReferences.test.ts @@ -34,7 +34,7 @@ describe("machine reference event channels", () => { initial: (to) => to.Idle().resolve(({ target }) => { initializations += 1 - return target(new Idle({})) + return target.decoded(new Idle({})) }) }).handle({ Idle: { @@ -83,7 +83,7 @@ describe("machine reference event channels", () => { states: states.states, events: Machine.events(), emittedEvents: Emissions, - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({}))) }).handle({ Idle: { entry: (_, enqueue) => { @@ -122,7 +122,7 @@ describe("machine reference event channels", () => { states: states.states, events: Events, emittedEvents: Emissions, - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({}))) }).handle({ Idle: { on: { @@ -173,7 +173,7 @@ describe("machine reference event channels", () => { states: states.states, events: Events, emittedEvents: Emissions, - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({}))) }).handle({ Idle: { on: { @@ -227,7 +227,7 @@ describe("machine reference event channels", () => { events: ChildEvents, parent: Machine.optionalParent(ParentEvents), emittedEvents: ChildEmissions, - initial: (to) => to.Waiting().resolve(({ target }) => target(new Waiting({}))) + initial: (to) => to.Waiting().resolve(({ target }) => target.decoded(new Waiting({}))) }).handle({ Waiting: { on: { @@ -238,7 +238,7 @@ describe("machine reference event channels", () => { if (parent !== undefined) { enqueue.sendTo(parent, ParentEvents.ChildReported({ value: 1 })) } - return target(new Reported({})) + return target.decoded(new Reported({})) }) } }, @@ -266,14 +266,15 @@ describe("machine reference event channels", () => { const parentMachine = Machine.make({ states: parentStates.states, events: Machine.events(ParentEvents, Notice), - initial: (to) => to.Awaiting().resolve(({ target }) => target(new Awaiting({}))) + initial: (to) => to.Awaiting().resolve(({ target }) => target.decoded(new Awaiting({}))) }).handle({ Awaiting: { invoke: (from) => from.child(Child), on: { 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" }))) + to.full.Finished().resolve(({ target }) => target.decoded(new Finished({ source: "parent event" }))), + Notice: (to) => + to.full.Finished().resolve(({ target }) => target.decoded(new Finished({ source: "emission" }))) } }, Finished: { output: ({ state }) => state.source } @@ -305,7 +306,7 @@ describe("machine reference event channels", () => { states: childStates.states, events: Machine.events(), parent: Machine.parent(ParentEvents), - initial: (to) => to.ChildIdle().resolve(({ target }) => target(new ChildIdle({}))) + initial: (to) => to.ChildIdle().resolve(({ target }) => target.decoded(new ChildIdle({}))) }) const childMachine = childDefinition.handle({ ChildIdle: { @@ -323,12 +324,12 @@ describe("machine reference event channels", () => { const parentMachine = Machine.make({ states: parentStates.states, events: ParentEvents, - initial: (to) => to.ParentWaiting().resolve(({ target }) => target(new ParentWaiting({}))) + initial: (to) => to.ParentWaiting().resolve(({ target }) => target.decoded(new ParentWaiting({}))) }).handle({ ParentWaiting: { invoke: (from) => from.child(Child).onFailure((to) => to.none), on: { - ChildReady: (to) => to.full.ParentDone().resolve(({ target }) => target(new ParentDone({}))) + ChildReady: (to) => to.full.ParentDone().resolve(({ target }) => target.decoded(new ParentDone({}))) } }, ParentDone: { output: () => undefined } diff --git a/test/machine/MermaidVisualization.test.ts b/test/machine/MermaidVisualization.test.ts index 2526215..aff97d0 100644 --- a/test/machine/MermaidVisualization.test.ts +++ b/test/machine/MermaidVisualization.test.ts @@ -54,14 +54,16 @@ const inspection: InspectionApi = { key: "approved", title: "approved %%\nnow", target: "Root.Done", - selection: { kind: "state", scope: "local", path: "Root.Done" } + selection: { kind: "state", scope: "local", path: "Root.Done" }, + updates: [] }, { type: "branch", key: "unchanged", title: "unchanged", target: undefined, - selection: { kind: "none", scope: "local", path: undefined } + selection: { kind: "none", scope: "local", path: undefined }, + updates: [] } ] }, @@ -72,8 +74,9 @@ const inspection: InspectionApi = { acceptance: "declinable", branches: [{ type: "direct", - target: "Root.Route", - selection: { kind: "choice", scope: "local", path: "Root.Route" } + target: "Root.Done", + selection: { kind: "state", scope: "local", path: "Root.Done" }, + updates: ["Root"] }] } ], @@ -106,7 +109,7 @@ describe("Mermaid visualization", () => { assert.include(rendered, "state \"● Choose route (Route)\" as state_1") assert.include(rendered, "state state_1 <>") assert.include(rendered, "state_1 --> state_2: choice [approved #37;#37; now]") - assert.include(rendered, "state_2 --> state_1: Retry [declinable]") + assert.include(rendered, "state_2 --> state_2: Retry [declinable] / update Root") assert.notMatch(rendered, /state_1 --> .*otherwise/) assert.include(rendered, "state_1: process / worker #37;#37; end note") assert.notInclude(rendered, "Candidate events") diff --git a/test/machine/PublicPrototype.test.ts b/test/machine/PublicPrototype.test.ts index b0e36aa..de575b9 100644 --- a/test/machine/PublicPrototype.test.ts +++ b/test/machine/PublicPrototype.test.ts @@ -10,7 +10,7 @@ it("uses the public pipeable and inspectable prototypes", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Start), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle())) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle())) }) assert.strictEqual(machine.pipe((value) => value), machine) diff --git a/test/machine/Resume.test.ts b/test/machine/Resume.test.ts index 8d692ba..eedb065 100644 --- a/test/machine/Resume.test.ts +++ b/test/machine/Resume.test.ts @@ -70,7 +70,7 @@ describe("Machine.resume", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: (to) => to.Inactive().resolve(({ target }) => target(new Inactive({}))) + initial: (to) => to.Inactive().resolve(({ target }) => target.decoded(new Inactive({}))) }).handle({ Root: { invoke: (from) => from.logic("root", { address: Machine.childAddress("root"), logic: restoredLogic("root") }), @@ -180,7 +180,7 @@ describe("Machine.resume", () => { states: { A: { on: { - Advance: (to) => to.local.B().resolve(({ target }) => target(new LeftB({}))) + Advance: (to) => to.local.B().resolve(({ target }) => target.decoded(new LeftB({}))) } } } @@ -189,7 +189,7 @@ describe("Machine.resume", () => { states: { A: { on: { - Advance: (to) => to.local.B().resolve(({ target }) => target(new RightB({}))) + Advance: (to) => to.local.B().resolve(({ target }) => target.decoded(new RightB({}))) } } } @@ -234,11 +234,11 @@ describe("Machine.resume", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Finish), - initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) + initial: (to) => to.Count().resolve(({ target }) => target.decoded(new Count({ value: 0 }))) }).handle({ Count: { on: { - Finish: (to) => to.full.Done().resolve(({ target }) => target(new Done({ value: 9 }))) + Finish: (to) => to.full.Done().resolve(({ target }) => target.decoded(new Done({ value: 9 }))) } }, Done: { output: ({ state }) => state.value } @@ -281,7 +281,7 @@ describe("Machine.resume", () => { }) .handle({ Flow: { - onDone: (to) => to.full.Next().resolve(({ target }) => target(new Next({}))) + onDone: (to) => to.full.Next().resolve(({ target }) => target.decoded(new Next({}))) }, Next: {} }) @@ -303,10 +303,10 @@ describe("Machine.resume", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Ping), - initial: (to) => to.A().resolve(({ target }) => target(new A({}))) + initial: (to) => to.A().resolve(({ target }) => target.decoded(new A({}))) }).handle({ A: { - always: (to) => to.full.B().resolve(({ target }) => target(new B({}))) + always: (to) => to.full.B().resolve(({ target }) => target.decoded(new B({}))) }, B: {} }) @@ -329,15 +329,15 @@ describe("Machine.resume", () => { states: states.states, events: Machine.events(Cancel), internalEvents: Machine.internalEvents(Timeout), - initial: (to) => to.Cancelled().resolve(({ target }) => target(new Cancelled({}))) + initial: (to) => to.Cancelled().resolve(({ target }) => target.decoded(new Cancelled({}))) }).handle({ Waiting: { invoke: (from) => from.timer("timeout", "1 second").onDone((to) => - to.full.TimedOut().resolve(({ target }) => target(new TimedOut({}))) + to.full.TimedOut().resolve(({ target }) => target.decoded(new TimedOut({}))) ), on: { - Cancel: (to) => to.full.Cancelled().resolve(({ target }) => target(new Cancelled({}))) + Cancel: (to) => to.full.Cancelled().resolve(({ target }) => target.decoded(new Cancelled({}))) } }, Cancelled: {}, @@ -371,12 +371,12 @@ describe("Machine.resume", () => { states: states.states, events: Machine.events(), internalEvents: Machine.internalEvents(LoadedEvent), - initial: (to) => to.Loaded().resolve(({ target }) => target(new Loaded({ value: "initial" }))) + initial: (to) => to.Loaded().resolve(({ target }) => target.decoded(new Loaded({ value: "initial" }))) }).handle({ Loading: { invoke: (from) => from.effect("load", () => Ref.updateAndGet(runs, (n) => n + 1).pipe(Effect.as("fresh"))).onDone((to) => - to.full.Loaded().resolve(({ output, target }) => target(new Loaded({ value: output }))) + to.full.Loaded().resolve(({ output, target }) => target.decoded(new Loaded({ value: output }))) ) }, Loaded: {} @@ -405,7 +405,7 @@ describe("Machine.resume", () => { states: states.states, events: Machine.events(), internalEvents: Machine.internalEvents(FailedEvent), - initial: (to) => to.Failed().resolve(({ target }) => target(new Failed({ message: "initial" }))) + initial: (to) => to.Failed().resolve(({ target }) => target.decoded(new Failed({ message: "initial" }))) }).handle({ Loading: { invoke: (from) => @@ -413,7 +413,7 @@ describe("Machine.resume", () => { Ref.update(runs, (n) => n + 1).pipe( Effect.andThen(Effect.fail(new LoadFailure({ message: "offline" }))) )).onFailure((to) => - to.full.Failed().resolve(({ error, target }) => target(new Failed({ message: error.message }))) + to.full.Failed().resolve(({ error, target }) => target.decoded(new Failed({ message: error.message }))) ) }, Failed: {} @@ -442,12 +442,14 @@ describe("Machine.resume", () => { const child = Machine.make({ states: childStates.states, events: Machine.events(ChildFinish), - initial: (to) => to.ChildIdle().resolve(({ target }) => target(new ChildIdle({ value: 1 }))) + initial: (to) => to.ChildIdle().resolve(({ target }) => target.decoded(new ChildIdle({ value: 1 }))) }).handle({ ChildIdle: { on: { ChildFinish: (to) => - to.full.ChildDone().resolve(({ state, target }) => target(new ChildDone({ value: state.value + 1 }))) + to.full.ChildDone().resolve(({ state, target }) => + target.decoded(new ChildDone({ value: state.value + 1 })) + ) } }, ChildDone: { output: ({ state }) => state.value } @@ -457,12 +459,12 @@ describe("Machine.resume", () => { const machine = Machine.make({ states: states.states, events: Machine.events(ChildOutput), - initial: (to) => to.ChildOutput().resolve(({ target }) => target(new ChildOutput({ value: 0 }))) + initial: (to) => to.ChildOutput().resolve(({ target }) => target.decoded(new ChildOutput({ value: 0 }))) }).handle({ Parent: { invoke: (from) => from.child(Child).onDone((to) => - to.full.ChildOutput().resolve(({ output, target }) => target(new ChildOutput({ value: output }))) + to.full.ChildOutput().resolve(({ output, target }) => target.decoded(new ChildOutput({ value: output }))) ) }, ChildOutput: {} @@ -556,13 +558,13 @@ describe("Machine.resume", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Add), - initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) + initial: (to) => to.Count().resolve(({ target }) => target.decoded(new Count({ value: 0 }))) }).handle({ Count: { on: { Add: (to) => to.full.Count().resolve(({ event, state, target }) => - target(new Count({ value: state.value + event.value })) + target.decoded(new Count({ value: state.value + event.value })) ) } } diff --git a/test/machine/RuntimeDifferential.test.ts b/test/machine/RuntimeDifferential.test.ts index 83086f7..5cd8c46 100644 --- a/test/machine/RuntimeDifferential.test.ts +++ b/test/machine/RuntimeDifferential.test.ts @@ -54,7 +54,7 @@ describe("pure planning and managed runtime differential", () => { states: states.states, events: Machine.events(Cascade, Ignore, Finish), internalEvents: Machine.internalEvents(Increment), - initial: (to) => to.Count().resolve(({ target }) => target(new Count({ value: 0 }))) + initial: (to) => to.Count().resolve(({ target }) => target.decoded(new Count({ value: 0 }))) }).handle({ Count: { on: { @@ -64,8 +64,9 @@ describe("pure planning and managed runtime differential", () => { return undefined }), 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 }))) + to.full.Count().resolve(({ state, target }) => target.decoded(new Count({ value: state.value + 1 }))), + Finish: (to) => + to.full.Done().resolve(({ state, target }) => target.decoded(new Done({ value: state.value }))) } }, Done: { output: ({ state }) => state.value } @@ -144,9 +145,9 @@ describe("pure planning and managed runtime differential", () => { internalEvents: Machine.internalEvents(Bump), initial: (to) => to.Running.initial.resolve(({ target }) => - target( + target.decoded( new Running({}), - (running) => running.Left(new Left({ value: 0 })).Right(new Right({ value: 0 })) + (running) => running.Left.decoded(new Left({ value: 0 })).Right.decoded(new Right({ value: 0 })) ) ) }).handle({ @@ -155,7 +156,7 @@ describe("pure planning and managed runtime differential", () => { Finish: (to) => to.full.Done().resolve(({ snapshot, target }) => { if (snapshot.path !== "Running") throw new Error("expected Running snapshot") - return target( + return target.decoded( new Done({ value: snapshot.states.Left.value.value + snapshot.states.Right.value.value }) @@ -168,7 +169,7 @@ describe("pure planning and managed runtime differential", () => { Advance: (to) => to.branch.Running.Left().resolve(({ state, target }, enqueue) => { enqueue.raise(new Bump({})) - return target(new Left({ value: state.value + 1 })) + return target.decoded(new Left({ value: state.value + 1 })) }) } }, @@ -176,11 +177,11 @@ describe("pure planning and managed runtime differential", () => { on: { Advance: (to) => to.branch.Running.Right().resolve(({ state, target }) => - target(new Right({ value: state.value + 10 })) + target.decoded(new Right({ value: state.value + 10 })) ), Bump: (to) => to.branch.Running.Right().resolve(({ state, target }) => - target(new Right({ value: state.value + 100 })) + target.decoded(new Right({ value: state.value + 100 })) ), Inspect: (to) => to.none.resolve((context) => { @@ -448,7 +449,7 @@ describe("pure planning and managed runtime differential", () => { events: Machine.events(Begin), internalEvents: Machine.internalEvents(RaisedOne, RaisedTwo), emittedEvents: Machine.emittedEvents(Notice), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({}))) }).handle({ Idle: { entry: (_, enqueue) => { @@ -461,7 +462,7 @@ describe("pure planning and managed runtime differential", () => { record("transition:begin") enqueue.emit(new Notice({ label: "transition" })) enqueue.raise(new RaisedOne({})) - return target(new Working({})) + return target.decoded(new Working({})) }) } }, @@ -482,7 +483,7 @@ describe("pure planning and managed runtime differential", () => { to.full.Finished().resolve(({ target }, enqueue) => { record("raised:two") enqueue.emit(new Notice({ label: "raised-two" })) - return target(new Finished({})) + return target.decoded(new Finished({})) }) } }, @@ -561,11 +562,11 @@ describe("pure planning and managed runtime differential", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Ignore, Go), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({}))) }).handle({ Idle: { on: { - Go: (to) => to.full.Active().resolve(({ target }) => target(new Active({}))) + Go: (to) => to.full.Active().resolve(({ target }) => target.decoded(new Active({}))) } }, Active: {} diff --git a/test/machine/Scheduling.test.ts b/test/machine/Scheduling.test.ts index 41c76ce..0d9fdae 100644 --- a/test/machine/Scheduling.test.ts +++ b/test/machine/Scheduling.test.ts @@ -21,20 +21,21 @@ describe("machine scheduling", () => { states: states.states, events: Machine.events(StartBurst), internalEvents: Machine.internalEvents(Burst), - initial: (to) => to.SchedulingActive().resolve(({ target }) => target(new SchedulingActive({ count: 0 }))) + initial: (to) => + to.SchedulingActive().resolve(({ target }) => target.decoded(new SchedulingActive({ count: 0 }))) }).handle({ SchedulingActive: { on: { StartBurst: (to) => to.full.SchedulingActive().resolve(({ state, target }, enqueue) => { enqueue.raise(new Burst({})) - return target(state) + return target.decoded(state) }), 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 })) + return target.decoded(new SchedulingActive({ count })) }) } } diff --git a/test/machine/SnapshotCodecAdversarial.test.ts b/test/machine/SnapshotCodecAdversarial.test.ts index eaf834a..77652b0 100644 --- a/test/machine/SnapshotCodecAdversarial.test.ts +++ b/test/machine/SnapshotCodecAdversarial.test.ts @@ -135,7 +135,7 @@ const historyMachine = Machine.make({ id: "codec-history", states: HistoryStates.states, events: Machine.events(), - initial: (to) => to.Outside().resolve(({ target }) => target(new Outside({}))) + initial: (to) => to.Outside().resolve(({ target }) => target.decoded(new Outside({}))) }) const historySnapshot = () => @@ -175,7 +175,9 @@ const richMachine = Machine.make({ events: Machine.events(), initial: (to) => to.RichState().resolve(({ target }) => - target(new RichState({ createdAt: new Date("2026-08-19T12:00:00.000Z"), sequence: 42n, missing: undefined })) + target.decoded( + new RichState({ createdAt: new Date("2026-08-19T12:00:00.000Z"), sequence: 42n, missing: undefined }) + ) ) }) @@ -194,7 +196,7 @@ const opaqueMachine = Machine.make({ id: "codec-opaque", states: OpaqueStates.states, events: Machine.events(), - initial: (to) => to.OpaqueState().resolve(({ target }) => target({ _tag: "CodecOpaqueState", resource: {} })) + initial: (to) => to.OpaqueState().resolve(({ target }) => target.decoded({ _tag: "CodecOpaqueState", resource: {} })) }) class OutputDone extends Schema.TaggedClass("CodecOutputDone")("CodecOutputDone", {}) {} @@ -205,7 +207,7 @@ const outputMachine = Machine.make({ id: "codec-output", states: OutputStates.states, events: Machine.events(), - initial: (to) => to.OutputDone().resolve(({ target }) => target(new OutputDone({}))) + initial: (to) => to.OutputDone().resolve(({ target }) => target.decoded(new OutputDone({}))) }) const expectEncodeFailure = Effect.fnUntraced(function*(snapshot: unknown, boundary?: string) { @@ -374,10 +376,10 @@ describe("snapshot codec adversarial boundaries", () => { id: "codec-automatic-original", states: states.states, events: Machine.events(), - initial: (to) => to.Before().resolve(({ target }) => target(new Before({}))) + initial: (to) => to.Before().resolve(({ target }) => target.decoded(new Before({}))) }).handle({ Before: { - always: (to) => to.full.Boundary().resolve(({ target }) => target(new Boundary({}))) + always: (to) => to.full.Boundary().resolve(({ target }) => target.decoded(new Boundary({}))) }, Boundary: {}, After: {} @@ -386,11 +388,11 @@ describe("snapshot codec adversarial boundaries", () => { id: "codec-automatic-changed", states: states.states, events: Machine.events(), - initial: (to) => to.Before().resolve(({ target }) => target(new Before({}))) + initial: (to) => to.Before().resolve(({ target }) => target.decoded(new Before({}))) }).handle({ Before: {}, Boundary: { - always: (to) => to.full.After().resolve(({ target }) => target(new After({}))) + always: (to) => to.full.After().resolve(({ target }) => target.decoded(new After({}))) }, After: {} }) diff --git a/test/machine/SnapshotContext.test.ts b/test/machine/SnapshotContext.test.ts index a7eac43..b4c54e8 100644 --- a/test/machine/SnapshotContext.test.ts +++ b/test/machine/SnapshotContext.test.ts @@ -73,7 +73,7 @@ describe("Machine transition snapshot context", () => { }).resolve(({ snapshot, select }) => { captured = snapshot return States.matches(snapshot, "System.Network.Online") - ? select.online(new Playing({})) + ? select.online.decoded(new Playing({})) : select.unchanged() }) } @@ -112,7 +112,7 @@ describe("Machine transition snapshot context", () => { Disconnect: (to) => to.local.Playing().resolve(({ snapshot, target }) => { captured.push(snapshot) - return target(new Playing({})) + return target.decoded(new Playing({})) }) } } @@ -125,7 +125,7 @@ describe("Machine transition snapshot context", () => { Disconnect: (to) => to.local.Offline().resolve(({ snapshot, target }) => { captured.push(snapshot) - return target(new Offline({})) + return target.decoded(new Offline({})) }) } } @@ -166,7 +166,7 @@ describe("Machine transition snapshot context", () => { }).resolve(({ snapshot, select }) => { captured = snapshot return States.matches(snapshot, "System.Network.Online") - ? select.online(new Playing({})) + ? select.online.decoded(new Playing({})) : select.unchanged() }) } @@ -217,12 +217,12 @@ describe("Machine transition snapshot context", () => { events: Machine.events(), initial: (to) => to.System.initial.resolve(({ target }) => - target( + target.decoded( new System({}), (system) => system - .Work(new Work({}), (work) => work.Finished(new Finished({}))) - .Monitor(new Monitor({}), (monitor) => monitor.Active(new Active({}))) + .Work.decoded(new Work({}), (work) => work.Finished.decoded(new Finished({}))) + .Monitor.decoded(new Monitor({}), (monitor) => monitor.Active.decoded(new Active({}))) ) ) }).handle({ @@ -232,7 +232,7 @@ describe("Machine transition snapshot context", () => { onDone: (to) => to.local.Restarted().resolve(({ snapshot, target }) => { captured = snapshot - return target(new Restarted({})) + return target.decoded(new Restarted({})) }) } } diff --git a/test/machine/StateDefinition.test.ts b/test/machine/StateDefinition.test.ts index b9487b7..991eb77 100644 --- a/test/machine/StateDefinition.test.ts +++ b/test/machine/StateDefinition.test.ts @@ -128,7 +128,7 @@ describe("exact state-definition runtime validation", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: (to) => to.Idle().resolve(({ target }) => target(new Idle({}))) + initial: (to) => to.Idle().resolve(({ target }) => target.decoded(new Idle({}))) }) assert.strictEqual(Machine.stateNodes(machine)[0]?.path, "Idle") @@ -143,7 +143,7 @@ describe("exact state-definition runtime validation", () => { const machine = Machine.make({ states: states.states, events: Machine.events(), - initial: (to) => to.Opaque().resolve(({ target }) => target({ _tag: "OpaqueState", value: 1 })) + initial: (to) => to.Opaque().resolve(({ target }) => target.decoded({ _tag: "OpaqueState", value: 1 })) }) assert.strictEqual(Machine.stateNodes(machine)[0]?.path, "Opaque") diff --git a/test/machine/StateUpdate.test.ts b/test/machine/StateUpdate.test.ts index a516b52..aae0ca0 100644 --- a/test/machine/StateUpdate.test.ts +++ b/test/machine/StateUpdate.test.ts @@ -4,6 +4,162 @@ import { Machine } from "../../src/index.js" import { MachineTest } from "../../src/testing/index.js" describe("state value updates", () => { + it.effect("changes topology and one retained owner atomically", () => + Effect.gen(function*() { + const State = Schema.TaggedUnion({ + Ready: { notice: Schema.NullOr(Schema.String) }, + Idle: {}, + SavingPlan: { request: Schema.String } + }) + const Event = Schema.TaggedUnion({ CreatePlan: { input: Schema.String }, InvalidPlan: {} }) + const states = Machine.states({ + Ready: { + schema: State.cases.Ready, + initial: "Idle", + states: { + Idle: State.cases.Idle, + SavingPlan: State.cases.SavingPlan + } + } + }) + let observedEntry: { readonly notice: string | null; readonly request: string } | undefined + const machine = Machine.make({ + states: states.states, + events: Machine.events(Event), + initial: (to) => + to.Ready.initial.resolve(({ target }) => + target.from({ notice: "Previous notice" }, (ready) => ready.Idle.from()) + ) + }).handle({ + Ready: { + states: { + Idle: { + on: { + CreatePlan: (to) => + to.local.SavingPlan() + .updating(to.branch.Ready) + .resolve(({ current, event, owner, target }) => + target.from({ request: event.input }).update( + owner.decoded(State.cases.Ready.make({ ...current, notice: null })) + ) + ), + InvalidPlan: (to) => + to.local.SavingPlan() + .updating(to.branch.Ready) + .resolve(({ owner, target }) => + target.from({ request: "invalid" }).update( + owner.from({ notice: 1 } as any) + ) + ) + } + }, + SavingPlan: { + entry: ({ ancestors, state }) => { + observedEntry = { notice: ancestors.Ready.notice, request: state.request } + return undefined + } + } + } + } + }) + + assert.deepStrictEqual(Machine.transitionDefinitions(machine)[0]?.branches, [{ + type: "direct", + target: "Ready.SavingPlan", + selection: { kind: "state", scope: "local", path: "Ready.SavingPlan" }, + updates: ["Ready"] + }]) + + const initial = yield* Machine.planInitial(machine) + const invalid = yield* Machine.plan(machine, initial.state, Event.cases.InvalidPlan.make({})).pipe(Effect.flip) + assert.instanceOf(invalid, Machine.MachineSchemaDecodeError) + assert.strictEqual(invalid.state, "Ready") + assert.strictEqual(observedEntry, undefined) + + const planned = yield* Machine.plan(machine, initial.state, Event.cases.CreatePlan.make({ input: "New plan" })) + + assert.deepStrictEqual(planned.next, { + path: "Ready", + value: State.cases.Ready.make({ notice: null }), + state: { + path: "Ready.SavingPlan", + value: State.cases.SavingPlan.make({ request: "New plan" }) + } + }) + assert.deepStrictEqual(observedEntry, { notice: null, request: "New plan" }) + assert.deepStrictEqual(planned.microsteps[0]?.exitPaths, ["Ready.Idle"]) + assert.deepStrictEqual(planned.microsteps[0]?.entryPaths, ["Ready.SavingPlan"]) + assert.deepStrictEqual(planned.microsteps[0]?.transitions[0]?.updates, ["Ready"]) + + const trace = yield* MachineTest.run(machine, { events: [Event.cases.CreatePlan.make({ input: "New plan" })] }) + yield* MachineTest.verify(machine, trace) + assert.strictEqual(MachineTest.coverage(machine, trace).microsteps.updates, 1) + })) + + it.effect("combines invocation completion with a schema-less destination", () => + Effect.gen(function*() { + const State = Schema.TaggedUnion({ + Ready: { day: Schema.String, notice: Schema.String }, + Saving: { request: Schema.String } + }) + const states = Machine.states({ + Ready: { + schema: State.cases.Ready, + initial: "Saving", + states: { + Idle: {}, + Saving: State.cases.Saving + } + } + }) + let idleSawDay: string | undefined + const machine = Machine.make({ + states: states.states, + events: Machine.events(), + initial: (to) => + to.Ready.initial.resolve(({ target }) => + target.from( + { day: "Sunday", notice: "Saving" }, + (ready) => ready.Saving.from({ request: "change" }) + ) + ) + }).handle({ + Ready: { + states: { + Idle: { + entry: ({ ancestors }) => { + idleSawDay = ancestors.Ready.day + return undefined + } + }, + Saving: { + invoke: (from) => + from.effect("save", () => Effect.succeed("Monday")).onDone((to) => + to.local.Idle() + .updating(to.branch.Ready) + .resolve(({ current, output, owner, target }) => + target.from().update( + owner.decoded(State.cases.Ready.make({ ...current, day: output, notice: "Saved" })) + ) + ) + ) + } + } + } + }) + + const ref = yield* Machine.start(machine) + for (let index = 0; index < 5; index += 1) yield* Effect.yieldNow + const snapshot = yield* ref.state + assert.strictEqual(snapshot.path, "Ready") + if (snapshot.path !== "Ready") throw new Error("expected Ready") + assert.strictEqual(snapshot.value.day, "Monday") + assert.strictEqual(snapshot.value.notice, "Saved") + assert.strictEqual(snapshot.state.path, "Ready.Idle") + assert.strictEqual(idleSawDay, "Monday") + yield* ref.stop + })) + it.effect("updates the local owner without changing its active descendants", () => Effect.gen(function*() { const State = Schema.TaggedUnion({ @@ -41,8 +197,8 @@ describe("state value updates", () => { idle: { on: { Increment: (to) => - to.branch.session.update(({ ancestors, target }) => - target.from({ count: ancestors.session.count + 1 }) + to.branch.session.update(({ current, owner }) => + owner.from({ count: current.count + 1 }) ) } } @@ -60,7 +216,8 @@ describe("state value updates", () => { branches: [{ type: "direct", target: undefined, - selection: { kind: "update", scope: "branch", path: "session" } + selection: { kind: "update", scope: "branch", path: "session" }, + updates: ["session"] }] }]) @@ -83,7 +240,8 @@ describe("state value updates", () => { branchIndex: 0, branchKey: undefined, target: undefined, - resolvedTarget: undefined + resolvedTarget: undefined, + updates: ["session"] }]) assert.deepStrictEqual(planned.microsteps[0]?.exitPaths, []) assert.deepStrictEqual(planned.microsteps[0]?.entryPaths, []) @@ -135,13 +293,15 @@ describe("state value updates", () => { key: "changed", title: "Value changed", target: undefined, - selection: { kind: "update", scope: "local", path: "scope" } + selection: { kind: "update", scope: "local", path: "scope" }, + updates: ["scope"] }, { type: "branch", key: "unchanged", title: "unchanged", target: undefined, - selection: { kind: "none", scope: "local", path: undefined } + selection: { kind: "none", scope: "local", path: undefined }, + updates: [] }]) const initial = yield* Machine.planInitial(machine) @@ -192,11 +352,10 @@ describe("state value updates", () => { return undefined }, on: { - Quiet: (to) => - to.local.update(({ ancestors, target }) => target.from({ count: ancestors.scope.count + 1 })), + Quiet: (to) => to.local.update(({ current, owner }) => owner.from({ count: current.count + 1 })), Loud: (to) => to.local.update( - ({ ancestors, target }) => target.from({ count: ancestors.scope.count + 1 }), + ({ current, owner }) => owner.from({ count: current.count + 1 }), { reenter: true } ) } @@ -238,9 +397,9 @@ describe("state value updates", () => { states: { idle: { always: (to) => - to.local.update(({ ancestors, decline, target }) => - ancestors.scope.count < 2 - ? target.from({ count: ancestors.scope.count + 1 }) + to.local.update(({ current, decline, owner }) => + current.count < 2 + ? owner.from({ count: current.count + 1 }) : decline(), { declinable: true }) } } @@ -284,8 +443,7 @@ describe("state value updates", () => { a: { on: { Leave: (to) => to.full.outside().resolve(({ target }) => target.from()), - Update: (to) => - to.local.update(({ ancestors, target }) => target.from({ count: ancestors.root.count + 1 })) + Update: (to) => to.local.update(({ current, owner }) => owner.from({ count: current.count + 1 })) } } } @@ -365,9 +523,7 @@ describe("state value updates", () => { idle: { on: { Update: (to) => - to.branch.root.update(({ ancestors, target }) => - target.from({ revision: ancestors.root.revision + 1 }) - ) + to.branch.root.update(({ current, owner }) => owner.from({ revision: current.revision + 1 })) } } } @@ -408,7 +564,7 @@ describe("state value updates", () => { states: { idle: { on: { - Break: (to) => to.local.update(({ target }) => target.from({ count: "bad" } as any)) + Break: (to) => to.local.update(({ owner }) => owner.from({ count: "bad" } as any)) } } } @@ -444,7 +600,7 @@ describe("state value updates", () => { idle: { invoke: (from) => from.effect("load", () => Effect.sync(() => ++starts)).onDone((to) => - to.local.update(({ output, target }) => target.from({ count: output })) + to.local.update(({ output, owner }) => owner.from({ count: output })) ) } } @@ -485,11 +641,11 @@ describe("state value updates", () => { idle: { on: { Update: (to) => - to.local.update(({ self, target }, enqueue) => { + to.local.update(({ self, owner }, enqueue) => { enqueue.raise(Events.Raised()) enqueue.emit(Emissions.Changed({ count: 1 })) enqueue.sendTo(self, Events.Raised()) - return target.from({ count: 1 }) + return owner.from({ count: 1 }) }), Raised: (to) => to.none } diff --git a/test/machine/Totality.test.ts b/test/machine/Totality.test.ts index 5817f4e..28cf15a 100644 --- a/test/machine/Totality.test.ts +++ b/test/machine/Totality.test.ts @@ -257,11 +257,11 @@ describe("machine operation totality", () => { const machine = Machine.make({ states: states.states, events: Machine.events(Finish), - initial: (to) => to.Value().resolve(({ target }) => target({ _tag: "Value", amount: 42 })) + initial: (to) => to.Value().resolve(({ target }) => target.decoded({ _tag: "Value", amount: 42 })) }).handle({ Value: { on: { - Finish: (to) => to.full.Done().resolve(({ target }) => target({ _tag: "Done" })) + Finish: (to) => to.full.Done().resolve(({ target }) => target.decoded({ _tag: "Done" })) } }, Done: { output: () => 42 } diff --git a/test/machine/Visualization.test.ts b/test/machine/Visualization.test.ts index 9ce21e8..f473245 100644 --- a/test/machine/Visualization.test.ts +++ b/test/machine/Visualization.test.ts @@ -102,17 +102,22 @@ const makeMachine = (unsafeStart = false) => Start: unsafeStart ? (to) => to.full.disabled().resolve(({ target }) => - ({ ...target(new Disabled({})), path: "application.workflow.idle" }) as any + ({ ...target.decoded(new Disabled({})), path: "application.workflow.idle" }) as any ) : (to) => - to.local.running().resolve(({ target }) => - target(new Running({}), (running) => running.editing(new Editing({}))) - ), - Refresh: (to) => to.local.update(({ target }) => target(new Workflow({}))) + to.local.running() + .updating(to.branch.application.workflow) + .resolve(({ owner, target }) => + target.decoded( + new Running({}), + (running) => running.editing.decoded(new Editing({})) + ).update(owner.decoded(new Workflow({}))) + ), + Refresh: (to) => to.local.update(({ owner }) => owner.decoded(new Workflow({}))) } }, running: { - initialize: ({ builder }) => builder(new Editing({})) + initialize: ({ builder }) => builder.decoded(new Editing({})) } } }, @@ -120,7 +125,7 @@ const makeMachine = (unsafeStart = false) => states: { online: { on: { - Disconnect: (to) => to.local.offline().resolve(({ target }) => target(new Offline({}))) + Disconnect: (to) => to.local.offline().resolve(({ target }) => target.decoded(new Offline({}))) } } } @@ -153,7 +158,7 @@ const lifecycleDefinition = Machine.make({ id: "lifecycle-inspection", states: LifecycleStates.states, events: Machine.events(), - initial: (to) => to.idle().resolve(({ target }) => target(new Idle({}))) + initial: (to) => to.idle().resolve(({ target }) => target.decoded(new Idle({}))) }) const makeLifecycleMachine = (unsafe: "always" | "done" | undefined = undefined) => @@ -161,14 +166,14 @@ const makeLifecycleMachine = (unsafe: "always" | "done" | undefined = undefined) idle: { always: (to) => to.full.workflow().resolve(({ target }) => { - const selected = target(new Workflow({}), (workflow) => workflow.complete(new Complete({}))) + const selected = target.decoded(new Workflow({}), (workflow) => workflow.complete.decoded(new Complete({}))) return unsafe === "always" ? ({ ...selected, path: "idle" } as any) : selected }) }, workflow: { onDone: (to) => to.full.disabled().resolve(({ target }) => { - const selected = target(new Disabled({})) + const selected = target.decoded(new Disabled({})) return unsafe === "done" ? ({ ...selected, path: "workflow" } as any) : selected }) } @@ -235,7 +240,8 @@ describe("Machine structural visualization", () => { branches: [{ type: "direct", target: "application.workflow.running", - selection: { path: "application.workflow.running", kind: "state", scope: "local" } + selection: { path: "application.workflow.running", kind: "state", scope: "local" }, + updates: ["application.workflow"] }] }, { @@ -246,7 +252,8 @@ describe("Machine structural visualization", () => { branches: [{ type: "direct", target: undefined, - selection: { path: "application.workflow", kind: "update", scope: "local" } + selection: { path: "application.workflow", kind: "update", scope: "local" }, + updates: ["application.workflow"] }] }, { @@ -257,7 +264,8 @@ describe("Machine structural visualization", () => { branches: [{ type: "direct", target: "application.connection.offline", - selection: { path: "application.connection.offline", kind: "state", scope: "local" } + selection: { path: "application.connection.offline", kind: "state", scope: "local" }, + updates: [] }] } ]) @@ -287,7 +295,8 @@ describe("Machine structural visualization", () => { branches: [{ type: "direct", target: undefined, - selection: { path: undefined, kind: "none", scope: "local" } + selection: { path: undefined, kind: "none", scope: "local" }, + updates: [] }] }, { @@ -298,7 +307,8 @@ describe("Machine structural visualization", () => { branches: [{ type: "direct", target: undefined, - selection: { path: undefined, kind: "none", scope: "local" } + selection: { path: undefined, kind: "none", scope: "local" }, + updates: [] }] }, { @@ -309,7 +319,8 @@ describe("Machine structural visualization", () => { branches: [{ type: "direct", target: undefined, - selection: { path: undefined, kind: "none", scope: "local" } + selection: { path: undefined, kind: "none", scope: "local" }, + updates: [] }] } ]) @@ -331,7 +342,8 @@ describe("Machine structural visualization", () => { branches: [{ type: "direct", target: "workflow", - selection: { path: "workflow", kind: "state", scope: "full" } + selection: { path: "workflow", kind: "state", scope: "full" }, + updates: [] }] }, { @@ -342,7 +354,8 @@ describe("Machine structural visualization", () => { branches: [{ type: "direct", target: "disabled", - selection: { path: "disabled", kind: "state", scope: "full" } + selection: { path: "disabled", kind: "state", scope: "full" }, + updates: [] }] } ]) @@ -377,7 +390,7 @@ describe("Machine structural visualization", () => { "│ ├─ ● workflow [compound, initial: idle]", "│ │ ├─ ● idle", "│ │ │ ├─ ◇ on: Start", - "│ │ │ │ └┄ → running", + "│ │ │ │ └┄ → running / update application.workflow", "│ │ │ └─ ◇ on: Refresh", "│ │ │ └┄ update application.workflow", "│ │ ├─ ○ running [compound, initial: editing]", @@ -406,7 +419,7 @@ describe("Machine structural visualization", () => { assert.include(rendered, "state_5 --> [*]") assert.include(rendered, "state \"○ recent [history: shallow]\" as state_6") assert.include(rendered, "[*] --> state_0") - assert.include(rendered, "state_2 --> state_3: Start") + assert.include(rendered, "state_2 --> state_3: Start / update application.workflow") assert.include(rendered, "state_2: Refresh / update application.workflow") assert.include(rendered, "state_8 --> state_9: Disconnect") assert.notMatch(rendered, /state_\d+ --> state_\d+: Refresh/) diff --git a/test/machine/visualization/mermaid.ts b/test/machine/visualization/mermaid.ts index 8249848..691b541 100644 --- a/test/machine/visualization/mermaid.ts +++ b/test/machine/visualization/mermaid.ts @@ -166,14 +166,19 @@ export const makeMermaidRenderer = ( const source = ids.get(definition.source) if (source === undefined) continue for (const branch of definition.branches) { - if (branch.selection.kind === "update" && branch.selection.path !== undefined) { + if (branch.target === undefined && branch.updates.length > 0) { lines.push( - ` ${source}: ${branchLabel(definition, branch)} / update ${escapeText(branch.selection.path)}` + ` ${source}: ${branchLabel(definition, branch)} / update ${escapeText(branch.updates.join(", "))}` ) continue } const target = branch.target === undefined ? undefined : ids.get(branch.target) - if (target !== undefined) lines.push(` ${source} --> ${target}: ${branchLabel(definition, branch)}`) + if (target !== undefined) { + const updates = branch.updates.length === 0 + ? "" + : ` / update ${escapeText(branch.updates.join(", "))}` + lines.push(` ${source} --> ${target}: ${branchLabel(definition, branch)}${updates}`) + } } } diff --git a/test/machine/visualization/model.ts b/test/machine/visualization/model.ts index 8fbdf0b..f3e1f69 100644 --- a/test/machine/visualization/model.ts +++ b/test/machine/visualization/model.ts @@ -51,6 +51,7 @@ export interface TransitionDefinition { readonly scope: "local" | "branch" | "full" | "initial" | undefined readonly path: string | undefined } + readonly updates: ReadonlyArray } | { readonly type: "branch" @@ -62,6 +63,7 @@ export interface TransitionDefinition { readonly scope: "local" | "branch" | "full" | "initial" | undefined readonly path: string | undefined } + readonly updates: ReadonlyArray } > } diff --git a/test/machine/visualization/text.ts b/test/machine/visualization/text.ts index 64c7f94..8f0e50a 100644 --- a/test/machine/visualization/text.ts +++ b/test/machine/visualization/text.ts @@ -21,20 +21,21 @@ interface TransitionLabel { const triggerLabels = (definitions: ReadonlyArray): ReadonlyArray => definitions.flatMap((definition) => { const branches = definition.branches.flatMap((branch) => { - if (branch.selection.kind === "update" && branch.selection.path !== undefined) { + if (branch.target === undefined && branch.updates.length > 0) { return [ branch.type === "direct" - ? `update ${branch.selection.path}` - : `[${branch.title}] update ${branch.selection.path}` + ? `update ${branch.updates.join(", ")}` + : `[${branch.title}] update ${branch.updates.join(", ")}` ] } if (branch.target === undefined) return [] const target = branch.target.slice(branch.target.lastIndexOf(".") + 1) + const updates = branch.updates.length === 0 ? "" : ` / update ${branch.updates.join(", ")}` return [ branch.type === "direct" ? - `→ ${target}` : - `[${branch.title}] → ${target}` + `→ ${target}${updates}` : + `[${branch.title}] → ${target}${updates}` ] }) if (branches.length === 0) return [] diff --git a/test/testing/Coverage.test.ts b/test/testing/Coverage.test.ts index e3b3960..3d4bf62 100644 --- a/test/testing/Coverage.test.ts +++ b/test/testing/Coverage.test.ts @@ -20,16 +20,16 @@ const CounterStates = Machine.states({ count: Count, done: Done }) const counterMachine = Machine.make({ states: CounterStates.states, events: Machine.events(Add, Finish), - initial: (to) => to.count().resolve(({ target }) => target(new Count({ value: 0 }))) + initial: (to) => to.count().resolve(({ target }) => target.decoded(new Count({ value: 0 }))) }).handle({ count: { on: { Add: (to) => to.full.count().resolve( - ({ event, state, target }) => target(new Count({ value: state.value + event.amount })), + ({ event, state, target }) => target.decoded(new Count({ value: state.value + event.amount })), { reenter: true, declinable: true } ), - Finish: (to) => to.full.done().resolve(({ target }) => target(new Done({}))) + Finish: (to) => to.full.done().resolve(({ target }) => target.decoded(new Done({}))) } }, done: {} @@ -44,14 +44,14 @@ const opaqueMachine = Machine.make({ states: OpaqueStates.states, events: Machine.events(), input: Schema.Any, - initial: (to) => to.opaque().resolve(({ input: payload, target }) => target(new Opaque({ payload }))) + initial: (to) => to.opaque().resolve(({ input: payload, target }) => target.decoded(new Opaque({ payload }))) }) const StartupStates = Machine.states({ count: Count }) const startupMachine = Machine.make({ states: StartupStates.states, events: Machine.events(Add), - initial: (to) => to.count().resolve(({ target }) => target(new Count({ value: 0 }))) + initial: (to) => to.count().resolve(({ target }) => target.decoded(new Count({ value: 0 }))) }).handle({ count: { always: (to) => @@ -60,12 +60,14 @@ const startupMachine = Machine.make({ unchanged: { target: to.none } }).resolve(({ state, select }) => state.value === 0 - ? select.zero(new Count({ value: 1 })) + ? select.zero.decoded(new Count({ value: 1 })) : select.unchanged() ), on: { Add: (to) => - to.full.count().resolve(({ event, state, target }) => target(new Count({ value: state.value + event.amount }))) + to.full.count().resolve(({ event, state, target }) => + target.decoded(new Count({ value: state.value + event.amount })) + ) } } }) @@ -77,7 +79,7 @@ class Select extends Schema.TaggedClass