@typeonce/effect-machine 0.7.0 → 0.9.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 (77) hide show
  1. package/README.md +85 -24
  2. package/dist/Machine.d.ts +545 -444
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +69 -172
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/activities.d.ts +5 -12
  7. package/dist/internal/machine/activities.d.ts.map +1 -1
  8. package/dist/internal/machine/activities.js +47 -17
  9. package/dist/internal/machine/activities.js.map +1 -1
  10. package/dist/internal/machine/atom.d.ts +10 -4
  11. package/dist/internal/machine/atom.d.ts.map +1 -1
  12. package/dist/internal/machine/atom.js.map +1 -1
  13. package/dist/internal/machine/configuration.d.ts.map +1 -1
  14. package/dist/internal/machine/configuration.js +103 -20
  15. package/dist/internal/machine/configuration.js.map +1 -1
  16. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  17. package/dist/internal/machine/executionPlan.js +31 -0
  18. package/dist/internal/machine/executionPlan.js.map +1 -1
  19. package/dist/internal/machine/invocation.d.ts +10 -2
  20. package/dist/internal/machine/invocation.d.ts.map +1 -1
  21. package/dist/internal/machine/invocation.js +93 -34
  22. package/dist/internal/machine/invocation.js.map +1 -1
  23. package/dist/internal/machine/invocationEvent.d.ts +39 -0
  24. package/dist/internal/machine/invocationEvent.d.ts.map +1 -0
  25. package/dist/internal/machine/invocationEvent.js +43 -0
  26. package/dist/internal/machine/invocationEvent.js.map +1 -0
  27. package/dist/internal/machine/machine.d.ts +6 -50
  28. package/dist/internal/machine/machine.d.ts.map +1 -1
  29. package/dist/internal/machine/machine.js +30 -81
  30. package/dist/internal/machine/machine.js.map +1 -1
  31. package/dist/internal/machine/planner.d.ts +12 -1
  32. package/dist/internal/machine/planner.d.ts.map +1 -1
  33. package/dist/internal/machine/planner.js +90 -37
  34. package/dist/internal/machine/planner.js.map +1 -1
  35. package/dist/internal/machine/protocol.d.ts +9 -0
  36. package/dist/internal/machine/protocol.d.ts.map +1 -1
  37. package/dist/internal/machine/protocol.js +114 -0
  38. package/dist/internal/machine/protocol.js.map +1 -1
  39. package/dist/internal/machine/serialization.d.ts.map +1 -1
  40. package/dist/internal/machine/serialization.js +53 -21
  41. package/dist/internal/machine/serialization.js.map +1 -1
  42. package/dist/internal/machine/stateDefinition.d.ts +4 -4
  43. package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
  44. package/dist/internal/machine/stateDefinition.js +18 -11
  45. package/dist/internal/machine/stateDefinition.js.map +1 -1
  46. package/dist/internal/machine/symbols.d.ts +2 -0
  47. package/dist/internal/machine/symbols.d.ts.map +1 -1
  48. package/dist/internal/machine/symbols.js +2 -0
  49. package/dist/internal/machine/symbols.js.map +1 -1
  50. package/dist/internal/machine/topology.d.ts +5 -5
  51. package/dist/internal/machine/topology.d.ts.map +1 -1
  52. package/dist/internal/machine/topology.js +39 -13
  53. package/dist/internal/machine/topology.js.map +1 -1
  54. package/dist/internal/testing/machine/verification.d.ts.map +1 -1
  55. package/dist/internal/testing/machine/verification.js +10 -3
  56. package/dist/internal/testing/machine/verification.js.map +1 -1
  57. package/dist/unstable/reactivity/AtomMachine.d.ts +11 -5
  58. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  59. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  60. package/docs/agent-guide.md +174 -121
  61. package/package.json +3 -3
  62. package/src/Machine.ts +1336 -872
  63. package/src/internal/machine/activities.ts +48 -33
  64. package/src/internal/machine/atom.ts +13 -6
  65. package/src/internal/machine/configuration.ts +102 -23
  66. package/src/internal/machine/executionPlan.ts +35 -0
  67. package/src/internal/machine/invocation.ts +180 -49
  68. package/src/internal/machine/invocationEvent.ts +72 -0
  69. package/src/internal/machine/machine.ts +105 -403
  70. package/src/internal/machine/planner.ts +110 -48
  71. package/src/internal/machine/protocol.ts +149 -0
  72. package/src/internal/machine/serialization.ts +56 -31
  73. package/src/internal/machine/stateDefinition.ts +22 -11
  74. package/src/internal/machine/symbols.ts +3 -0
  75. package/src/internal/machine/topology.ts +44 -18
  76. package/src/internal/testing/machine/verification.ts +13 -3
  77. package/src/unstable/reactivity/AtomMachine.ts +12 -5
@@ -4,8 +4,7 @@
4
4
  * @since 0.4.0
5
5
  */
6
6
 
7
- /** @internal */
8
- export const ActivityMetadataTypeId: unique symbol = Symbol.for("effect/Machine/ActivityMetadata")
7
+ import * as Duration from "effect/Duration"
9
8
 
10
9
  /** @internal */
11
10
  export type StaticActivityMetadata =
@@ -21,8 +20,7 @@ export type StaticActivityMetadata =
21
20
  }
22
21
  | {
23
22
  readonly type: "timer"
24
- readonly duration: string
25
- readonly event: string
23
+ readonly duration: string | "dynamic"
26
24
  }
27
25
  | {
28
26
  readonly type: "machine"
@@ -33,20 +31,10 @@ export type StaticActivityMetadata =
33
31
  }
34
32
 
35
33
  /** @internal */
36
- export type ActivityDefinition<Source extends string = string> =
37
- | ({
38
- readonly source: Source
39
- readonly id: string
40
- } & StaticActivityMetadata)
41
- | {
42
- readonly source: Source
43
- readonly type: "dynamic"
44
- }
45
-
46
- interface ActivityDescriptor {
34
+ export type ActivityDefinition<Source extends string = string> = {
35
+ readonly source: Source
47
36
  readonly id: string
48
- readonly [ActivityMetadataTypeId]: StaticActivityMetadata
49
- }
37
+ } & StaticActivityMetadata
50
38
 
51
39
  interface InspectableMachine {
52
40
  readonly handlers: unknown
@@ -62,31 +50,60 @@ const isObject = (value: unknown): value is object =>
62
50
 
63
51
  const getProperty = (value: unknown, key: PropertyKey): unknown => isObject(value) ? Reflect.get(value, key) : undefined
64
52
 
65
- const hasActivityMetadata = (value: unknown): value is ActivityDescriptor =>
66
- isObject(value) && typeof getProperty(value, "id") === "string" &&
67
- isObject(getProperty(value, ActivityMetadataTypeId))
68
-
69
53
  const appendStaticDefinition = (
70
54
  definitions: Array<ActivityDefinition>,
71
55
  source: string,
72
56
  descriptor: unknown
73
57
  ): void => {
74
- if (!hasActivityMetadata(descriptor)) {
58
+ if (!isObject(descriptor)) return
59
+ const child = getProperty(descriptor, "child")
60
+ if (isObject(child)) {
61
+ const id = getProperty(child, "id")
62
+ if (typeof id !== "string") return
63
+ const machine = getProperty(child, "machine")
64
+ const machineId = getProperty(machine, "id")
65
+ definitions.push({
66
+ source,
67
+ id,
68
+ type: "machine",
69
+ child: { id, machineId: typeof machineId === "string" ? machineId : null }
70
+ })
75
71
  return
76
72
  }
77
- definitions.push({
78
- source,
79
- id: descriptor.id,
80
- ...descriptor[ActivityMetadataTypeId]
81
- })
73
+ const id = getProperty(descriptor, "id")
74
+ if (typeof id !== "string") return
75
+ if (Reflect.has(descriptor, "effect")) {
76
+ definitions.push({
77
+ source,
78
+ id,
79
+ type: "effect",
80
+ outcomes: {
81
+ success: "dynamic",
82
+ failure: Reflect.has(descriptor, "onFailure") ? "dynamic" : "none"
83
+ }
84
+ })
85
+ return
86
+ }
87
+ if (Reflect.has(descriptor, "after")) {
88
+ const after = getProperty(descriptor, "after")
89
+ definitions.push({
90
+ source,
91
+ id,
92
+ type: "timer",
93
+ duration: typeof after === "function" ? "dynamic" : Duration.format(Duration.fromInputUnsafe(after as any))
94
+ })
95
+ return
96
+ }
97
+ if (Reflect.has(descriptor, "logic")) {
98
+ definitions.push({ source, id, type: "process" })
99
+ }
82
100
  }
83
101
 
84
102
  /**
85
103
  * Collects state-owned activity descriptions without executing user code.
86
104
  *
87
- * Function-valued invoke definitions depend on an entry context and are
88
- * therefore represented as dynamic. Static descriptors are returned in state
89
- * definition order and descriptor array order.
105
+ * Source factories are inspected without execution. Static descriptors are
106
+ * returned in state definition order and descriptor array order.
90
107
  *
91
108
  * @internal
92
109
  */
@@ -94,9 +111,7 @@ export const activityDefinitions = (machine: InspectableMachine): ReadonlyArray<
94
111
  const definitions: Array<ActivityDefinition> = []
95
112
  for (const node of machine.stateNodes.byPath.values()) {
96
113
  const invoke = getProperty(getProperty(machine.handlers, node.path), "invoke")
97
- if (typeof invoke === "function") {
98
- definitions.push({ source: node.path, type: "dynamic" })
99
- } else if (Array.isArray(invoke)) {
114
+ if (Array.isArray(invoke)) {
100
115
  for (const descriptor of invoke) {
101
116
  appendStaticDefinition(definitions, node.path, descriptor)
102
117
  }
@@ -120,7 +120,7 @@ const startMachineAtomEffect = <
120
120
  ): Effect.Effect<
121
121
  Machine.MachineRef<
122
122
  Machine.Machine.Snapshot<States>,
123
- Machine.Machine.EventOf<InputEvents>,
123
+ Machine.Machine.EventInputOf<InputEvents>,
124
124
  MachineRuntimeError<E, R>,
125
125
  Output
126
126
  >,
@@ -431,6 +431,13 @@ type SnapshotIdentifier<State> = SnapshotNode<State> extends infer Node ?
431
431
  Node extends { readonly path: infer Path extends string } ? Path : never
432
432
  : never
433
433
 
434
+ type ValuedSnapshotIdentifier<State> = SnapshotNode<State> extends infer Node ?
435
+ Node extends { readonly path: infer Path extends string; readonly value: infer Value } ?
436
+ [Value] extends [undefined] ? never
437
+ : Path
438
+ : never
439
+ : never
440
+
434
441
  type SnapshotValueByIdentifier<State, Path extends SnapshotIdentifier<State>> = SnapshotNode<State> extends infer Node ?
435
442
  Node extends { readonly path: Path; readonly value: infer Value } ? Value : never
436
443
  : never
@@ -443,7 +450,7 @@ type ChildState<Child extends Machine.ChildMachine.Any> = RefState<Machine.Child
443
450
 
444
451
  const selectValueByPath = <
445
452
  State extends Machine.Machine.AtomicSnapshot<string, unknown>,
446
- Path extends SnapshotIdentifier<State>
453
+ Path extends ValuedSnapshotIdentifier<State>
447
454
  >(
448
455
  snapshot: State,
449
456
  path: Path
@@ -467,7 +474,7 @@ export const select = <
467
474
  Error,
468
475
  Output,
469
476
  StartError,
470
- const Path extends SnapshotIdentifier<State>
477
+ const Path extends ValuedSnapshotIdentifier<State>
471
478
  >(
472
479
  self: MachineAtom<State, Event, Error, Output, StartError>,
473
480
  path: Path
@@ -498,7 +505,7 @@ export const selectSnapshot = <
498
505
  export const selectChild = <
499
506
  Child extends Machine.ChildMachine.Any,
500
507
  StartError,
501
- const Path extends SnapshotIdentifier<ChildState<Child>>
508
+ const Path extends ValuedSnapshotIdentifier<ChildState<Child>>
502
509
  >(
503
510
  self: ChildMachineAtom<Child, StartError>,
504
511
  path: Path
@@ -578,7 +585,7 @@ type EnsureMachineExecutable<M extends Machine.Machine.Any> = IsAny<Machine.Mach
578
585
 
579
586
  type ResumedMachineAtomOf<M extends Machine.Machine.Any, RuntimeError> = MachineAtom<
580
587
  Machine.Machine.Snapshot<Machine.Machine.States<M>>,
581
- Machine.Machine.InputEvent<M>,
588
+ Machine.Machine.EventInput<Machine.Machine.InputEvent<M>>,
582
589
  MachineRuntimeError<Machine.Machine.Error<M>, Machine.Machine.Services<M>>,
583
590
  Machine.Machine.Output<M>,
584
591
  Machine.MachineSchemaDecodeError | RuntimeError
@@ -628,7 +635,7 @@ export const make: {
628
635
  ...args: [...Machine.Machine.InputArgs<Input>]
629
636
  ): MachineAtom<
630
637
  Machine.Machine.Snapshot<States>,
631
- Machine.Machine.EventOf<InputEvents>,
638
+ Machine.Machine.EventInputOf<InputEvents>,
632
639
  MachineRuntimeError<E, R>,
633
640
  Output,
634
641
  MachineStartError<InitialE, E, InitialR, R>
@@ -84,7 +84,7 @@ const historyFromSnapshot = (
84
84
  const active = new Set<string>()
85
85
  const values = new Map<string, unknown>()
86
86
  for (const activePath of entry.active) {
87
- if (active.has(activePath) || !Object.prototype.hasOwnProperty.call(entry.values, activePath)) {
87
+ if (active.has(activePath)) {
88
88
  throw new Error(`Machine snapshot contains invalid remembered state "${activePath}"`)
89
89
  }
90
90
  const node = getNode(machine, activePath)
@@ -96,9 +96,15 @@ const historyFromSnapshot = (
96
96
  throw new Error(`Machine snapshot contains invalid remembered value for "${activePath}"`)
97
97
  }
98
98
  active.add(activePath)
99
- values.set(activePath, decodeStateValueSync(machine, node, entry.values[activePath]))
99
+ const hasValue = Object.prototype.hasOwnProperty.call(entry.values, activePath)
100
+ if (node.schema === undefined) {
101
+ if (hasValue) throw new Error(`Machine snapshot contains a value for structural state "${activePath}"`)
102
+ } else {
103
+ if (!hasValue) throw new Error(`Machine snapshot omits remembered value for "${activePath}"`)
104
+ values.set(activePath, decodeStateValueSync(machine, node, entry.values[activePath]))
105
+ }
100
106
  }
101
- if (!active.has(historyNode.parent) || Object.keys(entry.values).length !== active.size) {
107
+ if (!active.has(historyNode.parent) || Object.keys(entry.values).length !== values.size) {
102
108
  throw new Error(`Machine snapshot contains incomplete history record "${path}"`)
103
109
  }
104
110
  history.set(path, {
@@ -138,7 +144,6 @@ const historyFromSnapshotEffect = Effect.fnUntraced(function*(
138
144
  const node = machine.stateNodes.byPath.get(activePath)
139
145
  if (
140
146
  active.has(activePath) || node === undefined || node.type === "history" || node.type === "choice" ||
141
- !Object.prototype.hasOwnProperty.call(entry.values, activePath) ||
142
147
  !(isPathInSubtree(activePath, historyNode.parent) ||
143
148
  getPathToRoot(machine, historyNode.parent).includes(activePath))
144
149
  ) {
@@ -152,15 +157,39 @@ const historyFromSnapshotEffect = Effect.fnUntraced(function*(
152
157
  )
153
158
  }
154
159
  active.add(activePath)
155
- values.set(
156
- activePath,
157
- yield* decodeBoundary(machine, getStateNodeSchema(node), entry.values[activePath], {
158
- boundary: "history",
159
- state: activePath
160
- })
161
- )
160
+ const hasValue = Object.prototype.hasOwnProperty.call(entry.values, activePath)
161
+ if (node.schema === undefined) {
162
+ if (hasValue) {
163
+ return yield* Effect.fail(
164
+ new MachineSchemaDecodeError({
165
+ machineId: machine.id,
166
+ boundary: "history",
167
+ state: activePath,
168
+ cause: Cause.die(new Error(`Machine snapshot contains a value for structural state "${activePath}"`))
169
+ })
170
+ )
171
+ }
172
+ } else {
173
+ if (!hasValue) {
174
+ return yield* Effect.fail(
175
+ new MachineSchemaDecodeError({
176
+ machineId: machine.id,
177
+ boundary: "history",
178
+ state: activePath,
179
+ cause: Cause.die(new Error(`Machine snapshot omits remembered value for "${activePath}"`))
180
+ })
181
+ )
182
+ }
183
+ values.set(
184
+ activePath,
185
+ yield* decodeBoundary(machine, getStateNodeSchema(node), entry.values[activePath], {
186
+ boundary: "history",
187
+ state: activePath
188
+ })
189
+ )
190
+ }
162
191
  }
163
- if (!active.has(historyNode.parent) || Object.keys(entry.values).length !== active.size) {
192
+ if (!active.has(historyNode.parent) || Object.keys(entry.values).length !== values.size) {
164
193
  return yield* Effect.fail(
165
194
  new MachineSchemaDecodeError({
166
195
  machineId: machine.id,
@@ -212,7 +241,7 @@ const historyToSnapshot = (
212
241
  entries[path] = {
213
242
  mode: record.mode,
214
243
  active,
215
- values: Object.fromEntries(active.map((path) => [path, record.values.get(path)]))
244
+ values: Object.fromEntries(record.values)
216
245
  }
217
246
  }
218
247
  return entries
@@ -329,7 +358,9 @@ export const getParentValues = (
329
358
  const paths = getPathToRoot(machine, path)
330
359
  for (let index = 0; index < paths.length - 1; index++) {
331
360
  const parent = paths[index]!
332
- parents[parent] = getActiveValue(configuration, parent)
361
+ if (configuration.values.has(parent)) {
362
+ parents[parent] = configuration.values.get(parent)
363
+ }
333
364
  }
334
365
  return parents
335
366
  }
@@ -340,7 +371,7 @@ export const getParentValue = (
340
371
  path: string
341
372
  ): unknown => {
342
373
  const parent = getNode(machine, path).parent
343
- return parent === undefined ? undefined : getActiveValue(configuration, parent)
374
+ return parent === undefined ? undefined : configuration.values.get(parent)
344
375
  }
345
376
 
346
377
  export const getInitialEntryPaths = (
@@ -368,7 +399,7 @@ export const snapshotFromPath = <const States extends Machine.StateSchemas>(
368
399
  const node = getNode(machine, path)
369
400
  const snapshot: Record<string, unknown> = {
370
401
  path,
371
- value: getActiveValue(configuration, path)
402
+ value: configuration.values.get(path)
372
403
  }
373
404
  if (node.type === "compound") {
374
405
  const child = node.children.find((child) => configuration.active.has(child))
@@ -438,9 +469,14 @@ export const configurationFromSnapshot = (
438
469
 
439
470
  const visit = (current: Machine.AtomicSnapshot<string, unknown>): void => {
440
471
  const node = getNode(machine, String(current.path))
441
- const value = decodeStateValueSync(machine, node, current.value)
442
472
  active.add(node.path)
443
- values.set(node.path, value)
473
+ if (node.schema === undefined) {
474
+ if (current.value !== undefined) {
475
+ throw new Error(`Machine structural snapshot "${node.path}" cannot contain a value`)
476
+ }
477
+ } else {
478
+ values.set(node.path, decodeStateValueSync(machine, node, current.value))
479
+ }
444
480
  if (node.type === "compound") {
445
481
  if (!hasProperty(current, "state") || !isSnapshot(current.state)) {
446
482
  throw new Error(`Machine expected compound snapshot "${node.path}" to include an active child state`)
@@ -520,9 +556,14 @@ export const configurationFromSnapshotEffect = Effect.fnUntraced(function*(
520
556
  current: Machine.AtomicSnapshot<string, unknown>
521
557
  ) {
522
558
  const node = getNode(machine, String(current.path))
523
- const value = yield* decodeStateValue(machine, node, current.value)
524
559
  active.add(node.path)
525
- values.set(node.path, value)
560
+ if (node.schema === undefined) {
561
+ if (current.value !== undefined) {
562
+ throw new Error(`Machine structural snapshot "${node.path}" cannot contain a value`)
563
+ }
564
+ } else {
565
+ values.set(node.path, yield* decodeStateValue(machine, node, current.value))
566
+ }
526
567
  if (node.type === "compound") {
527
568
  if (!hasProperty(current, "state") || !isSnapshot(current.state)) {
528
569
  throw new Error(`Machine expected compound snapshot "${node.path}" to include an active child state`)
@@ -645,7 +686,9 @@ export const captureHistory = (
645
686
  }
646
687
  const values = new Map<string, unknown>()
647
688
  for (const path of active) {
648
- values.set(path, getActiveValue(current, path))
689
+ if (current.values.has(path)) {
690
+ values.set(path, current.values.get(path))
691
+ }
649
692
  }
650
693
  history.set(node.path, {
651
694
  mode,
@@ -722,6 +765,15 @@ export const configurationFromTargetPathEffect = Effect.fnUntraced(function*(
722
765
  for (const currentPath of paths) {
723
766
  const currentNode = getNode(machine, currentPath)
724
767
  active.add(currentPath)
768
+ if (currentNode.schema === undefined) {
769
+ const supplied = currentPath === node.path ?
770
+ value
771
+ : providedValues !== undefined && hasOwn(providedValues, currentPath) ?
772
+ providedValues[currentPath]
773
+ : undefined
774
+ if (supplied !== undefined) throw new Error(`Machine structural target "${currentPath}" cannot contain a value`)
775
+ continue
776
+ }
725
777
  if (currentPath === node.path) {
726
778
  values.set(currentPath, yield* decodeStateValue(machine, currentNode, value))
727
779
  } else if (providedValues !== undefined && hasOwn(providedValues, currentPath)) {
@@ -783,6 +835,12 @@ export const configurationFromTargetSnapshotEffect = Effect.fnUntraced(function*
783
835
  for (const ancestor of paths.slice(0, -1)) {
784
836
  const node = getNode(machine, ancestor)
785
837
  active.add(ancestor)
838
+ if (node.schema === undefined) {
839
+ if (providedValues !== undefined && hasOwn(providedValues, ancestor)) {
840
+ throw new Error(`Machine structural target "${ancestor}" cannot contain a value`)
841
+ }
842
+ continue
843
+ }
786
844
  if (providedValues !== undefined && hasOwn(providedValues, ancestor)) {
787
845
  values.set(ancestor, yield* decodeStateValue(machine, node, providedValues[ancestor]))
788
846
  } else if (current.values.has(ancestor)) {
@@ -816,6 +874,9 @@ export const configurationFromTargetSnapshotEffect = Effect.fnUntraced(function*
816
874
  if (!isPathInSubtree(providedPath, child)) continue
817
875
  const providedNode = getNode(machine, providedPath)
818
876
  active.add(providedPath)
877
+ if (providedNode.schema === undefined) {
878
+ throw new Error(`Machine structural target "${providedPath}" cannot contain a value`)
879
+ }
819
880
  values.set(providedPath, yield* decodeStateValue(machine, providedNode, providedValue))
820
881
  }
821
882
  }
@@ -885,6 +946,15 @@ const configurationFromTargetPathSync = (
885
946
  for (const currentPath of paths) {
886
947
  const currentNode = getNode(machine, currentPath)
887
948
  active.add(currentPath)
949
+ if (currentNode.schema === undefined) {
950
+ const supplied = currentPath === node.path ?
951
+ value
952
+ : providedValues !== undefined && hasOwn(providedValues, currentPath) ?
953
+ providedValues[currentPath]
954
+ : undefined
955
+ if (supplied !== undefined) throw new Error(`Machine structural target "${currentPath}" cannot contain a value`)
956
+ continue
957
+ }
888
958
  if (currentPath === node.path) {
889
959
  values.set(currentPath, decodeStateValueSync(machine, currentNode, value))
890
960
  } else if (providedValues !== undefined && hasOwn(providedValues, currentPath)) {
@@ -932,6 +1002,12 @@ const configurationFromTargetSnapshotSync = (
932
1002
  for (const ancestor of paths.slice(0, -1)) {
933
1003
  const node = getNode(machine, ancestor)
934
1004
  active.add(ancestor)
1005
+ if (node.schema === undefined) {
1006
+ if (providedValues !== undefined && hasOwn(providedValues, ancestor)) {
1007
+ throw new Error(`Machine structural target "${ancestor}" cannot contain a value`)
1008
+ }
1009
+ continue
1010
+ }
935
1011
  if (providedValues !== undefined && hasOwn(providedValues, ancestor)) {
936
1012
  values.set(ancestor, decodeStateValueSync(machine, node, providedValues[ancestor]))
937
1013
  } else if (current.values.has(ancestor)) {
@@ -960,6 +1036,9 @@ const configurationFromTargetSnapshotSync = (
960
1036
  if (!isPathInSubtree(providedPath, child)) continue
961
1037
  const providedNode = getNode(machine, providedPath)
962
1038
  active.add(providedPath)
1039
+ if (providedNode.schema === undefined) {
1040
+ throw new Error(`Machine structural target "${providedPath}" cannot contain a value`)
1041
+ }
963
1042
  values.set(providedPath, decodeStateValueSync(machine, providedNode, providedValue))
964
1043
  }
965
1044
  }
@@ -1098,7 +1177,7 @@ export const resolveFinalOutputEffect: <
1098
1177
  ) {
1099
1178
  const node = getNode(machine, path)
1100
1179
  const output = getStateConfigByPath(machine, path)?.output?.({
1101
- state: getActiveValue(configuration, path),
1180
+ state: configuration.values.get(path),
1102
1181
  parent: getParentValue(machine, configuration, path),
1103
1182
  parents: getParentValues(machine, configuration, path),
1104
1183
  event,
@@ -1251,7 +1330,7 @@ const resolveFinalOutputSync = <const Events extends ReadonlyArray<Machine.Tagge
1251
1330
  ): unknown => {
1252
1331
  const node = getNode(machine, path)
1253
1332
  const output = getStateConfigByPath(machine, path)?.output?.({
1254
- state: getActiveValue(configuration, path),
1333
+ state: configuration.values.get(path),
1255
1334
  parent: getParentValue(machine, configuration, path),
1256
1335
  parents: getParentValues(machine, configuration, path),
1257
1336
  event,
@@ -21,6 +21,7 @@ import {
21
21
  validateInitialConfiguration
22
22
  } from "./configuration.js"
23
23
  import { InfiniteTransitionError } from "./errors.js"
24
+ import * as InvocationEvent from "./invocationEvent.js"
24
25
  import {
25
26
  broadenTransitionBoundary,
26
27
  type EvaluatedTransition,
@@ -100,6 +101,12 @@ const compileIndexedExecutionDescriptor = (
100
101
  if (node.type === "choice" || node.type === "history") {
101
102
  return undefined
102
103
  }
104
+ // Structural active states deliberately use the generic semantic
105
+ // reference until the indexed value table represents absent values as an
106
+ // explicit capability rather than an `undefined` slot.
107
+ if (node.schema === undefined) {
108
+ return undefined
109
+ }
103
110
  if (node.type === "atomic" || node.type === "final") {
104
111
  leafPaths.push(node.path)
105
112
  }
@@ -769,6 +776,34 @@ const planIndexedState = (
769
776
  input: unknown,
770
777
  retainMicrosteps: boolean
771
778
  ): ExecutionMacrostep<OwnedIndexedState> => {
779
+ if (InvocationEvent.isInvocationEvent(input)) {
780
+ // Invocation lifecycle transitions retain the generic planner as their
781
+ // semantic reference. Ordinary machine events and initialization stay on
782
+ // the indexed representation; only the private lifecycle macrostep crosses
783
+ // the representation boundary.
784
+ const planned = planConfiguration(
785
+ machine as any,
786
+ activeConfigurationFromIndexedState(descriptor, configuration),
787
+ input
788
+ )
789
+ return {
790
+ next: ownedIndexedStateFromActive(descriptor, planned.next),
791
+ commands: planned.commands,
792
+ emittedEvents: planned.emittedEvents,
793
+ microsteps: planned.microsteps.map((step) => ({
794
+ next: ownedIndexedStateFromActive(descriptor, step.next),
795
+ event: step.event,
796
+ commands: step.commands,
797
+ raisedEvents: step.raisedEvents,
798
+ emittedEvents: step.emittedEvents,
799
+ exitPaths: step.exitPaths,
800
+ entryPaths: step.entryPaths,
801
+ changed: step.changed
802
+ })),
803
+ done: planned.done,
804
+ output: planned.output
805
+ }
806
+ }
772
807
  const decoded = decodeEventSync(machine, input)
773
808
  if (descriptor.flat) {
774
809
  return planIndexedFlatState(machine, descriptor, configuration, decoded, retainMicrosteps)