@typeonce/effect-machine 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/Machine.d.ts +62 -16
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +4 -3
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +33 -3
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +38 -9
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +11 -2
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +49 -9
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/topology.d.ts +9 -1
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +7 -0
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +15 -3
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +2 -0
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js.map +1 -1
- package/docs/agent-guide.md +17 -0
- package/package.json +1 -1
- package/src/Machine.ts +230 -43
- package/src/internal/machine/executionPlan.ts +37 -3
- package/src/internal/machine/machine.ts +57 -9
- package/src/internal/machine/planner.ts +64 -11
- package/src/internal/machine/topology.ts +18 -1
- package/src/internal/testing/machine/verification.ts +16 -3
- package/src/testing/MachineTest.ts +2 -0
package/src/Machine.ts
CHANGED
|
@@ -3577,9 +3577,10 @@ export declare namespace Machine {
|
|
|
3577
3577
|
*
|
|
3578
3578
|
* **Details**
|
|
3579
3579
|
*
|
|
3580
|
-
* Every branch exposes its
|
|
3581
|
-
* A compound local or branch target covers its descendants
|
|
3582
|
-
* `
|
|
3580
|
+
* Every branch exposes its static selection without executing its resolver.
|
|
3581
|
+
* A compound local or branch target covers its descendants. An `update`
|
|
3582
|
+
* selection keeps `target` undefined and records its value owner in
|
|
3583
|
+
* `selection.path`; `none` identifies an explicitly targetless branch.
|
|
3583
3584
|
*
|
|
3584
3585
|
* @category models
|
|
3585
3586
|
* @since 0.4.0
|
|
@@ -4461,6 +4462,33 @@ export declare namespace Machine {
|
|
|
4461
4462
|
>
|
|
4462
4463
|
}
|
|
4463
4464
|
|
|
4465
|
+
/**
|
|
4466
|
+
* Opaque instruction that replaces one active compound or parallel state's
|
|
4467
|
+
* value without changing its active descendants.
|
|
4468
|
+
*
|
|
4469
|
+
* @category models
|
|
4470
|
+
* @since 0.21.0
|
|
4471
|
+
*/
|
|
4472
|
+
export interface StateUpdate<
|
|
4473
|
+
States extends StateSchemas,
|
|
4474
|
+
StateId extends ValuedStateIdentifier<States>
|
|
4475
|
+
> {
|
|
4476
|
+
readonly [Topology.StateUpdateTypeId]: typeof Topology.StateUpdateTypeId
|
|
4477
|
+
readonly path: StateId
|
|
4478
|
+
readonly value: StateByIdentifier<States, StateId>
|
|
4479
|
+
}
|
|
4480
|
+
|
|
4481
|
+
/** @internal */
|
|
4482
|
+
type StateUpdateBuilder<
|
|
4483
|
+
States extends StateSchemas,
|
|
4484
|
+
StateId extends ValuedStateIdentifier<States>
|
|
4485
|
+
> =
|
|
4486
|
+
& ((value: StateByIdentifier<States, StateId>) => StateUpdate<States, StateId>)
|
|
4487
|
+
& FromMethod<
|
|
4488
|
+
readonly [input: SchemaByIdentifier<States, StateId>["~type.make.in"]],
|
|
4489
|
+
StateUpdate<States, StateId>
|
|
4490
|
+
>
|
|
4491
|
+
|
|
4464
4492
|
/**
|
|
4465
4493
|
* Opaque result returned by an explicitly targetless transition.
|
|
4466
4494
|
*
|
|
@@ -4745,6 +4773,45 @@ export declare namespace Machine {
|
|
|
4745
4773
|
& SelectionTreeWithPrefix<AllStates, Children, Path, Scope, Builder>
|
|
4746
4774
|
: SelectionMethod<Builder, Path>
|
|
4747
4775
|
|
|
4776
|
+
/** @internal */
|
|
4777
|
+
type StateUpdateSelectionForNode<
|
|
4778
|
+
AllStates extends StateSchemas,
|
|
4779
|
+
Node,
|
|
4780
|
+
Path extends string
|
|
4781
|
+
> = Node extends { readonly states: StateSchemas } ? NodeSchema<Node> extends never ? {}
|
|
4782
|
+
: {
|
|
4783
|
+
readonly update: SelectionValue<
|
|
4784
|
+
StateUpdateBuilder<AllStates, Extract<Path, ValuedStateIdentifier<AllStates>>>,
|
|
4785
|
+
Path,
|
|
4786
|
+
"update"
|
|
4787
|
+
>
|
|
4788
|
+
}
|
|
4789
|
+
: {}
|
|
4790
|
+
|
|
4791
|
+
/** @internal */
|
|
4792
|
+
type BranchUpdateSelectionPath<
|
|
4793
|
+
AllStates extends StateSchemas,
|
|
4794
|
+
Node,
|
|
4795
|
+
Path extends string,
|
|
4796
|
+
Rest extends string
|
|
4797
|
+
> =
|
|
4798
|
+
& StateUpdateSelectionForNode<AllStates, Node, Path>
|
|
4799
|
+
& (Node extends { readonly states: infer Children extends StateSchemas } ?
|
|
4800
|
+
Rest extends `${infer Head}.${infer Tail}` ? Head extends keyof Children ? {
|
|
4801
|
+
readonly [Key in Head]: BranchUpdateSelectionPath<
|
|
4802
|
+
AllStates,
|
|
4803
|
+
Children[Head],
|
|
4804
|
+
JoinPath<Path, Head>,
|
|
4805
|
+
Tail
|
|
4806
|
+
>
|
|
4807
|
+
}
|
|
4808
|
+
: {}
|
|
4809
|
+
: Rest extends keyof Children ? {
|
|
4810
|
+
readonly [Key in Rest]: StateUpdateSelectionForNode<AllStates, Children[Rest], JoinPath<Path, Rest>>
|
|
4811
|
+
}
|
|
4812
|
+
: {}
|
|
4813
|
+
: {})
|
|
4814
|
+
|
|
4748
4815
|
type FullSelectionNode<
|
|
4749
4816
|
AllStates extends StateSchemas,
|
|
4750
4817
|
Node,
|
|
@@ -4769,13 +4836,17 @@ export declare namespace Machine {
|
|
|
4769
4836
|
Source extends StateNodeIdentifier<States>,
|
|
4770
4837
|
Root extends string = Source extends `${infer Head}.${string}` ? Head : Source
|
|
4771
4838
|
> = Root extends ActiveStateKey<States> ? Root extends keyof BranchTargetBuilder<States, Source> ? {
|
|
4772
|
-
readonly [Key in Root]:
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4839
|
+
readonly [Key in Root]:
|
|
4840
|
+
& SelectionNode<
|
|
4841
|
+
States,
|
|
4842
|
+
States[Key],
|
|
4843
|
+
Key,
|
|
4844
|
+
"branch",
|
|
4845
|
+
BranchTargetBuilder<States, Source>[Key]
|
|
4846
|
+
>
|
|
4847
|
+
& (Source extends ChoiceIdentifier<States> ? {}
|
|
4848
|
+
: Source extends `${Key}.${infer Rest}` ? BranchUpdateSelectionPath<States, States[Key], Key, Rest>
|
|
4849
|
+
: StateUpdateSelectionForNode<States, States[Key], Key>)
|
|
4779
4850
|
}
|
|
4780
4851
|
: {}
|
|
4781
4852
|
: {}
|
|
@@ -4783,14 +4854,21 @@ export declare namespace Machine {
|
|
|
4783
4854
|
type LocalTargetSelector<
|
|
4784
4855
|
States extends StateSchemas,
|
|
4785
4856
|
Source extends StateNodeIdentifier<States>
|
|
4786
|
-
> = NearestCompoundScope<States, Source> extends infer Scope extends
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4857
|
+
> = NearestCompoundScope<States, Source> extends infer Scope ? [Scope] extends [never] ? {}
|
|
4858
|
+
: Scope extends StateIdentifier<States> ?
|
|
4859
|
+
ChildrenOf<States, Scope> extends infer Children extends StateSchemas ?
|
|
4860
|
+
LocalTargetBuilder<States, Source> extends infer Builder ?
|
|
4861
|
+
& SelectionTreeWithPrefix<States, Children, Scope, "local", Builder>
|
|
4862
|
+
& ("with" extends keyof Builder ? {
|
|
4863
|
+
readonly with: SelectionValue<Builder["with"], Scope>
|
|
4864
|
+
}
|
|
4865
|
+
: {})
|
|
4866
|
+
& (Source extends ChoiceIdentifier<States> ? {}
|
|
4867
|
+
: Scope extends ValuedStateIdentifier<States> ? {
|
|
4868
|
+
readonly update: SelectionValue<StateUpdateBuilder<States, Scope>, Scope, "update">
|
|
4869
|
+
}
|
|
4870
|
+
: {})
|
|
4871
|
+
: {}
|
|
4794
4872
|
: {}
|
|
4795
4873
|
: {}
|
|
4796
4874
|
: {}
|
|
@@ -4831,9 +4909,9 @@ export declare namespace Machine {
|
|
|
4831
4909
|
> {
|
|
4832
4910
|
/** Handles the trigger without selecting a destination. */
|
|
4833
4911
|
readonly none: SelectionValue<TargetBuilder<States, Source>["none"], never, "none">
|
|
4834
|
-
/** Selects a destination
|
|
4912
|
+
/** Selects a destination or updates the nearest active compound scope. */
|
|
4835
4913
|
readonly local: LocalTargetSelector<States, Source>
|
|
4836
|
-
/** Selects a destination
|
|
4914
|
+
/** Selects a destination or updates a valued active ancestor under the current root. */
|
|
4837
4915
|
readonly branch: BranchTargetSelector<States, Source>
|
|
4838
4916
|
/** Selects a complete destination under any top-level state. */
|
|
4839
4917
|
readonly full: FullTargetSelector<States>
|
|
@@ -5243,9 +5321,9 @@ export declare namespace Machine {
|
|
|
5243
5321
|
* **Details**
|
|
5244
5322
|
*
|
|
5245
5323
|
* Handlers return snapshots for complete state replacement, target builder
|
|
5246
|
-
* results for path-safe partial transitions,
|
|
5247
|
-
* explicitly targetless transition. Raw decoded state
|
|
5248
|
-
* not accepted at transition boundaries.
|
|
5324
|
+
* results for path-safe partial transitions, state-value updates, or
|
|
5325
|
+
* `target.none()` for an explicitly targetless transition. Raw decoded state
|
|
5326
|
+
* values and `void` are not accepted at transition boundaries.
|
|
5249
5327
|
*
|
|
5250
5328
|
* @category utility types
|
|
5251
5329
|
* @since 0.4.0
|
|
@@ -5255,11 +5333,13 @@ export declare namespace Machine {
|
|
|
5255
5333
|
| Target<States, StateIdentifier<States>>
|
|
5256
5334
|
| HistoryTarget<States, HistoryIdentifier<States>>
|
|
5257
5335
|
| ChoiceTarget<States, ChoiceIdentifier<States>>
|
|
5336
|
+
| StateUpdate<States, ValuedStateIdentifier<States>>
|
|
5258
5337
|
| StateConstruction<
|
|
5259
5338
|
| Snapshot<States>
|
|
5260
5339
|
| Target<States, StateIdentifier<States>>
|
|
5261
5340
|
| HistoryTarget<States, HistoryIdentifier<States>>
|
|
5262
5341
|
| ChoiceTarget<States, ChoiceIdentifier<States>>
|
|
5342
|
+
| StateUpdate<States, ValuedStateIdentifier<States>>
|
|
5263
5343
|
>
|
|
5264
5344
|
| NoTarget
|
|
5265
5345
|
|
|
@@ -5639,6 +5719,28 @@ export declare namespace Machine {
|
|
|
5639
5719
|
| (SelectionKind<Selection> extends "none" ? undefined : SelectedTargetResult<Selection> | undefined)
|
|
5640
5720
|
| Declined
|
|
5641
5721
|
|
|
5722
|
+
/** @internal */
|
|
5723
|
+
type StateUpdateResolver<
|
|
5724
|
+
Events extends ReadonlyArray<TaggedSchema>,
|
|
5725
|
+
Emits extends ReadonlyArray<TaggedSchema>,
|
|
5726
|
+
Context,
|
|
5727
|
+
Selection
|
|
5728
|
+
> = (
|
|
5729
|
+
context: TransitionResolveContext<Context, Selection>,
|
|
5730
|
+
enqueue: Enqueue<EventOf<Events>, EmitOf<Emits>>
|
|
5731
|
+
) => SelectedTargetResult<Selection>
|
|
5732
|
+
|
|
5733
|
+
/** @internal */
|
|
5734
|
+
type DeclinableStateUpdateResolver<
|
|
5735
|
+
Events extends ReadonlyArray<TaggedSchema>,
|
|
5736
|
+
Emits extends ReadonlyArray<TaggedSchema>,
|
|
5737
|
+
Context,
|
|
5738
|
+
Selection
|
|
5739
|
+
> = (
|
|
5740
|
+
context: TransitionResolveContext<Context, Selection> & DeclineCapability,
|
|
5741
|
+
enqueue: Enqueue<EventOf<Events>, EmitOf<Emits>>
|
|
5742
|
+
) => SelectedTargetResult<Selection> | Declined
|
|
5743
|
+
|
|
5642
5744
|
/** One named destination declared by a branching transition. */
|
|
5643
5745
|
export interface TransitionBranchInput<
|
|
5644
5746
|
Selection extends TargetSelection<any, any, any> = TargetSelection<any, any, any>
|
|
@@ -5880,6 +5982,80 @@ export declare namespace Machine {
|
|
|
5880
5982
|
: {}
|
|
5881
5983
|
: {})
|
|
5882
5984
|
|
|
5985
|
+
/** @internal */
|
|
5986
|
+
interface StateUpdateTransitionRequired<
|
|
5987
|
+
States extends StateSchemas,
|
|
5988
|
+
Events extends ReadonlyArray<TaggedSchema>,
|
|
5989
|
+
Emits extends ReadonlyArray<TaggedSchema>,
|
|
5990
|
+
StateId extends StateNodeIdentifier<States>,
|
|
5991
|
+
Context,
|
|
5992
|
+
Reenter extends boolean,
|
|
5993
|
+
Selection extends TargetSelection<any, any, "update">
|
|
5994
|
+
> {
|
|
5995
|
+
(
|
|
5996
|
+
resolve: StateUpdateResolver<Events, Emits, Context, Selection>,
|
|
5997
|
+
options?: TransitionRequiredOptions<Reenter>
|
|
5998
|
+
): BuiltTransition<
|
|
5999
|
+
States,
|
|
6000
|
+
Events,
|
|
6001
|
+
Emits,
|
|
6002
|
+
StateId,
|
|
6003
|
+
Context,
|
|
6004
|
+
Reenter,
|
|
6005
|
+
SelectedTargetResult<Selection>,
|
|
6006
|
+
"required"
|
|
6007
|
+
>
|
|
6008
|
+
}
|
|
6009
|
+
|
|
6010
|
+
/** @internal */
|
|
6011
|
+
interface StateUpdateTransitionDeclinable<
|
|
6012
|
+
States extends StateSchemas,
|
|
6013
|
+
Events extends ReadonlyArray<TaggedSchema>,
|
|
6014
|
+
Emits extends ReadonlyArray<TaggedSchema>,
|
|
6015
|
+
StateId extends StateNodeIdentifier<States>,
|
|
6016
|
+
Context,
|
|
6017
|
+
Reenter extends boolean,
|
|
6018
|
+
Selection extends TargetSelection<any, any, "update">
|
|
6019
|
+
> {
|
|
6020
|
+
(
|
|
6021
|
+
resolve: DeclinableStateUpdateResolver<Events, Emits, Context, Selection>,
|
|
6022
|
+
options: TransitionDeclinableOptions<Reenter>
|
|
6023
|
+
): BuiltTransition<
|
|
6024
|
+
States,
|
|
6025
|
+
Events,
|
|
6026
|
+
Emits,
|
|
6027
|
+
StateId,
|
|
6028
|
+
Context,
|
|
6029
|
+
Reenter,
|
|
6030
|
+
SelectedTargetResult<Selection> | Declined,
|
|
6031
|
+
"declinable"
|
|
6032
|
+
>
|
|
6033
|
+
}
|
|
6034
|
+
|
|
6035
|
+
/** @internal */
|
|
6036
|
+
type StateUpdateTransition<
|
|
6037
|
+
States extends StateSchemas,
|
|
6038
|
+
Events extends ReadonlyArray<TaggedSchema>,
|
|
6039
|
+
Emits extends ReadonlyArray<TaggedSchema>,
|
|
6040
|
+
StateId extends StateNodeIdentifier<States>,
|
|
6041
|
+
Context,
|
|
6042
|
+
Reenter extends boolean,
|
|
6043
|
+
Acceptance extends TransitionAcceptance,
|
|
6044
|
+
Selection extends TargetSelection<any, any, "update">
|
|
6045
|
+
> =
|
|
6046
|
+
& Selection
|
|
6047
|
+
& StateUpdateTransitionRequired<States, Events, Emits, StateId, Context, Reenter, Selection>
|
|
6048
|
+
& ("declinable" extends Acceptance ? StateUpdateTransitionDeclinable<
|
|
6049
|
+
States,
|
|
6050
|
+
Events,
|
|
6051
|
+
Emits,
|
|
6052
|
+
StateId,
|
|
6053
|
+
Context,
|
|
6054
|
+
Reenter,
|
|
6055
|
+
Selection
|
|
6056
|
+
>
|
|
6057
|
+
: {})
|
|
6058
|
+
|
|
5883
6059
|
/** @internal Type evidence retained by a machine initial-entry declaration. */
|
|
5884
6060
|
export interface InitialBuilderEvidence<out Selection> {
|
|
5885
6061
|
readonly [InitialBuilderTypeId]: Types.Covariant<Selection>
|
|
@@ -5933,19 +6109,18 @@ export declare namespace Machine {
|
|
|
5933
6109
|
Reenter extends boolean,
|
|
5934
6110
|
Acceptance extends TransitionAcceptance,
|
|
5935
6111
|
Node
|
|
5936
|
-
> = Node extends
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
readonly [Key in keyof Node]: TransitionSelectorNode<
|
|
6112
|
+
> = Node extends TargetSelection<any, any, "update"> ? StateUpdateTransition<
|
|
6113
|
+
States,
|
|
6114
|
+
Events,
|
|
6115
|
+
Emits,
|
|
6116
|
+
StateId,
|
|
6117
|
+
Context,
|
|
6118
|
+
Reenter,
|
|
6119
|
+
Acceptance,
|
|
6120
|
+
Node
|
|
6121
|
+
>
|
|
6122
|
+
: Node extends (...args: infer Args) => infer Selection ? Selection extends TargetSelection<any, any, any> ?
|
|
6123
|
+
& ((...args: Args) => TransitionTarget<
|
|
5949
6124
|
States,
|
|
5950
6125
|
Events,
|
|
5951
6126
|
Emits,
|
|
@@ -5953,10 +6128,21 @@ export declare namespace Machine {
|
|
|
5953
6128
|
Context,
|
|
5954
6129
|
Reenter,
|
|
5955
6130
|
Acceptance,
|
|
5956
|
-
|
|
5957
|
-
>
|
|
5958
|
-
|
|
5959
|
-
|
|
6131
|
+
Selection
|
|
6132
|
+
>)
|
|
6133
|
+
& {
|
|
6134
|
+
readonly [Key in keyof Node]: TransitionSelectorNode<
|
|
6135
|
+
States,
|
|
6136
|
+
Events,
|
|
6137
|
+
Emits,
|
|
6138
|
+
StateId,
|
|
6139
|
+
Context,
|
|
6140
|
+
Reenter,
|
|
6141
|
+
Acceptance,
|
|
6142
|
+
Node[Key]
|
|
6143
|
+
>
|
|
6144
|
+
}
|
|
6145
|
+
: never
|
|
5960
6146
|
: Node extends TargetSelection<any, any, any> ? TransitionTarget<
|
|
5961
6147
|
States,
|
|
5962
6148
|
Events,
|
|
@@ -8760,9 +8946,10 @@ export const initialDefinition: <M extends Machine.Any>(machine: M) => Machine.I
|
|
|
8760
8946
|
*
|
|
8761
8947
|
* Event handlers retain their handler-key order within each source state and
|
|
8762
8948
|
* are followed by eventless and completion handlers. This function does not
|
|
8763
|
-
* execute resolvers. Every
|
|
8764
|
-
*
|
|
8765
|
-
*
|
|
8949
|
+
* execute resolvers. Every branch exposes its static selection. State updates
|
|
8950
|
+
* retain the updated owner in `selection.path` while leaving `target`
|
|
8951
|
+
* undefined because they do not change topology. `acceptance` reports whether
|
|
8952
|
+
* the resolver may decline the transition.
|
|
8766
8953
|
*
|
|
8767
8954
|
* @category getters
|
|
8768
8955
|
* @since 0.4.0
|
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
validateDeclaredTransitionTarget
|
|
48
48
|
} from "./planner.js"
|
|
49
49
|
import { decodeEmitSync, decodeEventSync, decodeInputSync, decodeStateValueSync } from "./protocol.js"
|
|
50
|
-
import { isInitialTarget, isNoTarget, isSnapshot, isTarget, TargetSnapshotTypeId } from "./topology.js"
|
|
50
|
+
import { isInitialTarget, isNoTarget, isSnapshot, isStateUpdate, isTarget, TargetSnapshotTypeId } from "./topology.js"
|
|
51
51
|
|
|
52
52
|
interface IndexedExecutionDescriptor {
|
|
53
53
|
readonly flat: boolean
|
|
@@ -548,7 +548,25 @@ const collectIndexedEvaluatedTransition = (
|
|
|
548
548
|
selection.context,
|
|
549
549
|
selection.transition.evaluate
|
|
550
550
|
)
|
|
551
|
-
const
|
|
551
|
+
const update = isStateUpdate(transitionResult.state)
|
|
552
|
+
? (() => {
|
|
553
|
+
const index = descriptor.indexByPath.get(transitionResult.state.path)
|
|
554
|
+
const node = index === undefined ? undefined : descriptor.nodes[index]
|
|
555
|
+
if (
|
|
556
|
+
index === undefined || node === undefined || state.active[index] !== 1 || node.schema === undefined ||
|
|
557
|
+
(node.type !== "compound" && node.type !== "parallel")
|
|
558
|
+
) {
|
|
559
|
+
throw new Error(
|
|
560
|
+
`Machine state update owner "${transitionResult.state.path}" must be an active valued compound or parallel state`
|
|
561
|
+
)
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
path: node.path,
|
|
565
|
+
value: decodeStateValueSync(machine, node, transitionResult.state.value)
|
|
566
|
+
}
|
|
567
|
+
})()
|
|
568
|
+
: undefined
|
|
569
|
+
const unresolvedTarget = update === undefined ? transitionResult.state : undefined
|
|
552
570
|
validateDeclaredTransitionTarget(
|
|
553
571
|
selection.sourcePath,
|
|
554
572
|
selection.trigger,
|
|
@@ -568,9 +586,14 @@ const collectIndexedEvaluatedTransition = (
|
|
|
568
586
|
throw new Error("Machine expected indexed transition target to be a snapshot or target builder result")
|
|
569
587
|
}
|
|
570
588
|
const next = target === undefined
|
|
571
|
-
? state
|
|
589
|
+
? update === undefined ? state : (() => {
|
|
590
|
+
const next = copyOwnedIndexedState(state)
|
|
591
|
+
next.values[descriptor.indexByPath.get(update.path)!] = update.value
|
|
592
|
+
return next
|
|
593
|
+
})()
|
|
572
594
|
: normalizeIndexedTargetStateSync(machine, descriptor, state, target as any, selection.leafIndex)
|
|
573
595
|
const changed = selection.transition.reenter || !hasSameIndexedActive(state, next)
|
|
596
|
+
const stabilize = changed || update !== undefined
|
|
574
597
|
if (!changed) {
|
|
575
598
|
return {
|
|
576
599
|
selection,
|
|
@@ -578,11 +601,13 @@ const collectIndexedEvaluatedTransition = (
|
|
|
578
601
|
branchKey: transitionResult.branchKey,
|
|
579
602
|
unresolvedTarget: unresolvedTarget as any,
|
|
580
603
|
target: target as any,
|
|
604
|
+
update,
|
|
581
605
|
next,
|
|
582
606
|
commands: [...transitionResult.commands, ...(initialResolution?.commands ?? [])],
|
|
583
607
|
raisedEvents: [...transitionResult.raisedEvents, ...(initialResolution?.raisedEvents ?? [])],
|
|
584
608
|
emittedEvents: [...transitionResult.emittedEvents, ...(initialResolution?.emittedEvents ?? [])],
|
|
585
609
|
changed: false,
|
|
610
|
+
stabilize,
|
|
586
611
|
exitPaths: [],
|
|
587
612
|
entryPaths: [],
|
|
588
613
|
choiceTransitions: initialResolution?.transitions ?? []
|
|
@@ -603,11 +628,13 @@ const collectIndexedEvaluatedTransition = (
|
|
|
603
628
|
branchKey: transitionResult.branchKey,
|
|
604
629
|
unresolvedTarget: unresolvedTarget as any,
|
|
605
630
|
target: target as any,
|
|
631
|
+
update,
|
|
606
632
|
next,
|
|
607
633
|
commands: [...transitionResult.commands, ...(initialResolution?.commands ?? [])],
|
|
608
634
|
raisedEvents: [...transitionResult.raisedEvents, ...(initialResolution?.raisedEvents ?? [])],
|
|
609
635
|
emittedEvents: [...transitionResult.emittedEvents, ...(initialResolution?.emittedEvents ?? [])],
|
|
610
636
|
changed: true,
|
|
637
|
+
stabilize,
|
|
611
638
|
exitPaths: getExitPaths(machine, activeConfigurationFromIndexedState(descriptor, state), boundary),
|
|
612
639
|
entryPaths: getEntryPaths(machine, activeConfigurationFromIndexedState(descriptor, next), boundary),
|
|
613
640
|
choiceTransitions: initialResolution?.transitions ?? []
|
|
@@ -663,6 +690,13 @@ const indexedMicrostep = (
|
|
|
663
690
|
if (transitions.length === 1) {
|
|
664
691
|
next = transitions[0]!.next
|
|
665
692
|
} else {
|
|
693
|
+
for (const transition of transitions) {
|
|
694
|
+
if (transition.update !== undefined) {
|
|
695
|
+
const values = next.values.slice()
|
|
696
|
+
values[descriptor.indexByPath.get(transition.update.path)!] = transition.update.value
|
|
697
|
+
next = { ...next, values }
|
|
698
|
+
}
|
|
699
|
+
}
|
|
666
700
|
const applicationOrder = [
|
|
667
701
|
...transitions.filter((transition) => !transition.changed),
|
|
668
702
|
...transitions.filter((transition) => transition.changed)
|
|
@@ -227,6 +227,15 @@ const decorateTransitionSelection = (selection: Topology.TargetSelection): Topol
|
|
|
227
227
|
reenter: () => makeDirectTransitionDescriptor(selection, undefined, { reenter: true })
|
|
228
228
|
})
|
|
229
229
|
|
|
230
|
+
const decorateStateUpdateSelection = (selection: Topology.TargetSelection): Topology.TargetSelection => {
|
|
231
|
+
const update = (
|
|
232
|
+
resolve: (context: any, enqueue: unknown) => unknown,
|
|
233
|
+
options?: unknown
|
|
234
|
+
) => makeDirectTransitionDescriptor(selection, resolve, options)
|
|
235
|
+
Object.assign(update, selection)
|
|
236
|
+
return Object.freeze(update) as unknown as Topology.TargetSelection
|
|
237
|
+
}
|
|
238
|
+
|
|
230
239
|
const noneTransitionSelection = decorateTransitionSelection(Topology.noneTargetSelection)
|
|
231
240
|
|
|
232
241
|
const makeInitialBuilderDescriptor = (
|
|
@@ -268,6 +277,7 @@ const decorateInitialSelectorNode = (node: unknown): unknown => {
|
|
|
268
277
|
|
|
269
278
|
const decorateTransitionSelectorNode = (node: unknown): unknown => {
|
|
270
279
|
if (Topology.isTargetSelection(node)) {
|
|
280
|
+
if (node.kind === "update") return decorateStateUpdateSelection(node)
|
|
271
281
|
return node === Topology.noneTargetSelection ? noneTransitionSelection : decorateTransitionSelection(node)
|
|
272
282
|
}
|
|
273
283
|
if (typeof node === "function") {
|
|
@@ -363,22 +373,29 @@ const makeSelectionValue = (
|
|
|
363
373
|
scope: Topology.TargetSelectionScope
|
|
364
374
|
): Topology.TargetSelection => Topology.makeTargetSelection(kind, path, scope)
|
|
365
375
|
|
|
376
|
+
const makeStateUpdateSelection = (
|
|
377
|
+
path: string,
|
|
378
|
+
scope: "local" | "branch"
|
|
379
|
+
): Topology.TargetSelection => Topology.makeTargetSelection("update", path, scope)
|
|
380
|
+
|
|
366
381
|
const addSelectionChildren = (
|
|
367
382
|
builder: Record<string, unknown>,
|
|
368
383
|
stateNodes: Machine.StateNodes,
|
|
369
384
|
parent: string,
|
|
370
|
-
scope: "local" | "branch"
|
|
385
|
+
scope: "local" | "branch",
|
|
386
|
+
source?: string
|
|
371
387
|
): void => {
|
|
372
388
|
for (const node of stateNodes.byPath.values()) {
|
|
373
389
|
if (node.parent !== parent || node.type === "history") continue
|
|
374
|
-
builder[node.key] = makeSelectionNode(stateNodes, node.path, scope)
|
|
390
|
+
builder[node.key] = makeSelectionNode(stateNodes, node.path, scope, source)
|
|
375
391
|
}
|
|
376
392
|
}
|
|
377
393
|
|
|
378
394
|
const makeSelectionNode = (
|
|
379
395
|
stateNodes: Machine.StateNodes,
|
|
380
396
|
path: string,
|
|
381
|
-
scope: Topology.TargetSelectionScope
|
|
397
|
+
scope: Topology.TargetSelectionScope,
|
|
398
|
+
source?: string
|
|
382
399
|
): unknown => {
|
|
383
400
|
const node = getTargetBuilderNode(stateNodes, path)
|
|
384
401
|
const kind: Topology.TargetSelectionKind = node.type === "choice" ? "choice" : "state"
|
|
@@ -389,7 +406,17 @@ const makeSelectionNode = (
|
|
|
389
406
|
enumerable: true
|
|
390
407
|
})
|
|
391
408
|
if (scope === "local" || scope === "branch") {
|
|
392
|
-
addSelectionChildren(method, stateNodes, path, scope)
|
|
409
|
+
addSelectionChildren(method, stateNodes, path, scope, source)
|
|
410
|
+
}
|
|
411
|
+
if (
|
|
412
|
+
scope === "branch" && source !== undefined && node.schema !== undefined &&
|
|
413
|
+
(source === path || source.startsWith(`${path}.`)) &&
|
|
414
|
+
getTargetBuilderNode(stateNodes, source).type !== "choice"
|
|
415
|
+
) {
|
|
416
|
+
Object.defineProperty(method, "update", {
|
|
417
|
+
value: makeStateUpdateSelection(path, "branch"),
|
|
418
|
+
enumerable: true
|
|
419
|
+
})
|
|
393
420
|
}
|
|
394
421
|
}
|
|
395
422
|
return method
|
|
@@ -424,13 +451,16 @@ const makeTargetSelector = (
|
|
|
424
451
|
}
|
|
425
452
|
const branch: Record<string, unknown> = {}
|
|
426
453
|
const root = getTargetBuilderNode(stateNodes, source.split(".")[0]!)
|
|
427
|
-
branch[root.key] = makeSelectionNode(stateNodes, root.path, "branch")
|
|
454
|
+
branch[root.key] = makeSelectionNode(stateNodes, root.path, "branch", source)
|
|
428
455
|
const local: Record<string, unknown> = {}
|
|
429
456
|
const localScope = getLocalTargetScope(stateNodes, source)
|
|
430
457
|
if (localScope !== undefined) {
|
|
431
458
|
const localScopeNode = getTargetBuilderNode(stateNodes, localScope)
|
|
432
459
|
if (localScopeNode.schema !== undefined) {
|
|
433
460
|
local.with = makeSelectionValue("state", localScope, "local")
|
|
461
|
+
if (getTargetBuilderNode(stateNodes, source).type !== "choice") {
|
|
462
|
+
local.update = makeStateUpdateSelection(localScope, "local")
|
|
463
|
+
}
|
|
434
464
|
}
|
|
435
465
|
addSelectionChildren(local, stateNodes, localScope, "local")
|
|
436
466
|
}
|
|
@@ -469,6 +499,13 @@ const getSelectionBuilder = (
|
|
|
469
499
|
source: string
|
|
470
500
|
): unknown => {
|
|
471
501
|
if (selection.kind === "none") return target.none
|
|
502
|
+
if (selection.kind === "update") {
|
|
503
|
+
return withFrom(
|
|
504
|
+
(value: unknown) => Topology.makeStateUpdate(selection.path!, value),
|
|
505
|
+
"leaf",
|
|
506
|
+
true
|
|
507
|
+
)
|
|
508
|
+
}
|
|
472
509
|
let builder: any
|
|
473
510
|
let parts = selection.path!.split(".")
|
|
474
511
|
if (selection.kind === "history") {
|
|
@@ -514,6 +551,12 @@ const validateResolvedSelection = (
|
|
|
514
551
|
}
|
|
515
552
|
return
|
|
516
553
|
}
|
|
554
|
+
if (selection.kind === "update") {
|
|
555
|
+
if (!Topology.isStateUpdate(result) || result.path !== selection.path) {
|
|
556
|
+
throw new Error(`Machine state update for "${selection.path}" must return its selected update builder`)
|
|
557
|
+
}
|
|
558
|
+
return
|
|
559
|
+
}
|
|
517
560
|
if (result === undefined) return
|
|
518
561
|
const resultPath = typeof result === "object" && result !== null && hasProperty(result, "path") &&
|
|
519
562
|
typeof result.path === "string"
|
|
@@ -556,6 +599,9 @@ const runCapturedBranch = (
|
|
|
556
599
|
return resolved === undefined ? constructSelectedTarget(selectedTarget) : resolved
|
|
557
600
|
}
|
|
558
601
|
|
|
602
|
+
const topologyTargetPath = (selection: Topology.TargetSelection): string | undefined =>
|
|
603
|
+
selection.kind === "update" ? undefined : selection.path
|
|
604
|
+
|
|
559
605
|
const isArrayIndexKey = (key: string): boolean => {
|
|
560
606
|
const index = Number(key)
|
|
561
607
|
return Number.isInteger(index) && index >= 0 && index < 0xffff_ffff && String(index) === key
|
|
@@ -738,7 +784,9 @@ const captureTransition = (
|
|
|
738
784
|
declinable,
|
|
739
785
|
targets: [
|
|
740
786
|
...new Set(
|
|
741
|
-
branches.flatMap((branch) =>
|
|
787
|
+
branches.flatMap((branch) =>
|
|
788
|
+
topologyTargetPath(branch.selection) === undefined ? [] : [branch.selection.path!]
|
|
789
|
+
)
|
|
742
790
|
)
|
|
743
791
|
],
|
|
744
792
|
branches: branches.map((branch) =>
|
|
@@ -746,7 +794,7 @@ const captureTransition = (
|
|
|
746
794
|
type: "branch" as const,
|
|
747
795
|
key: branch.key,
|
|
748
796
|
title: branch.title,
|
|
749
|
-
target: branch.selection
|
|
797
|
+
target: topologyTargetPath(branch.selection),
|
|
750
798
|
selection: transitionTargetSelection(branch.selection)
|
|
751
799
|
})
|
|
752
800
|
),
|
|
@@ -763,10 +811,10 @@ const captureTransition = (
|
|
|
763
811
|
return {
|
|
764
812
|
reenter,
|
|
765
813
|
declinable,
|
|
766
|
-
targets: branch.selection
|
|
814
|
+
targets: topologyTargetPath(branch.selection) === undefined ? [] : [branch.selection.path!],
|
|
767
815
|
branches: [{
|
|
768
816
|
type: "direct" as const,
|
|
769
|
-
target: branch.selection
|
|
817
|
+
target: topologyTargetPath(branch.selection),
|
|
770
818
|
selection: transitionTargetSelection(branch.selection)
|
|
771
819
|
}],
|
|
772
820
|
evaluate,
|