@typeonce/effect-machine 0.14.1 → 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.
Files changed (74) hide show
  1. package/README.md +99 -48
  2. package/dist/Machine.d.ts +436 -156
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +114 -57
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/activities.d.ts +2 -0
  7. package/dist/internal/machine/activities.d.ts.map +1 -1
  8. package/dist/internal/machine/activities.js +4 -0
  9. package/dist/internal/machine/activities.js.map +1 -1
  10. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  11. package/dist/internal/machine/executionPlan.js +6 -1
  12. package/dist/internal/machine/executionPlan.js.map +1 -1
  13. package/dist/internal/machine/invocation.d.ts +1 -1
  14. package/dist/internal/machine/invocation.d.ts.map +1 -1
  15. package/dist/internal/machine/invocation.js +25 -3
  16. package/dist/internal/machine/invocation.js.map +1 -1
  17. package/dist/internal/machine/invocationEvent.d.ts +8 -0
  18. package/dist/internal/machine/invocationEvent.d.ts.map +1 -1
  19. package/dist/internal/machine/invocationEvent.js +8 -0
  20. package/dist/internal/machine/invocationEvent.js.map +1 -1
  21. package/dist/internal/machine/machine.d.ts +9 -4
  22. package/dist/internal/machine/machine.d.ts.map +1 -1
  23. package/dist/internal/machine/machine.js +131 -55
  24. package/dist/internal/machine/machine.js.map +1 -1
  25. package/dist/internal/machine/planner.d.ts +7 -0
  26. package/dist/internal/machine/planner.d.ts.map +1 -1
  27. package/dist/internal/machine/planner.js +20 -11
  28. package/dist/internal/machine/planner.js.map +1 -1
  29. package/dist/internal/machine/runtime.d.ts +1 -0
  30. package/dist/internal/machine/runtime.d.ts.map +1 -1
  31. package/dist/internal/machine/runtime.js +25 -16
  32. package/dist/internal/machine/runtime.js.map +1 -1
  33. package/dist/internal/machine/stateDefinition.d.ts +3 -1
  34. package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
  35. package/dist/internal/machine/stateDefinition.js +35 -0
  36. package/dist/internal/machine/stateDefinition.js.map +1 -1
  37. package/dist/internal/machine/topology.d.ts +11 -0
  38. package/dist/internal/machine/topology.d.ts.map +1 -1
  39. package/dist/internal/machine/topology.js +17 -6
  40. package/dist/internal/machine/topology.js.map +1 -1
  41. package/dist/internal/testing/machine/finiteModel.js +1 -1
  42. package/dist/internal/testing/machine/finiteModel.js.map +1 -1
  43. package/dist/internal/testing/machine/transitionCoverage.d.ts.map +1 -1
  44. package/dist/internal/testing/machine/transitionCoverage.js +6 -2
  45. package/dist/internal/testing/machine/transitionCoverage.js.map +1 -1
  46. package/dist/internal/testing/machine/verification.d.ts.map +1 -1
  47. package/dist/internal/testing/machine/verification.js +6 -0
  48. package/dist/internal/testing/machine/verification.js.map +1 -1
  49. package/dist/testing/MachineTest.d.ts +9 -8
  50. package/dist/testing/MachineTest.d.ts.map +1 -1
  51. package/dist/testing/MachineTest.js +7 -7
  52. package/dist/testing/MachineTest.js.map +1 -1
  53. package/dist/unstable/cluster/ClusterMachine.d.ts +1 -1
  54. package/dist/unstable/cluster/ClusterMachine.js +1 -1
  55. package/dist/unstable/reactivity/AtomMachine.d.ts +3 -3
  56. package/dist/unstable/reactivity/AtomMachine.js +3 -3
  57. package/docs/agent-guide.md +207 -88
  58. package/package.json +1 -1
  59. package/src/Machine.ts +871 -256
  60. package/src/internal/machine/activities.ts +7 -0
  61. package/src/internal/machine/executionPlan.ts +6 -1
  62. package/src/internal/machine/invocation.ts +39 -4
  63. package/src/internal/machine/invocationEvent.ts +16 -0
  64. package/src/internal/machine/machine.ts +187 -67
  65. package/src/internal/machine/planner.ts +17 -3
  66. package/src/internal/machine/runtime.ts +61 -25
  67. package/src/internal/machine/stateDefinition.ts +41 -1
  68. package/src/internal/machine/topology.ts +31 -2
  69. package/src/internal/testing/machine/finiteModel.ts +1 -1
  70. package/src/internal/testing/machine/transitionCoverage.ts +6 -2
  71. package/src/internal/testing/machine/verification.ts +11 -0
  72. package/src/testing/MachineTest.ts +9 -7
  73. package/src/unstable/cluster/ClusterMachine.ts +1 -1
  74. package/src/unstable/reactivity/AtomMachine.ts +3 -3
@@ -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 type * as Runtime from "./runtime.js"
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,
@@ -11,6 +11,7 @@ import type {
11
11
  ChildAddress,
12
12
  ChildMachine,
13
13
  Command,
14
+ Definition,
14
15
  ExecutionServices,
15
16
  InitialEvent as InitialEventModel,
16
17
  Logic,
@@ -79,7 +80,14 @@ type ValidateDefinedStates<States extends Machine.StateSchemas> = [States] exten
79
80
  type InvalidDefinedStateTreeInput<States extends Machine.StateSchemas> = [States] extends
80
81
  [Machine.ValidateStateSchemas<States>] ? never
81
82
  : States & Machine.ValidateStateSchemas<States>
82
- interface DefineStates {
83
+ type ReusableStateNodeConfig =
84
+ | Machine.AtomicStateNodeConfig
85
+ | Machine.CompoundStateNodeConfig
86
+ | Machine.ParallelStateNodeConfig
87
+ interface StateConstructor {
88
+ <const Node extends ReusableStateNodeConfig>(node: Node): Node
89
+ }
90
+ interface StatesConstructor {
83
91
  <const States extends Machine.StateSchemas>(
84
92
  states: States,
85
93
  ..._validation: ValidateDefinedStates<NoInfer<States>>
@@ -106,8 +114,8 @@ const Proto = {
106
114
 
107
115
  const makeBoundInvoke = (config: unknown): unknown => config
108
116
 
109
- const cloneWithHandlers = (
110
- self: Machine.Any,
117
+ const makeWithHandlers = (
118
+ self: Definition.Any,
111
119
  handlers: Machine.StateConfigs<any, any, any, any, any, any, any>
112
120
  ): Machine.Any => {
113
121
  const machine = Object.create(Proto)
@@ -123,15 +131,12 @@ const cloneWithHandlers = (
123
131
  machine.stateNodes = self.stateNodes
124
132
  machine.makeTargetBuilder = self.makeTargetBuilder
125
133
  machine.handlers = handlers
126
- machine.handle = makeHandle(machine)
127
134
  machine.invoke = makeBoundInvoke
128
135
  Protocol.copyProtocol(self, machine)
129
136
  return machine
130
137
  }
131
138
 
132
139
  type DefinitionBranch = {
133
- readonly title?: string
134
- readonly when?: (context: any) => Option.Option<unknown>
135
140
  readonly target: (selector: unknown) => unknown
136
141
  readonly resolve?: (context: any, enqueue: unknown) => unknown
137
142
  }
@@ -140,6 +145,17 @@ type CapturedBranch = DefinitionBranch & {
140
145
  readonly selection: Topology.TargetSelection
141
146
  }
142
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
+
143
159
  const transitionTargetSelection = (
144
160
  selection: Topology.TargetSelection
145
161
  ): Machine.TransitionTargetSelection =>
@@ -329,20 +345,134 @@ const runCapturedBranch = (
329
345
  context: Record<string, any>,
330
346
  enqueue: unknown,
331
347
  stateNodes: Machine.StateNodes,
332
- source: string,
333
- match?: { readonly value: unknown }
348
+ source: string
334
349
  ): unknown => {
335
350
  const selectedTarget = getSelectionBuilder(context.target, branch.selection, stateNodes, source)
336
351
  if (branch.resolve === undefined) return constructSelectedTarget(selectedTarget)
337
352
  const resolverContext = { ...context }
338
353
  if (branch.selection.kind === "none") delete resolverContext.target
339
354
  else resolverContext.target = selectedTarget
340
- if (match !== undefined) resolverContext.match = match.value
341
355
  const resolved = branch.resolve(resolverContext, enqueue)
342
356
  validateResolvedSelection(resolved, branch.selection, stateNodes)
343
357
  return resolved === undefined ? constructSelectedTarget(selectedTarget) : resolved
344
358
  }
345
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
+
346
476
  const captureTransition = (
347
477
  transition: unknown,
348
478
  stateNodes: Machine.StateNodes,
@@ -355,64 +485,53 @@ const captureTransition = (
355
485
  const definition = transition as Record<PropertyKey, unknown>
356
486
  const selector = makeTargetSelector(stateNodes, path)
357
487
  const reenter = definition.reenter === true
358
- if (Array.isArray(definition.cases)) {
359
- const rawCases = definition.cases as ReadonlyArray<unknown>
360
- if (definition.cases.length === 0 || !hasProperty(definition, "otherwise")) {
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") {
361
491
  throw new Error(
362
- `Machine conditional transition for state "${path}" on "${String(trigger)}" requires cases and otherwise`
492
+ `Machine branching transition for state "${path}" on "${String(trigger)}" requires branches and resolve`
363
493
  )
364
494
  }
365
- const cases = rawCases.map((branch) => {
366
- const captured = captureDefinitionBranch(branch, selector, path, trigger)
367
- if (typeof captured.title !== "string" || captured.title.length === 0 || typeof captured.when !== "function") {
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) {
368
504
  throw new Error(
369
- `Machine conditional transition case for state "${path}" on "${String(trigger)}" requires title and when`
505
+ `Machine branching transition for state "${path}" on "${String(trigger)}" must select one declared branch`
370
506
  )
371
507
  }
372
- return captured
373
- })
374
- const otherwise = captureDefinitionBranch(definition.otherwise, selector, path, trigger)
375
- const evaluate = (context: Record<string, any>, enqueue: unknown) => {
376
- const predicateContext = { ...context }
377
- delete predicateContext.target
378
- for (let branchIndex = 0; branchIndex < cases.length; branchIndex++) {
379
- const branch = cases[branchIndex]!
380
- const result = branch.when!(predicateContext)
381
- if (!Option.isOption(result)) {
382
- throw new Error(`Machine conditional transition case "${branch.title}" must return Option`)
383
- }
384
- if (Option.isSome(result)) {
385
- return {
386
- result: runCapturedBranch(branch, context, enqueue, stateNodes, path, { value: result.value }),
387
- branchIndex
388
- }
389
- }
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`)
390
511
  }
512
+ validateSelectedBranchResult(selected.result, branch.selection, stateNodes)
391
513
  return {
392
- result: runCapturedBranch(otherwise, context, enqueue, stateNodes, path),
393
- branchIndex: cases.length
514
+ result: selected.result,
515
+ branchIndex: selected.branchIndex,
516
+ branchKey: selected.branchKey
394
517
  }
395
518
  }
396
519
  return {
397
520
  reenter,
398
521
  targets: [
399
522
  ...new Set(
400
- [...cases, otherwise].flatMap((branch) => branch.selection.path === undefined ? [] : [branch.selection.path])
523
+ branches.flatMap((branch) => branch.selection.path === undefined ? [] : [branch.selection.path])
401
524
  )
402
525
  ],
403
- branches: [
404
- ...cases.map((branch) => ({
405
- type: "case" as const,
406
- title: branch.title!,
526
+ branches: branches.map((branch) =>
527
+ Object.freeze({
528
+ type: "branch" as const,
529
+ key: branch.key,
530
+ title: branch.title,
407
531
  target: branch.selection.path,
408
532
  selection: transitionTargetSelection(branch.selection)
409
- })),
410
- {
411
- type: "otherwise" as const,
412
- target: otherwise.selection.path,
413
- selection: transitionTargetSelection(otherwise.selection)
414
- }
415
- ],
533
+ })
534
+ ),
416
535
  evaluate,
417
536
  transition: (context: Record<string, any>, enqueue: unknown) => evaluate(context, enqueue).result
418
537
  }
@@ -420,7 +539,8 @@ const captureTransition = (
420
539
  const branch = captureDefinitionBranch(transition, selector, path, trigger)
421
540
  const evaluate = (context: Record<string, any>, enqueue: unknown) => ({
422
541
  result: runCapturedBranch(branch, context, enqueue, stateNodes, path),
423
- branchIndex: 0
542
+ branchIndex: 0,
543
+ branchKey: undefined
424
544
  })
425
545
  return {
426
546
  reenter,
@@ -458,7 +578,7 @@ const captureInvokeDefinition = (
458
578
  if (Array.isArray(invoke)) return invoke.map((item) => captureInvokeDefinition(item, stateNodes, path))
459
579
  if (typeof invoke !== "object" || invoke === null) return invoke
460
580
  const captured = { ...(invoke as Record<PropertyKey, unknown>) }
461
- for (const key of ["onDone", "onFailure", "onSnapshot"] as const) {
581
+ for (const key of ["onElement", "onDone", "onFailure", "onSnapshot"] as const) {
462
582
  if (captured[key] !== undefined) {
463
583
  captured[key] = captureTransition(captured[key], stateNodes, path, key)
464
584
  }
@@ -525,15 +645,12 @@ const flattenHandlers = (
525
645
  }
526
646
  }
527
647
 
528
- const makeHandle = (self: Machine.Any): Machine.Any["handle"] =>
648
+ const makeHandle = (self: Definition.Any): Definition.Any["handle"] =>
529
649
  ((config: Record<string, unknown>) => {
530
- const handlers: Record<PropertyKey, Machine.AnyStateConfig> = Object.assign(
531
- Object.create(null),
532
- self.handlers
533
- )
650
+ const handlers: Record<PropertyKey, Machine.AnyStateConfig> = Object.create(null)
534
651
  flattenHandlers(handlers, self.stateNodes, self.states, "", config)
535
- return cloneWithHandlers(self, handlers)
536
- }) as Machine.Any["handle"]
652
+ return makeWithHandlers(self, handlers)
653
+ }) as Definition.Any["handle"]
537
654
 
538
655
  export const isMachine = (
539
656
  u: unknown
@@ -1141,12 +1258,19 @@ const compileInitial = (
1141
1258
  }
1142
1259
  }
1143
1260
 
1144
- export const defineStates: DefineStates = (<const States extends Machine.StateSchemas>(
1261
+ export const state: StateConstructor = (<const Node extends ReusableStateNodeConfig>(node: Node): Node => {
1262
+ StateDefinition.validateStateDefinitions({ state: node }, "Machine.state")
1263
+ return StateDefinition.captureStateDefinitions({ state: node }).state
1264
+ }) as StateConstructor
1265
+
1266
+ export const states: StatesConstructor = (<const States extends Machine.StateSchemas>(
1145
1267
  states: States
1146
1268
  ): Machine.DefinedStates<States> => {
1147
- StateDefinition.validateStateDefinitions(states, "Machine.defineStates")
1269
+ StateDefinition.validateStateDefinitions(states, "Machine.states")
1270
+ const captured = StateDefinition.captureStateDefinitions(states)
1148
1271
  return {
1149
- states,
1272
+ states: captured,
1273
+ path: ((path: string) => path) as Machine.DefinedStates<States>["path"],
1150
1274
  get:
1151
1275
  ((snapshot: Machine.AtomicSnapshot<string, unknown>, path: string) =>
1152
1276
  Topology.getSnapshotByPath(snapshot, path).pipe(
@@ -1163,7 +1287,7 @@ export const defineStates: DefineStates = (<const States extends Machine.StateSc
1163
1287
  ((snapshot: Machine.AtomicSnapshot<string, unknown>, path: string) =>
1164
1288
  Option.isSome(Topology.getSnapshotByPath(snapshot, path))) as Machine.DefinedStates<States>["matches"]
1165
1289
  }
1166
- }) as DefineStates
1290
+ }) as StatesConstructor
1167
1291
 
1168
1292
  type MakeConfig<
1169
1293
  States extends Machine.StateSchemas,
@@ -1201,19 +1325,15 @@ type MakeResult<
1201
1325
  InitialR,
1202
1326
  InternalEvents extends ReadonlyArray<Machine.TaggedSchema>,
1203
1327
  ParentEvents extends ReadonlyArray<Machine.TaggedSchema>
1204
- > = Machine<
1328
+ > = Definition<
1205
1329
  States,
1206
1330
  readonly [...InputEvents, ...InternalEvents],
1207
1331
  Input,
1208
- Machine.StateIdentifier<States>,
1209
- never,
1210
- never,
1211
1332
  InitialE,
1212
1333
  InitialR,
1213
1334
  Machine.FinalStateFromDefinition<States>,
1214
1335
  Machine.TerminalOutput<States>,
1215
1336
  Emits,
1216
- never,
1217
1337
  InputEvents,
1218
1338
  ParentEvents
1219
1339
  >
@@ -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 === "done"
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 === "done"
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
  },