@typeonce/effect-machine 0.15.0 → 0.16.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 +31 -3
- package/dist/Machine.d.ts +258 -88
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +33 -33
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/activities.d.ts +2 -0
- package/dist/internal/machine/activities.d.ts.map +1 -1
- package/dist/internal/machine/activities.js +4 -0
- package/dist/internal/machine/activities.js.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +6 -1
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/invocation.d.ts +1 -1
- package/dist/internal/machine/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +25 -3
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/invocationEvent.d.ts +8 -0
- package/dist/internal/machine/invocationEvent.d.ts.map +1 -1
- package/dist/internal/machine/invocationEvent.js +8 -0
- package/dist/internal/machine/invocationEvent.js.map +1 -1
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +119 -48
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +7 -0
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +20 -11
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/runtime.d.ts +1 -0
- package/dist/internal/machine/runtime.d.ts.map +1 -1
- package/dist/internal/machine/runtime.js +25 -16
- package/dist/internal/machine/runtime.js.map +1 -1
- package/dist/internal/machine/topology.d.ts +11 -0
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +17 -6
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/testing/machine/transitionCoverage.d.ts.map +1 -1
- package/dist/internal/testing/machine/transitionCoverage.js +6 -2
- package/dist/internal/testing/machine/transitionCoverage.js.map +1 -1
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +6 -0
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +2 -1
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js.map +1 -1
- package/docs/agent-guide.md +63 -37
- package/package.json +1 -1
- package/src/Machine.ts +592 -159
- package/src/internal/machine/activities.ts +7 -0
- package/src/internal/machine/executionPlan.ts +6 -1
- package/src/internal/machine/invocation.ts +39 -4
- package/src/internal/machine/invocationEvent.ts +16 -0
- package/src/internal/machine/machine.ts +160 -47
- package/src/internal/machine/planner.ts +17 -3
- package/src/internal/machine/runtime.ts +61 -25
- package/src/internal/machine/topology.ts +31 -2
- package/src/internal/testing/machine/transitionCoverage.ts +6 -2
- package/src/internal/testing/machine/verification.ts +11 -0
- package/src/testing/MachineTest.ts +2 -0
|
@@ -22,6 +22,9 @@ export type StaticActivityMetadata =
|
|
|
22
22
|
readonly type: "timer"
|
|
23
23
|
readonly duration: string | "dynamic"
|
|
24
24
|
}
|
|
25
|
+
| {
|
|
26
|
+
readonly type: "stream"
|
|
27
|
+
}
|
|
25
28
|
| {
|
|
26
29
|
readonly type: "machine"
|
|
27
30
|
readonly child: {
|
|
@@ -94,6 +97,10 @@ const appendStaticDefinition = (
|
|
|
94
97
|
})
|
|
95
98
|
return
|
|
96
99
|
}
|
|
100
|
+
if (Reflect.has(descriptor, "stream")) {
|
|
101
|
+
definitions.push({ source, id, type: "stream" })
|
|
102
|
+
return
|
|
103
|
+
}
|
|
97
104
|
if (Reflect.has(descriptor, "logic")) {
|
|
98
105
|
definitions.push({ source, id, type: "process" })
|
|
99
106
|
}
|
|
@@ -445,11 +445,12 @@ const collectIndexedTransition = (
|
|
|
445
445
|
}
|
|
446
446
|
}
|
|
447
447
|
const evaluated = evaluate === undefined
|
|
448
|
-
? { result: transition(context, enqueue), branchIndex: 0 }
|
|
448
|
+
? { result: transition(context, enqueue), branchIndex: 0, branchKey: undefined }
|
|
449
449
|
: evaluate(context, enqueue)
|
|
450
450
|
return {
|
|
451
451
|
state: isNoTarget(evaluated.result) ? undefined : evaluated.result,
|
|
452
452
|
branchIndex: evaluated.branchIndex,
|
|
453
|
+
branchKey: evaluated.branchKey,
|
|
453
454
|
commands: commands ?? emptyExecutionValues,
|
|
454
455
|
raisedEvents: raisedEvents ?? emptyExecutionValues,
|
|
455
456
|
emittedEvents: emittedEvents ?? emptyExecutionValues
|
|
@@ -569,6 +570,7 @@ const collectIndexedEvaluatedTransition = (
|
|
|
569
570
|
return {
|
|
570
571
|
selection,
|
|
571
572
|
branchIndex: transitionResult.branchIndex,
|
|
573
|
+
branchKey: transitionResult.branchKey,
|
|
572
574
|
unresolvedTarget: unresolvedTarget as any,
|
|
573
575
|
target: target as any,
|
|
574
576
|
next,
|
|
@@ -593,6 +595,7 @@ const collectIndexedEvaluatedTransition = (
|
|
|
593
595
|
return {
|
|
594
596
|
selection,
|
|
595
597
|
branchIndex: transitionResult.branchIndex,
|
|
598
|
+
branchKey: transitionResult.branchKey,
|
|
596
599
|
unresolvedTarget: unresolvedTarget as any,
|
|
597
600
|
target: target as any,
|
|
598
601
|
next,
|
|
@@ -619,6 +622,7 @@ const indexedMicrostep = (
|
|
|
619
622
|
trigger: transition.selection.trigger,
|
|
620
623
|
reenter: transition.selection.transition.reenter,
|
|
621
624
|
branchIndex: transition.branchIndex,
|
|
625
|
+
branchKey: transition.branchKey,
|
|
622
626
|
target: transition.unresolvedTarget === undefined ? undefined : getTargetNodePath(transition.unresolvedTarget),
|
|
623
627
|
resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target)
|
|
624
628
|
})
|
|
@@ -804,6 +808,7 @@ const planIndexedFlatState = (
|
|
|
804
808
|
trigger: { type: "event", event: event._tag },
|
|
805
809
|
reenter: transition.reenter,
|
|
806
810
|
branchIndex: transitionResult.branchIndex,
|
|
811
|
+
branchKey: transitionResult.branchKey,
|
|
807
812
|
target: target === undefined ? undefined : getTargetNodePath(target as any),
|
|
808
813
|
resolvedTarget: target === undefined ? undefined : getTargetNodePath(target as any)
|
|
809
814
|
}]
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
import * as Cause from "effect/Cause"
|
|
8
8
|
import * as Effect from "effect/Effect"
|
|
9
|
+
import * as Stream from "effect/Stream"
|
|
9
10
|
import type { ChildMachine, Inspection, Logic, Machine } from "../../Machine.js"
|
|
10
11
|
import * as Configuration from "./configuration.js"
|
|
11
12
|
import { InfiniteTransitionError, MachineSchemaDecodeError, StoppedError } from "./errors.js"
|
|
12
13
|
import * as InvocationEvent from "./invocationEvent.js"
|
|
13
14
|
import * as Planner from "./planner.js"
|
|
14
|
-
import
|
|
15
|
+
import * as Runtime from "./runtime.js"
|
|
15
16
|
import { ChildMachineLogicTypeId } from "./symbols.js"
|
|
16
17
|
|
|
17
18
|
/** @internal */
|
|
@@ -37,12 +38,35 @@ const oneShot = (effect: Effect.Effect<any, any, any>): Logic<void, never, any,
|
|
|
37
38
|
run: () => effect
|
|
38
39
|
})
|
|
39
40
|
|
|
41
|
+
const streamLogic = (
|
|
42
|
+
stream: Stream.Stream<any, any, any>,
|
|
43
|
+
path: string,
|
|
44
|
+
id: string
|
|
45
|
+
): Runtime.ProcessLogic<void, never, any, any, void> => ({
|
|
46
|
+
initial: () => Effect.void,
|
|
47
|
+
run: ({ parent }) => {
|
|
48
|
+
const send = parent?.[Runtime.acknowledgedSend]
|
|
49
|
+
if (send === undefined) {
|
|
50
|
+
return Effect.die(new Error("Stream invocation requires an acknowledged parent machine"))
|
|
51
|
+
}
|
|
52
|
+
return Stream.runForEach(
|
|
53
|
+
stream,
|
|
54
|
+
(element) =>
|
|
55
|
+
send(InvocationEvent.element(path, id, element)).pipe(
|
|
56
|
+
Effect.asVoid,
|
|
57
|
+
Effect.catchCause(() => Effect.interrupt)
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
40
63
|
const resolveValue = (value: unknown, context: Machine.InvokeContext<any, any, any, any>): unknown =>
|
|
41
64
|
typeof value === "function" ? value(context) : value
|
|
42
65
|
|
|
43
66
|
const resolveOne = (
|
|
44
67
|
raw: Record<PropertyKey, any>,
|
|
45
|
-
context: Machine.InvokeContext<any, any, any, any
|
|
68
|
+
context: Machine.InvokeContext<any, any, any, any>,
|
|
69
|
+
path: string
|
|
46
70
|
): AnyConfig => {
|
|
47
71
|
if ("effect" in raw) {
|
|
48
72
|
return {
|
|
@@ -78,6 +102,17 @@ const resolveOne = (
|
|
|
78
102
|
activityKind: "Timer"
|
|
79
103
|
}
|
|
80
104
|
}
|
|
105
|
+
if ("stream" in raw) {
|
|
106
|
+
const id = String(raw.id)
|
|
107
|
+
return {
|
|
108
|
+
id,
|
|
109
|
+
src: () =>
|
|
110
|
+
streamLogic(raw.stream(context), path, id) as unknown as Runtime.ProcessLogic<any, any, any, any, any, any>,
|
|
111
|
+
onDone: raw.onDone,
|
|
112
|
+
onFailure: raw.onFailure,
|
|
113
|
+
activityKind: "Stream"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
81
116
|
if ("logic" in raw) {
|
|
82
117
|
return {
|
|
83
118
|
id: String(raw.id),
|
|
@@ -103,7 +138,7 @@ const resolveOne = (
|
|
|
103
138
|
onSnapshot: raw.onSnapshot
|
|
104
139
|
}
|
|
105
140
|
}
|
|
106
|
-
throw new Error("Machine invoke must define exactly one of effect, after, logic, or child")
|
|
141
|
+
throw new Error("Machine invoke must define exactly one of effect, stream, after, logic, or child")
|
|
107
142
|
}
|
|
108
143
|
|
|
109
144
|
/** @internal */
|
|
@@ -269,7 +304,7 @@ export const startAll = (
|
|
|
269
304
|
return InvocationEvent.definitions(Configuration.getStateConfigByPath(machine, path)?.invoke).map((definition) =>
|
|
270
305
|
"child" in definition && !("input" in definition)
|
|
271
306
|
? startStaticChild(scope, ownedChildren, path, definition)
|
|
272
|
-
: start(scope, ownedChildren, path, resolveOne(definition, context))
|
|
307
|
+
: start(scope, ownedChildren, path, resolveOne(definition, context, path))
|
|
273
308
|
)
|
|
274
309
|
})
|
|
275
310
|
return effects.length === 0 ? undefined : runSequentialDiscard(effects)
|
|
@@ -10,6 +10,13 @@ export const InvocationEventTypeId: unique symbol = Symbol("effect/Machine/Invoc
|
|
|
10
10
|
|
|
11
11
|
/** @internal */
|
|
12
12
|
export type InvocationEvent =
|
|
13
|
+
| {
|
|
14
|
+
readonly [InvocationEventTypeId]: true
|
|
15
|
+
readonly path: string
|
|
16
|
+
readonly id: string
|
|
17
|
+
readonly type: "element"
|
|
18
|
+
readonly element: unknown
|
|
19
|
+
}
|
|
13
20
|
| {
|
|
14
21
|
readonly [InvocationEventTypeId]: true
|
|
15
22
|
readonly path: string
|
|
@@ -41,6 +48,15 @@ export const done = (path: string, id: string, output: unknown): InvocationEvent
|
|
|
41
48
|
output
|
|
42
49
|
})
|
|
43
50
|
|
|
51
|
+
/** @internal */
|
|
52
|
+
export const element = (path: string, id: string, value: unknown): InvocationEvent => ({
|
|
53
|
+
[InvocationEventTypeId]: true,
|
|
54
|
+
path,
|
|
55
|
+
id,
|
|
56
|
+
type: "element",
|
|
57
|
+
element: value
|
|
58
|
+
})
|
|
59
|
+
|
|
44
60
|
/** @internal */
|
|
45
61
|
export const failure = (path: string, id: string, error: unknown): InvocationEvent => ({
|
|
46
62
|
[InvocationEventTypeId]: true,
|
|
@@ -137,8 +137,6 @@ const makeWithHandlers = (
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
type DefinitionBranch = {
|
|
140
|
-
readonly title?: string
|
|
141
|
-
readonly when?: (context: any) => Option.Option<unknown>
|
|
142
140
|
readonly target: (selector: unknown) => unknown
|
|
143
141
|
readonly resolve?: (context: any, enqueue: unknown) => unknown
|
|
144
142
|
}
|
|
@@ -147,6 +145,17 @@ type CapturedBranch = DefinitionBranch & {
|
|
|
147
145
|
readonly selection: Topology.TargetSelection
|
|
148
146
|
}
|
|
149
147
|
|
|
148
|
+
type BranchDeclaration = {
|
|
149
|
+
readonly title?: string
|
|
150
|
+
readonly target: unknown
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
type CapturedNamedBranch = {
|
|
154
|
+
readonly key: string
|
|
155
|
+
readonly title: string
|
|
156
|
+
readonly selection: Topology.TargetSelection
|
|
157
|
+
}
|
|
158
|
+
|
|
150
159
|
const transitionTargetSelection = (
|
|
151
160
|
selection: Topology.TargetSelection
|
|
152
161
|
): Machine.TransitionTargetSelection =>
|
|
@@ -336,20 +345,134 @@ const runCapturedBranch = (
|
|
|
336
345
|
context: Record<string, any>,
|
|
337
346
|
enqueue: unknown,
|
|
338
347
|
stateNodes: Machine.StateNodes,
|
|
339
|
-
source: string
|
|
340
|
-
match?: { readonly value: unknown }
|
|
348
|
+
source: string
|
|
341
349
|
): unknown => {
|
|
342
350
|
const selectedTarget = getSelectionBuilder(context.target, branch.selection, stateNodes, source)
|
|
343
351
|
if (branch.resolve === undefined) return constructSelectedTarget(selectedTarget)
|
|
344
352
|
const resolverContext = { ...context }
|
|
345
353
|
if (branch.selection.kind === "none") delete resolverContext.target
|
|
346
354
|
else resolverContext.target = selectedTarget
|
|
347
|
-
if (match !== undefined) resolverContext.match = match.value
|
|
348
355
|
const resolved = branch.resolve(resolverContext, enqueue)
|
|
349
356
|
validateResolvedSelection(resolved, branch.selection, stateNodes)
|
|
350
357
|
return resolved === undefined ? constructSelectedTarget(selectedTarget) : resolved
|
|
351
358
|
}
|
|
352
359
|
|
|
360
|
+
const isArrayIndexKey = (key: string): boolean => {
|
|
361
|
+
const index = Number(key)
|
|
362
|
+
return Number.isInteger(index) && index >= 0 && index < 0xffff_ffff && String(index) === key
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const captureNamedBranches = (
|
|
366
|
+
declarations: unknown,
|
|
367
|
+
path: string,
|
|
368
|
+
trigger: PropertyKey
|
|
369
|
+
): ReadonlyArray<CapturedNamedBranch> => {
|
|
370
|
+
if (typeof declarations !== "object" || declarations === null || Array.isArray(declarations)) {
|
|
371
|
+
throw new Error(`Machine branching transition for state "${path}" on "${String(trigger)}" requires a branch record`)
|
|
372
|
+
}
|
|
373
|
+
if (Object.getOwnPropertySymbols(declarations).length > 0) {
|
|
374
|
+
throw new Error(`Machine branching transition for state "${path}" on "${String(trigger)}" cannot use symbol keys`)
|
|
375
|
+
}
|
|
376
|
+
const keys = Object.keys(declarations)
|
|
377
|
+
if (keys.length === 0) {
|
|
378
|
+
throw new Error(`Machine branching transition for state "${path}" on "${String(trigger)}" requires a branch`)
|
|
379
|
+
}
|
|
380
|
+
return Object.freeze(keys.map((key) => {
|
|
381
|
+
if (key.length === 0 || isArrayIndexKey(key)) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
`Machine branching transition for state "${path}" on "${String(trigger)}" requires non-index string branch keys`
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
const declaration = (declarations as Record<string, unknown>)[key]
|
|
387
|
+
if (typeof declaration !== "object" || declaration === null || !hasProperty(declaration, "target")) {
|
|
388
|
+
throw new Error(`Machine transition branch "${key}" requires a target selection`)
|
|
389
|
+
}
|
|
390
|
+
const { target, title } = declaration as BranchDeclaration
|
|
391
|
+
if (!Topology.isTargetSelection(target)) {
|
|
392
|
+
throw new Error(`Machine transition branch "${key}" must select exactly one target`)
|
|
393
|
+
}
|
|
394
|
+
if (title !== undefined && (typeof title !== "string" || title.length === 0)) {
|
|
395
|
+
throw new Error(`Machine transition branch "${key}" title must be a non-empty string`)
|
|
396
|
+
}
|
|
397
|
+
return Object.freeze({ key, title: title ?? key, selection: target })
|
|
398
|
+
}))
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const wrapSelectedBranchBuilder = (
|
|
402
|
+
builder: unknown,
|
|
403
|
+
owner: object,
|
|
404
|
+
branchIndex: number,
|
|
405
|
+
branchKey: string
|
|
406
|
+
): unknown => {
|
|
407
|
+
if (typeof builder === "function") {
|
|
408
|
+
const wrapped = (...args: ReadonlyArray<unknown>) =>
|
|
409
|
+
Topology.makeSelectedBranch(owner, branchIndex, branchKey, builder(...args))
|
|
410
|
+
for (const property of Reflect.ownKeys(builder)) {
|
|
411
|
+
if (
|
|
412
|
+
property === "length" || property === "name" || property === "prototype" || property === "caller" ||
|
|
413
|
+
property === "arguments"
|
|
414
|
+
) continue
|
|
415
|
+
const descriptor = Object.getOwnPropertyDescriptor(builder, property)
|
|
416
|
+
if (descriptor === undefined) continue
|
|
417
|
+
if ("value" in descriptor && typeof descriptor.value === "function") {
|
|
418
|
+
descriptor.value = wrapSelectedBranchBuilder(descriptor.value, owner, branchIndex, branchKey)
|
|
419
|
+
}
|
|
420
|
+
Object.defineProperty(wrapped, property, descriptor)
|
|
421
|
+
}
|
|
422
|
+
return wrapped
|
|
423
|
+
}
|
|
424
|
+
if (typeof builder === "object" && builder !== null) {
|
|
425
|
+
const wrapped: Record<PropertyKey, unknown> = {}
|
|
426
|
+
for (const property of Reflect.ownKeys(builder)) {
|
|
427
|
+
const descriptor = Object.getOwnPropertyDescriptor(builder, property)
|
|
428
|
+
if (descriptor === undefined) continue
|
|
429
|
+
if ("value" in descriptor && typeof descriptor.value === "function") {
|
|
430
|
+
descriptor.value = wrapSelectedBranchBuilder(descriptor.value, owner, branchIndex, branchKey)
|
|
431
|
+
}
|
|
432
|
+
Object.defineProperty(wrapped, property, descriptor)
|
|
433
|
+
}
|
|
434
|
+
return wrapped
|
|
435
|
+
}
|
|
436
|
+
throw new Error(`Machine could not construct transition branch "${branchKey}"`)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const makeBranchSelectors = (
|
|
440
|
+
context: Record<string, any>,
|
|
441
|
+
branches: ReadonlyArray<CapturedNamedBranch>,
|
|
442
|
+
owner: object,
|
|
443
|
+
stateNodes: Machine.StateNodes,
|
|
444
|
+
source: string
|
|
445
|
+
): Readonly<Record<string, unknown>> => {
|
|
446
|
+
const select: Record<string, unknown> = Object.create(null)
|
|
447
|
+
for (let branchIndex = 0; branchIndex < branches.length; branchIndex++) {
|
|
448
|
+
const branch = branches[branchIndex]!
|
|
449
|
+
select[branch.key] = wrapSelectedBranchBuilder(
|
|
450
|
+
getSelectionBuilder(context.target, branch.selection, stateNodes, source),
|
|
451
|
+
owner,
|
|
452
|
+
branchIndex,
|
|
453
|
+
branch.key
|
|
454
|
+
)
|
|
455
|
+
}
|
|
456
|
+
return Object.freeze(select)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const validateSelectedBranchResult = (
|
|
460
|
+
result: unknown,
|
|
461
|
+
selection: Topology.TargetSelection,
|
|
462
|
+
stateNodes: Machine.StateNodes
|
|
463
|
+
): void => {
|
|
464
|
+
if (selection.kind === "none") {
|
|
465
|
+
if (!Topology.isNoTarget(result)) {
|
|
466
|
+
throw new Error("Machine targetless branch must return its selected targetless builder")
|
|
467
|
+
}
|
|
468
|
+
return
|
|
469
|
+
}
|
|
470
|
+
if (result === undefined) {
|
|
471
|
+
throw new Error(`Machine transition branch selected "${selection.path}" without constructing its target`)
|
|
472
|
+
}
|
|
473
|
+
validateResolvedSelection(result, selection, stateNodes)
|
|
474
|
+
}
|
|
475
|
+
|
|
353
476
|
const captureTransition = (
|
|
354
477
|
transition: unknown,
|
|
355
478
|
stateNodes: Machine.StateNodes,
|
|
@@ -362,64 +485,53 @@ const captureTransition = (
|
|
|
362
485
|
const definition = transition as Record<PropertyKey, unknown>
|
|
363
486
|
const selector = makeTargetSelector(stateNodes, path)
|
|
364
487
|
const reenter = definition.reenter === true
|
|
365
|
-
if (
|
|
366
|
-
const
|
|
367
|
-
if (
|
|
488
|
+
if (hasProperty(definition, "branches")) {
|
|
489
|
+
const branching = definition as { readonly branches: unknown; readonly resolve?: unknown }
|
|
490
|
+
if (typeof branching.branches !== "function" || typeof branching.resolve !== "function") {
|
|
368
491
|
throw new Error(
|
|
369
|
-
`Machine
|
|
492
|
+
`Machine branching transition for state "${path}" on "${String(trigger)}" requires branches and resolve`
|
|
370
493
|
)
|
|
371
494
|
}
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
495
|
+
const resolve = branching.resolve
|
|
496
|
+
const branches = captureNamedBranches(branching.branches(selector), path, trigger)
|
|
497
|
+
const owner = Object.freeze({})
|
|
498
|
+
const evaluate = (context: Record<string, any>, enqueue: unknown) => {
|
|
499
|
+
const resolverContext = { ...context }
|
|
500
|
+
delete resolverContext.target
|
|
501
|
+
resolverContext.select = makeBranchSelectors(context, branches, owner, stateNodes, path)
|
|
502
|
+
const selected = resolve(resolverContext, enqueue)
|
|
503
|
+
if (!Topology.isSelectedBranch(selected) || selected.owner !== owner) {
|
|
375
504
|
throw new Error(
|
|
376
|
-
`Machine
|
|
505
|
+
`Machine branching transition for state "${path}" on "${String(trigger)}" must select one declared branch`
|
|
377
506
|
)
|
|
378
507
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const evaluate = (context: Record<string, any>, enqueue: unknown) => {
|
|
383
|
-
const predicateContext = { ...context }
|
|
384
|
-
delete predicateContext.target
|
|
385
|
-
for (let branchIndex = 0; branchIndex < cases.length; branchIndex++) {
|
|
386
|
-
const branch = cases[branchIndex]!
|
|
387
|
-
const result = branch.when!(predicateContext)
|
|
388
|
-
if (!Option.isOption(result)) {
|
|
389
|
-
throw new Error(`Machine conditional transition case "${branch.title}" must return Option`)
|
|
390
|
-
}
|
|
391
|
-
if (Option.isSome(result)) {
|
|
392
|
-
return {
|
|
393
|
-
result: runCapturedBranch(branch, context, enqueue, stateNodes, path, { value: result.value }),
|
|
394
|
-
branchIndex
|
|
395
|
-
}
|
|
396
|
-
}
|
|
508
|
+
const branch = branches[selected.branchIndex]
|
|
509
|
+
if (branch === undefined || selected.branchKey !== branch.key) {
|
|
510
|
+
throw new Error(`Machine branching transition returned invalid branch evidence`)
|
|
397
511
|
}
|
|
512
|
+
validateSelectedBranchResult(selected.result, branch.selection, stateNodes)
|
|
398
513
|
return {
|
|
399
|
-
result:
|
|
400
|
-
branchIndex:
|
|
514
|
+
result: selected.result,
|
|
515
|
+
branchIndex: selected.branchIndex,
|
|
516
|
+
branchKey: selected.branchKey
|
|
401
517
|
}
|
|
402
518
|
}
|
|
403
519
|
return {
|
|
404
520
|
reenter,
|
|
405
521
|
targets: [
|
|
406
522
|
...new Set(
|
|
407
|
-
|
|
523
|
+
branches.flatMap((branch) => branch.selection.path === undefined ? [] : [branch.selection.path])
|
|
408
524
|
)
|
|
409
525
|
],
|
|
410
|
-
branches:
|
|
411
|
-
|
|
412
|
-
type: "
|
|
413
|
-
|
|
526
|
+
branches: branches.map((branch) =>
|
|
527
|
+
Object.freeze({
|
|
528
|
+
type: "branch" as const,
|
|
529
|
+
key: branch.key,
|
|
530
|
+
title: branch.title,
|
|
414
531
|
target: branch.selection.path,
|
|
415
532
|
selection: transitionTargetSelection(branch.selection)
|
|
416
|
-
})
|
|
417
|
-
|
|
418
|
-
type: "otherwise" as const,
|
|
419
|
-
target: otherwise.selection.path,
|
|
420
|
-
selection: transitionTargetSelection(otherwise.selection)
|
|
421
|
-
}
|
|
422
|
-
],
|
|
533
|
+
})
|
|
534
|
+
),
|
|
423
535
|
evaluate,
|
|
424
536
|
transition: (context: Record<string, any>, enqueue: unknown) => evaluate(context, enqueue).result
|
|
425
537
|
}
|
|
@@ -427,7 +539,8 @@ const captureTransition = (
|
|
|
427
539
|
const branch = captureDefinitionBranch(transition, selector, path, trigger)
|
|
428
540
|
const evaluate = (context: Record<string, any>, enqueue: unknown) => ({
|
|
429
541
|
result: runCapturedBranch(branch, context, enqueue, stateNodes, path),
|
|
430
|
-
branchIndex: 0
|
|
542
|
+
branchIndex: 0,
|
|
543
|
+
branchKey: undefined
|
|
431
544
|
})
|
|
432
545
|
return {
|
|
433
546
|
reenter,
|
|
@@ -465,7 +578,7 @@ const captureInvokeDefinition = (
|
|
|
465
578
|
if (Array.isArray(invoke)) return invoke.map((item) => captureInvokeDefinition(item, stateNodes, path))
|
|
466
579
|
if (typeof invoke !== "object" || invoke === null) return invoke
|
|
467
580
|
const captured = { ...(invoke as Record<PropertyKey, unknown>) }
|
|
468
|
-
for (const key of ["onDone", "onFailure", "onSnapshot"] as const) {
|
|
581
|
+
for (const key of ["onElement", "onDone", "onFailure", "onSnapshot"] as const) {
|
|
469
582
|
if (captured[key] !== undefined) {
|
|
470
583
|
captured[key] = captureTransition(captured[key], stateNodes, path, key)
|
|
471
584
|
}
|
|
@@ -71,6 +71,7 @@ export type MicrostepPlan<State, Event, E, R> = {
|
|
|
71
71
|
readonly trigger: Machine.TransitionTrigger
|
|
72
72
|
readonly reenter: boolean
|
|
73
73
|
readonly branchIndex: number
|
|
74
|
+
readonly branchKey: string | undefined
|
|
74
75
|
readonly target: string | undefined
|
|
75
76
|
readonly resolvedTarget: string | undefined
|
|
76
77
|
}>
|
|
@@ -108,6 +109,7 @@ export type TransitionHandler<States extends Machine.StateSchemas, E, R, Context
|
|
|
108
109
|
type TransitionEvaluation<States extends Machine.StateSchemas, E, R> = {
|
|
109
110
|
readonly result: Machine.HandlerResult<States, E, R>
|
|
110
111
|
readonly branchIndex: number
|
|
112
|
+
readonly branchKey: string | undefined
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
type TransitionEvaluator<States extends Machine.StateSchemas, E, R, Context> = (
|
|
@@ -214,11 +216,12 @@ const collectTransition = <
|
|
|
214
216
|
) => {
|
|
215
217
|
const collected = makeCollector<Event>(machine)
|
|
216
218
|
const evaluated = evaluate === undefined
|
|
217
|
-
? { result: transition(context, collected.enqueue), branchIndex: 0 }
|
|
219
|
+
? { result: transition(context, collected.enqueue), branchIndex: 0, branchKey: undefined }
|
|
218
220
|
: evaluate(context, collected.enqueue)
|
|
219
221
|
return {
|
|
220
222
|
state: isNoTarget(evaluated.result) ? undefined : evaluated.result,
|
|
221
223
|
branchIndex: evaluated.branchIndex,
|
|
224
|
+
branchKey: evaluated.branchKey,
|
|
222
225
|
commands: collected.commands,
|
|
223
226
|
raisedEvents: collected.raisedEvents,
|
|
224
227
|
emittedEvents: collected.emittedEvents
|
|
@@ -523,6 +526,7 @@ export type SelectedTransition<States extends Machine.StateSchemas, E, R, Contex
|
|
|
523
526
|
export type EvaluatedTransition<States extends Machine.StateSchemas, Event, E, R, Context> = {
|
|
524
527
|
readonly selection: SelectedTransition<States, E, R, Context>
|
|
525
528
|
readonly branchIndex: number
|
|
529
|
+
readonly branchKey: string | undefined
|
|
526
530
|
readonly unresolvedTarget:
|
|
527
531
|
| Machine.Snapshot<States>
|
|
528
532
|
| Machine.Target<States, Machine.StateIdentifier<States>>
|
|
@@ -544,6 +548,7 @@ export type EvaluatedTransition<States extends Machine.StateSchemas, Event, E, R
|
|
|
544
548
|
readonly trigger: Machine.TransitionTrigger
|
|
545
549
|
readonly reenter: false
|
|
546
550
|
readonly branchIndex: number
|
|
551
|
+
readonly branchKey: string | undefined
|
|
547
552
|
readonly target: string
|
|
548
553
|
readonly resolvedTarget: string
|
|
549
554
|
}>
|
|
@@ -952,7 +957,9 @@ const selectInvocationTransition = <
|
|
|
952
957
|
return String(id) === event.id
|
|
953
958
|
})
|
|
954
959
|
if (invoke === undefined) return []
|
|
955
|
-
const handler = event.type === "
|
|
960
|
+
const handler = event.type === "element"
|
|
961
|
+
? invoke.onElement
|
|
962
|
+
: event.type === "done"
|
|
956
963
|
? invoke.onDone
|
|
957
964
|
: event.type === "failure"
|
|
958
965
|
? invoke.onFailure
|
|
@@ -968,7 +975,9 @@ const selectInvocationTransition = <
|
|
|
968
975
|
snapshot,
|
|
969
976
|
target: getTargetBuilder(machine, event.path),
|
|
970
977
|
id: event.id,
|
|
971
|
-
...(event.type === "
|
|
978
|
+
...(event.type === "element"
|
|
979
|
+
? { element: event.element }
|
|
980
|
+
: event.type === "done"
|
|
972
981
|
? { output: event.output }
|
|
973
982
|
: event.type === "failure"
|
|
974
983
|
? { error: event.error }
|
|
@@ -1150,6 +1159,7 @@ interface ResolvedChoiceTransition {
|
|
|
1150
1159
|
readonly trigger: Machine.TransitionTrigger
|
|
1151
1160
|
readonly reenter: false
|
|
1152
1161
|
readonly branchIndex: number
|
|
1162
|
+
readonly branchKey: string | undefined
|
|
1153
1163
|
readonly target: string
|
|
1154
1164
|
readonly resolvedTarget: string
|
|
1155
1165
|
}
|
|
@@ -1228,6 +1238,7 @@ function resolveChoiceTarget(
|
|
|
1228
1238
|
trigger: { type: "choice" },
|
|
1229
1239
|
reenter: false,
|
|
1230
1240
|
branchIndex: collected.branchIndex,
|
|
1241
|
+
branchKey: collected.branchKey,
|
|
1231
1242
|
target: returnedPath,
|
|
1232
1243
|
resolvedTarget: nested?.target.path ?? returnedPath
|
|
1233
1244
|
})
|
|
@@ -1398,6 +1409,7 @@ const collectEvaluatedTransition = <
|
|
|
1398
1409
|
return {
|
|
1399
1410
|
selection,
|
|
1400
1411
|
branchIndex: transitionResult.branchIndex,
|
|
1412
|
+
branchKey: transitionResult.branchKey,
|
|
1401
1413
|
unresolvedTarget,
|
|
1402
1414
|
target,
|
|
1403
1415
|
commands: [
|
|
@@ -1444,6 +1456,7 @@ const collectEvaluatedTransition = <
|
|
|
1444
1456
|
return {
|
|
1445
1457
|
selection,
|
|
1446
1458
|
branchIndex: transitionResult.branchIndex,
|
|
1459
|
+
branchKey: transitionResult.branchKey,
|
|
1447
1460
|
unresolvedTarget,
|
|
1448
1461
|
target,
|
|
1449
1462
|
commands: [
|
|
@@ -1799,6 +1812,7 @@ const microstep = <
|
|
|
1799
1812
|
trigger: transition.selection.trigger,
|
|
1800
1813
|
reenter: transition.selection.transition.reenter,
|
|
1801
1814
|
branchIndex: transition.branchIndex,
|
|
1815
|
+
branchKey: transition.branchKey,
|
|
1802
1816
|
target: transition.unresolvedTarget === undefined ? undefined : getTargetNodePath(transition.unresolvedTarget),
|
|
1803
1817
|
resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target)
|
|
1804
1818
|
},
|