@typeonce/effect-machine 0.11.0 → 0.13.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 (83) hide show
  1. package/README.md +89 -13
  2. package/dist/Machine.d.ts +396 -127
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +21 -19
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/atom.d.ts +1 -0
  7. package/dist/internal/machine/atom.d.ts.map +1 -1
  8. package/dist/internal/machine/atom.js +23 -0
  9. package/dist/internal/machine/atom.js.map +1 -1
  10. package/dist/internal/machine/cluster.js +2 -2
  11. package/dist/internal/machine/cluster.js.map +1 -1
  12. package/dist/internal/machine/commandRuntime.d.ts.map +1 -1
  13. package/dist/internal/machine/commandRuntime.js +2 -5
  14. package/dist/internal/machine/commandRuntime.js.map +1 -1
  15. package/dist/internal/machine/configuration.d.ts +12 -7
  16. package/dist/internal/machine/configuration.d.ts.map +1 -1
  17. package/dist/internal/machine/configuration.js +9 -5
  18. package/dist/internal/machine/configuration.js.map +1 -1
  19. package/dist/internal/machine/executionPlan.d.ts +4 -3
  20. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  21. package/dist/internal/machine/executionPlan.js +68 -43
  22. package/dist/internal/machine/executionPlan.js.map +1 -1
  23. package/dist/internal/machine/initialization.d.ts +5 -0
  24. package/dist/internal/machine/initialization.d.ts.map +1 -0
  25. package/dist/internal/machine/initialization.js +61 -0
  26. package/dist/internal/machine/initialization.js.map +1 -0
  27. package/dist/internal/machine/inspectionRuntime.d.ts +25 -0
  28. package/dist/internal/machine/inspectionRuntime.d.ts.map +1 -0
  29. package/dist/internal/machine/inspectionRuntime.js +79 -0
  30. package/dist/internal/machine/inspectionRuntime.js.map +1 -0
  31. package/dist/internal/machine/invocation.d.ts +2 -1
  32. package/dist/internal/machine/invocation.d.ts.map +1 -1
  33. package/dist/internal/machine/invocation.js +18 -11
  34. package/dist/internal/machine/invocation.js.map +1 -1
  35. package/dist/internal/machine/machine.d.ts +1 -2
  36. package/dist/internal/machine/machine.d.ts.map +1 -1
  37. package/dist/internal/machine/machine.js +21 -6
  38. package/dist/internal/machine/machine.js.map +1 -1
  39. package/dist/internal/machine/planner.d.ts +17 -2
  40. package/dist/internal/machine/planner.d.ts.map +1 -1
  41. package/dist/internal/machine/planner.js +137 -64
  42. package/dist/internal/machine/planner.js.map +1 -1
  43. package/dist/internal/machine/process.d.ts.map +1 -1
  44. package/dist/internal/machine/process.js +7 -5
  45. package/dist/internal/machine/process.js.map +1 -1
  46. package/dist/internal/machine/runtime.d.ts +29 -5
  47. package/dist/internal/machine/runtime.d.ts.map +1 -1
  48. package/dist/internal/machine/runtime.js +440 -64
  49. package/dist/internal/machine/runtime.js.map +1 -1
  50. package/dist/internal/machine/symbols.d.ts +2 -0
  51. package/dist/internal/machine/symbols.d.ts.map +1 -1
  52. package/dist/internal/machine/symbols.js +2 -0
  53. package/dist/internal/machine/symbols.js.map +1 -1
  54. package/dist/internal/machine/topology.d.ts +13 -0
  55. package/dist/internal/machine/topology.d.ts.map +1 -1
  56. package/dist/internal/machine/topology.js +9 -0
  57. package/dist/internal/machine/topology.js.map +1 -1
  58. package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
  59. package/dist/internal/testing/machine/finiteModel.js +3 -3
  60. package/dist/internal/testing/machine/finiteModel.js.map +1 -1
  61. package/dist/unstable/reactivity/AtomMachine.d.ts +9 -0
  62. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  63. package/dist/unstable/reactivity/AtomMachine.js +9 -0
  64. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  65. package/docs/agent-guide.md +130 -20
  66. package/package.json +5 -5
  67. package/src/Machine.ts +548 -217
  68. package/src/internal/machine/atom.ts +26 -0
  69. package/src/internal/machine/cluster.ts +2 -2
  70. package/src/internal/machine/commandRuntime.ts +2 -7
  71. package/src/internal/machine/configuration.ts +38 -13
  72. package/src/internal/machine/executionPlan.ts +85 -52
  73. package/src/internal/machine/initialization.ts +73 -0
  74. package/src/internal/machine/inspectionRuntime.ts +102 -0
  75. package/src/internal/machine/invocation.ts +38 -13
  76. package/src/internal/machine/machine.ts +31 -6
  77. package/src/internal/machine/planner.ts +166 -66
  78. package/src/internal/machine/process.ts +7 -5
  79. package/src/internal/machine/runtime.ts +663 -91
  80. package/src/internal/machine/symbols.ts +3 -0
  81. package/src/internal/machine/topology.ts +27 -0
  82. package/src/internal/testing/machine/finiteModel.ts +8 -7
  83. package/src/unstable/reactivity/AtomMachine.ts +12 -0
@@ -139,6 +139,32 @@ export const emissions = <State, Event, Error, Output, StartError, Emitted>(
139
139
  )
140
140
  }
141
141
 
142
+ export const inspection = <State, Event, Error, Output, StartError, Emitted>(
143
+ self: MachineAtom<State, Event, Error, Output, StartError, Emitted>
144
+ ): Stream.Stream<Machine.Inspection.Event, StartError, AtomRegistry.AtomRegistry> => {
145
+ const prepared = preparedByMachineAtom.get(self as object)
146
+ if (prepared === undefined) return Stream.empty
147
+ return Stream.unwrap(
148
+ Effect.gen(function*() {
149
+ const registry = yield* AtomRegistry.AtomRegistry
150
+ const releasePrepared = yield* Effect.sync(() => registry.mount(prepared))
151
+ yield* Effect.addFinalizer(() => Effect.sync(releasePrepared))
152
+ const machine = yield* Atom.getResult(prepared)
153
+ const pull = yield* Stream.toPull(machine.inspection)
154
+ const firstPull = yield* pull.pipe(Effect.forkScoped({ startImmediately: true }))
155
+ const releaseRef = yield* Effect.sync(() => registry.mount(self.ref))
156
+ yield* Effect.addFinalizer(() => Effect.sync(releaseRef))
157
+ yield* Atom.getResult(self.ref)
158
+ let first = true
159
+ return Stream.fromPull(Effect.succeed(Effect.suspend(() => {
160
+ if (!first) return pull
161
+ first = false
162
+ return Fiber.join(firstPull)
163
+ })))
164
+ })
165
+ )
166
+ }
167
+
142
168
  export const childEmissions = <Child extends Machine.ChildMachine.Any, StartError>(
143
169
  self: ChildMachineAtom<Child, StartError>
144
170
  ): Stream.Stream<RefEmitted<Machine.ChildMachine.Ref<Child>>, StartError, AtomRegistry.AtomRegistry> =>
@@ -317,7 +317,7 @@ export const make = <
317
317
  if (initial.commands.length > 0) {
318
318
  return yield* fail(
319
319
  "UnsupportedProcessLocal",
320
- "Machine actor commands require a managed local process"
320
+ "Machine commands require a managed local process"
321
321
  )
322
322
  }
323
323
  current = initial.state
@@ -329,7 +329,7 @@ export const make = <
329
329
  if (planned.commands.length > 0) {
330
330
  return yield* fail(
331
331
  "UnsupportedProcessLocal",
332
- "Machine actor commands require a managed local process"
332
+ "Machine commands require a managed local process"
333
333
  )
334
334
  }
335
335
  current = planned.next
@@ -10,16 +10,13 @@ import type { RuntimeCommand } from "./command.js"
10
10
  import { decodeEmit, decodeEvent } from "./protocol.js"
11
11
  import type { ProcessScope } from "./runtime.js"
12
12
 
13
- const isActorRef = (target: unknown): target is { readonly send: (event: unknown) => Effect.Effect<void, unknown> } =>
14
- typeof target === "object" && target !== null && "send" in target && typeof target.send === "function"
15
-
16
13
  export const makeLiveRuntime = <Events, Emits>(
17
14
  machine: Machine.Any,
18
15
  scope: ProcessScope<Events>
19
16
  ): Runtime<Events, Emits> => ({
20
17
  raise: (event) =>
21
18
  decodeEvent(machine, event).pipe(
22
- Effect.flatMap((event) => scope.self.send(event as Events))
19
+ Effect.flatMap((event) => scope.sendTo(scope.self, event as Events))
23
20
  ),
24
21
  emit: (event) =>
25
22
  decodeEmit(machine, event).pipe(
@@ -33,9 +30,7 @@ export const runCommands = <Event>(
33
30
  ) =>
34
31
  Effect.forEach(commands, (command) =>
35
32
  command._tag === "SendTo"
36
- ? isActorRef(command.target)
37
- ? command.target.send(command.event)
38
- : scope.sendTo(command.target as never, command.event)
33
+ ? scope.sendTo(command.target as never, command.event)
39
34
  : scope.stopChild(command.child as never), { discard: true })
40
35
 
41
36
  export const runEmittedEvents = <Events, Emits>(
@@ -8,7 +8,7 @@ import * as Cause from "effect/Cause"
8
8
  import * as Effect from "effect/Effect"
9
9
  import * as Option from "effect/Option"
10
10
  import { hasProperty } from "effect/Predicate"
11
- import type { ActorRef, Machine } from "../../Machine.js"
11
+ import type { Machine, MachineTarget } from "../../Machine.js"
12
12
  import { MachineSchemaDecodeError } from "./errors.js"
13
13
  import {
14
14
  decodeBoundary,
@@ -17,7 +17,14 @@ import {
17
17
  decodeStateValue,
18
18
  decodeStateValueSync
19
19
  } from "./protocol.js"
20
- import { getNode, getStateNodeSchema, isSnapshot, isTarget, TargetSnapshotTypeId } from "./topology.js"
20
+ import {
21
+ getNode,
22
+ getStateNodeSchema,
23
+ type InitialTarget as InitialTargetInstruction,
24
+ isSnapshot,
25
+ isTarget,
26
+ TargetSnapshotTypeId
27
+ } from "./topology.js"
21
28
 
22
29
  export interface HistoryRecord {
23
30
  readonly mode: "shallow" | "deep"
@@ -252,25 +259,25 @@ export interface ActiveConfiguration {
252
259
  readonly values: ReadonlyMap<string, unknown>
253
260
  readonly outputs: ReadonlyMap<string, unknown>
254
261
  readonly history: ReadonlyMap<string, HistoryRecord>
255
- readonly actorScope?: PlanningActorScope
262
+ readonly machineReferences?: PlanningMachineReferences
256
263
  }
257
264
 
258
- export interface PlanningActorScope {
259
- readonly self: ActorRef<any>
260
- readonly parent: ActorRef<any> | undefined
265
+ export interface PlanningMachineReferences {
266
+ readonly self: MachineTarget<any>
267
+ readonly parent: MachineTarget<any> | undefined
261
268
  }
262
269
 
263
- export const withActorScope = (
270
+ export const withMachineReferences = (
264
271
  configuration: ActiveConfiguration,
265
- actorScope: PlanningActorScope
272
+ machineReferences: PlanningMachineReferences
266
273
  ): ActiveConfiguration => ({
267
274
  ...configuration,
268
- actorScope: { self: actorScope.self, parent: actorScope.parent }
275
+ machineReferences: { self: machineReferences.self, parent: machineReferences.parent }
269
276
  })
270
277
 
271
- export const getActorScope = (
278
+ export const getMachineReferences = (
272
279
  configuration: ActiveConfiguration
273
- ): PlanningActorScope | undefined => configuration.actorScope
280
+ ): PlanningMachineReferences | undefined => configuration.machineReferences
274
281
 
275
282
  export interface FinalCompletion {
276
283
  readonly path: string
@@ -952,7 +959,8 @@ const configurationFromTargetPathSync = (
952
959
  current: ActiveConfiguration,
953
960
  path: string,
954
961
  value: unknown,
955
- providedValues: Readonly<Record<string, unknown>> | undefined
962
+ providedValues: Readonly<Record<string, unknown>> | undefined,
963
+ allowIncomplete = false
956
964
  ): ActiveConfiguration => {
957
965
  const node = getNode(machine, path)
958
966
  const active = new Set<string>()
@@ -998,12 +1006,29 @@ const configurationFromTargetPathSync = (
998
1006
  }
999
1007
  }
1000
1008
 
1001
- if (node.type === "compound" || node.type === "parallel") {
1009
+ if (!allowIncomplete && (node.type === "compound" || node.type === "parallel")) {
1002
1010
  throw new Error(`Machine target "${node.path}" must include an active child state`)
1003
1011
  }
1004
1012
  return { active, values, outputs, history: current.history }
1005
1013
  }
1006
1014
 
1015
+ /** Builds the selected path and its ancestors without requiring nested active
1016
+ * children. The planner completes those children through declared initial
1017
+ * topology and `initialize` handlers before normalization continues. */
1018
+ export const configurationFromInitialTargetSync = (
1019
+ machine: Machine.Any,
1020
+ current: ActiveConfiguration,
1021
+ target: InitialTargetInstruction
1022
+ ): ActiveConfiguration =>
1023
+ configurationFromTargetPathSync(
1024
+ machine,
1025
+ current,
1026
+ target.path,
1027
+ target.value,
1028
+ target.values as Readonly<Record<string, unknown>> | undefined,
1029
+ true
1030
+ )
1031
+
1007
1032
  const configurationFromTargetSnapshotSync = (
1008
1033
  machine: Machine.Any,
1009
1034
  current: ActiveConfiguration,
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import * as Effect from "effect/Effect"
8
- import type { ActorRef, Machine } from "../../Machine.js"
8
+ import type { Machine, MachineTarget } from "../../Machine.js"
9
9
  import { getTargetBuilder, type RuntimeCommand } from "./command.js"
10
10
  import {
11
11
  type ActiveConfiguration,
@@ -18,10 +18,10 @@ import {
18
18
  isDescendantOf,
19
19
  normalizeConfigurationSync,
20
20
  normalizeTargetConfigurationSync,
21
- type PlanningActorScope,
21
+ type PlanningMachineReferences,
22
22
  snapshotFromConfiguration,
23
23
  validateInitialConfiguration,
24
- withActorScope
24
+ withMachineReferences
25
25
  } from "./configuration.js"
26
26
  import { InfiniteTransitionError, StoppedError } from "./errors.js"
27
27
  import * as InvocationEvent from "./invocationEvent.js"
@@ -38,6 +38,7 @@ import {
38
38
  normalizeTransition,
39
39
  planConfiguration,
40
40
  removeConflictingTransitions,
41
+ resolveInitialTarget,
41
42
  type SelectedTransition,
42
43
  sortEntryPaths,
43
44
  sortEvaluatedTransitions,
@@ -46,7 +47,7 @@ import {
46
47
  validateDeclaredTransitionTarget
47
48
  } from "./planner.js"
48
49
  import { decodeEmitSync, decodeEventSync, decodeInputSync, decodeStateValueSync } from "./protocol.js"
49
- import { isNoTarget, isSnapshot, isTarget, TargetSnapshotTypeId } from "./topology.js"
50
+ import { isInitialTarget, isNoTarget, isSnapshot, isTarget, TargetSnapshotTypeId } from "./topology.js"
50
51
 
51
52
  interface IndexedExecutionDescriptor {
52
53
  readonly flat: boolean
@@ -67,31 +68,31 @@ interface IndexedExecutionDescriptor {
67
68
  >
68
69
  }
69
70
 
70
- const planningActorScopes = new WeakMap<Machine.Any, PlanningActorScope>()
71
+ const planningMachineReferences = new WeakMap<Machine.Any, PlanningMachineReferences>()
71
72
 
72
- const getPlanningActorScope = (machine: Machine.Any): PlanningActorScope => {
73
- const cached = planningActorScopes.get(machine)
73
+ const getPlanningMachineReferences = (machine: Machine.Any): PlanningMachineReferences => {
74
+ const cached = planningMachineReferences.get(machine)
74
75
  if (cached !== undefined) return cached
75
- const self: ActorRef<unknown> = {
76
+ const self: MachineTarget<unknown> = {
76
77
  id: machine.id ?? "Machine",
77
78
  sessionId: "Machine.plan",
78
79
  send: () => Effect.fail(new StoppedError())
79
80
  }
80
81
  const scope = { self, parent: undefined }
81
- planningActorScopes.set(machine, scope)
82
+ planningMachineReferences.set(machine, scope)
82
83
  return scope
83
84
  }
84
85
 
85
- const getActorContext = (
86
+ const resolveMachineReferences = (
86
87
  machine: Machine.Any,
87
- actorScope: PlanningActorScope | undefined
88
- ): PlanningActorScope =>
89
- actorScope === undefined
90
- ? getPlanningActorScope(machine)
91
- : { self: actorScope.self, parent: actorScope.parent }
88
+ machineReferences: PlanningMachineReferences | undefined
89
+ ): PlanningMachineReferences =>
90
+ machineReferences === undefined
91
+ ? getPlanningMachineReferences(machine)
92
+ : { self: machineReferences.self, parent: machineReferences.parent }
92
93
 
93
94
  const indexedStateConfigKeys: ReadonlySet<PropertyKey> = new Set([
94
- "initial",
95
+ "initialize",
95
96
  "invoke",
96
97
  "on",
97
98
  "output"
@@ -363,7 +364,7 @@ const makeIndexedTransitionContext = (
363
364
  configuration: OwnedIndexedState,
364
365
  sourceIndex: number,
365
366
  event: any,
366
- actorScope?: PlanningActorScope
367
+ machineReferences?: PlanningMachineReferences
367
368
  ): any => {
368
369
  const source = descriptor.nodes[sourceIndex]!
369
370
  const parentIndex = descriptor.parentIndices[sourceIndex]!
@@ -372,7 +373,7 @@ const makeIndexedTransitionContext = (
372
373
  ancestors[descriptor.nodes[ancestorIndex]!.path] = configuration.values[ancestorIndex]
373
374
  }
374
375
  return {
375
- ...getActorContext(machine, actorScope),
376
+ ...resolveMachineReferences(machine, machineReferences),
376
377
  state: configuration.values[sourceIndex],
377
378
  containingState: parentIndex < 0 ? undefined : configuration.values[parentIndex],
378
379
  ancestors,
@@ -402,6 +403,7 @@ const emptyExecutionValues: ReadonlyArray<never> = Object.freeze([])
402
403
  export interface ExecutionMicrostep<State = unknown> {
403
404
  readonly next: State
404
405
  readonly event: unknown
406
+ readonly transitions?: ReadonlyArray<Machine.RetainedTransition>
405
407
  readonly commands: ReadonlyArray<RuntimeCommand>
406
408
  readonly raisedEvents: ReadonlyArray<unknown>
407
409
  readonly emittedEvents: ReadonlyArray<unknown>
@@ -454,7 +456,7 @@ const selectIndexedEventTransitions = (
454
456
  descriptor: IndexedExecutionDescriptor,
455
457
  configuration: OwnedIndexedState,
456
458
  event: any,
457
- actorScope?: PlanningActorScope
459
+ machineReferences?: PlanningMachineReferences
458
460
  ): ReadonlyArray<IndexedSelectedTransition> => {
459
461
  const selected: Array<IndexedSelectedTransition> = []
460
462
  for (const leafIndex of configuration.activeLeaves) {
@@ -476,7 +478,7 @@ const selectIndexedEventTransitions = (
476
478
  configuration,
477
479
  sourceIndex,
478
480
  event,
479
- actorScope
481
+ machineReferences
480
482
  )
481
483
  })
482
484
  }
@@ -530,13 +532,22 @@ const collectIndexedEvaluatedTransition = (
530
532
  selection: IndexedSelectedTransition
531
533
  ): IndexedEvaluatedTransition => {
532
534
  const transitionResult = collectIndexedTransition(machine, selection.transition.transition, selection.context)
533
- const target = transitionResult.state
535
+ const unresolvedTarget = transitionResult.state
534
536
  validateDeclaredTransitionTarget(
535
537
  selection.sourcePath,
536
538
  selection.trigger,
537
539
  selection.transition.targets,
538
- target
540
+ unresolvedTarget
539
541
  )
542
+ const initialResolution = unresolvedTarget !== undefined && isInitialTarget(unresolvedTarget)
543
+ ? resolveInitialTarget(
544
+ machine,
545
+ activeConfigurationFromIndexedState(descriptor, state),
546
+ unresolvedTarget,
547
+ (selection.context as any).event
548
+ )
549
+ : undefined
550
+ const target = initialResolution?.target ?? unresolvedTarget
540
551
  if (target !== undefined && !isTarget(target) && !isSnapshot(target)) {
541
552
  throw new Error("Machine expected indexed transition target to be a snapshot or target builder result")
542
553
  }
@@ -547,16 +558,16 @@ const collectIndexedEvaluatedTransition = (
547
558
  if (!changed) {
548
559
  return {
549
560
  selection,
550
- unresolvedTarget: target as any,
561
+ unresolvedTarget: unresolvedTarget as any,
551
562
  target: target as any,
552
563
  next,
553
- commands: transitionResult.commands,
554
- raisedEvents: transitionResult.raisedEvents,
555
- emittedEvents: transitionResult.emittedEvents,
564
+ commands: [...transitionResult.commands, ...(initialResolution?.commands ?? [])],
565
+ raisedEvents: [...transitionResult.raisedEvents, ...(initialResolution?.raisedEvents ?? [])],
566
+ emittedEvents: [...transitionResult.emittedEvents, ...(initialResolution?.emittedEvents ?? [])],
556
567
  changed: false,
557
568
  exitPaths: [],
558
569
  entryPaths: [],
559
- choiceTransitions: []
570
+ choiceTransitions: initialResolution?.transitions ?? []
560
571
  }
561
572
  }
562
573
 
@@ -570,16 +581,16 @@ const collectIndexedEvaluatedTransition = (
570
581
  : naturalBoundary
571
582
  return {
572
583
  selection,
573
- unresolvedTarget: target as any,
584
+ unresolvedTarget: unresolvedTarget as any,
574
585
  target: target as any,
575
586
  next,
576
- commands: transitionResult.commands,
577
- raisedEvents: transitionResult.raisedEvents,
578
- emittedEvents: transitionResult.emittedEvents,
587
+ commands: [...transitionResult.commands, ...(initialResolution?.commands ?? [])],
588
+ raisedEvents: [...transitionResult.raisedEvents, ...(initialResolution?.raisedEvents ?? [])],
589
+ emittedEvents: [...transitionResult.emittedEvents, ...(initialResolution?.emittedEvents ?? [])],
579
590
  changed: true,
580
591
  exitPaths: getExitPaths(machine, activeConfigurationFromIndexedState(descriptor, state), boundary),
581
592
  entryPaths: getEntryPaths(machine, activeConfigurationFromIndexedState(descriptor, next), boundary),
582
- choiceTransitions: []
593
+ choiceTransitions: initialResolution?.transitions ?? []
583
594
  }
584
595
  }
585
596
 
@@ -588,13 +599,22 @@ const indexedMicrostep = (
588
599
  descriptor: IndexedExecutionDescriptor,
589
600
  state: OwnedIndexedState,
590
601
  event: any,
591
- selections: ReadonlyArray<IndexedSelectedTransition>
602
+ selections: ReadonlyArray<IndexedSelectedTransition>,
603
+ retainTransitions: boolean
592
604
  ): ExecutionMicrostep<OwnedIndexedState> => {
605
+ const retain = (transition: IndexedEvaluatedTransition): Machine.RetainedTransition => ({
606
+ source: transition.selection.sourcePath,
607
+ trigger: transition.selection.trigger,
608
+ reenter: transition.selection.transition.reenter,
609
+ target: transition.unresolvedTarget === undefined ? undefined : getTargetNodePath(transition.unresolvedTarget),
610
+ resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target)
611
+ })
593
612
  if (selections.length === 1) {
594
613
  const transition = collectIndexedEvaluatedTransition(machine, descriptor, state, selections[0]!)
595
614
  return {
596
615
  next: transition.next,
597
616
  event,
617
+ ...(retainTransitions ? { transitions: [retain(transition)] } : undefined),
598
618
  commands: transition.commands,
599
619
  raisedEvents: transition.raisedEvents,
600
620
  emittedEvents: transition.emittedEvents,
@@ -645,6 +665,7 @@ const indexedMicrostep = (
645
665
  return {
646
666
  next,
647
667
  event,
668
+ ...(retainTransitions ? { transitions: transitions.map(retain) } : undefined),
648
669
  commands,
649
670
  raisedEvents,
650
671
  emittedEvents,
@@ -660,7 +681,7 @@ const planIndexedFlatState = (
660
681
  configuration: OwnedIndexedState,
661
682
  decoded: { readonly _tag: PropertyKey },
662
683
  retainMicrosteps: boolean,
663
- actorScope?: PlanningActorScope
684
+ machineReferences?: PlanningMachineReferences
664
685
  ): ExecutionMacrostep<OwnedIndexedState> => {
665
686
  let current = configuration
666
687
  let event: any = decoded
@@ -712,7 +733,7 @@ const planIndexedFlatState = (
712
733
  machine,
713
734
  transition.transition,
714
735
  {
715
- ...getActorContext(machine, actorScope),
736
+ ...resolveMachineReferences(machine, machineReferences),
716
737
  state: current.values[sourceIndex],
717
738
  containingState: undefined,
718
739
  ancestors: {},
@@ -762,6 +783,17 @@ const planIndexedFlatState = (
762
783
  const step: ExecutionMicrostep<OwnedIndexedState> = {
763
784
  next,
764
785
  event,
786
+ ...(retainMicrosteps
787
+ ? {
788
+ transitions: [{
789
+ source: sourcePath,
790
+ trigger: { type: "event", event: event._tag },
791
+ reenter: transition.reenter,
792
+ target: target === undefined ? undefined : getTargetNodePath(target as any),
793
+ resolvedTarget: target === undefined ? undefined : getTargetNodePath(target as any)
794
+ }]
795
+ }
796
+ : undefined),
765
797
  commands: transitionResult.commands,
766
798
  raisedEvents: transitionResult.raisedEvents,
767
799
  emittedEvents: transitionResult.emittedEvents,
@@ -807,7 +839,7 @@ const planIndexedState = (
807
839
  configuration: OwnedIndexedState,
808
840
  input: unknown,
809
841
  retainMicrosteps: boolean,
810
- actorScope?: PlanningActorScope
842
+ machineReferences?: PlanningMachineReferences
811
843
  ): ExecutionMacrostep<OwnedIndexedState> => {
812
844
  if (InvocationEvent.isInvocationEvent(input)) {
813
845
  // Invocation lifecycle transitions retain the generic planner as their
@@ -816,9 +848,9 @@ const planIndexedState = (
816
848
  // the representation boundary.
817
849
  const planned = planConfiguration(
818
850
  machine as any,
819
- actorScope === undefined
851
+ machineReferences === undefined
820
852
  ? activeConfigurationFromIndexedState(descriptor, configuration)
821
- : withActorScope(activeConfigurationFromIndexedState(descriptor, configuration), actorScope),
853
+ : withMachineReferences(activeConfigurationFromIndexedState(descriptor, configuration), machineReferences),
822
854
  input
823
855
  )
824
856
  return {
@@ -828,6 +860,7 @@ const planIndexedState = (
828
860
  microsteps: planned.microsteps.map((step) => ({
829
861
  next: ownedIndexedStateFromActive(descriptor, step.next),
830
862
  event: step.event,
863
+ transitions: step.transitions,
831
864
  commands: step.commands,
832
865
  raisedEvents: step.raisedEvents,
833
866
  emittedEvents: step.emittedEvents,
@@ -841,7 +874,7 @@ const planIndexedState = (
841
874
  }
842
875
  const decoded = decodeEventSync(machine, input)
843
876
  if (descriptor.flat) {
844
- return planIndexedFlatState(machine, descriptor, configuration, decoded, retainMicrosteps, actorScope)
877
+ return planIndexedFlatState(machine, descriptor, configuration, decoded, retainMicrosteps, machineReferences)
845
878
  }
846
879
  if (descriptor.finalIndices.some((index) => configuration.active[index] === 1)) {
847
880
  const active = activeConfigurationFromIndexedState(descriptor, configuration)
@@ -862,7 +895,7 @@ const planIndexedState = (
862
895
  }
863
896
  }
864
897
 
865
- const selections = selectIndexedEventTransitions(machine, descriptor, configuration, decoded, actorScope)
898
+ const selections = selectIndexedEventTransitions(machine, descriptor, configuration, decoded, machineReferences)
866
899
  if (selections.length === 0) {
867
900
  return {
868
901
  next: configuration,
@@ -874,7 +907,7 @@ const planIndexedState = (
874
907
  }
875
908
  }
876
909
 
877
- const first = indexedMicrostep(machine, descriptor, configuration, decoded, selections)
910
+ const first = indexedMicrostep(machine, descriptor, configuration, decoded, selections, retainMicrosteps)
878
911
  let current = first.next
879
912
  let currentEvent: any = decoded
880
913
  const commands = [...first.commands]
@@ -930,9 +963,9 @@ const planIndexedState = (
930
963
  }
931
964
  raisedIndex += 1
932
965
  currentEvent = raised
933
- const raisedSelections = selectIndexedEventTransitions(machine, descriptor, current, raised, actorScope)
966
+ const raisedSelections = selectIndexedEventTransitions(machine, descriptor, current, raised, machineReferences)
934
967
  if (raisedSelections.length === 0) continue
935
- const step = indexedMicrostep(machine, descriptor, current, raised, raisedSelections)
968
+ const step = indexedMicrostep(machine, descriptor, current, raised, raisedSelections, retainMicrosteps)
936
969
  current = step.next
937
970
  commands.push(...step.commands)
938
971
  raisedEvents.push(...step.raisedEvents)
@@ -949,11 +982,11 @@ export interface CompiledExecutionPlan {
949
982
  state: unknown,
950
983
  event: unknown,
951
984
  retainMicrosteps?: boolean,
952
- actorScope?: PlanningActorScope
985
+ machineReferences?: PlanningMachineReferences
953
986
  ) => ExecutionMacrostep
954
987
  readonly initial?: (
955
988
  args: ReadonlyArray<unknown>,
956
- actorScope?: PlanningActorScope
989
+ machineReferences?: PlanningMachineReferences
957
990
  ) => {
958
991
  readonly state: Machine.Snapshot<any>
959
992
  readonly configuration: unknown
@@ -970,12 +1003,12 @@ const makeActiveExecutionPlan = (machine: Machine.Any): CompiledExecutionPlan =>
970
1003
  fromConfiguration: (configuration) => configuration,
971
1004
  toConfiguration: (state) => state as ActiveConfiguration,
972
1005
  snapshot: (state) => snapshotFromConfiguration(machine, state as ActiveConfiguration),
973
- plan: (state, event, _retainMicrosteps, actorScope) =>
1006
+ plan: (state, event, _retainMicrosteps, machineReferences) =>
974
1007
  planConfiguration(
975
1008
  machine as any,
976
- actorScope === undefined
1009
+ machineReferences === undefined
977
1010
  ? state as ActiveConfiguration
978
- : withActorScope(state as ActiveConfiguration, actorScope),
1011
+ : withMachineReferences(state as ActiveConfiguration, machineReferences),
979
1012
  event as any
980
1013
  )
981
1014
  })
@@ -987,9 +1020,9 @@ const makeIndexedExecutionPlan = (
987
1020
  fromConfiguration: (configuration) => ownedIndexedStateFromActive(indexed, configuration),
988
1021
  toConfiguration: (state) => activeConfigurationFromIndexedState(indexed, state as OwnedIndexedState),
989
1022
  snapshot: (state) => snapshotFromIndexedState(indexed, state as OwnedIndexedState),
990
- plan: (state, event, retainMicrosteps = false, actorScope) =>
991
- planIndexedState(machine, indexed, state as OwnedIndexedState, event, retainMicrosteps, actorScope),
992
- initial: (args, actorScope) => {
1023
+ plan: (state, event, retainMicrosteps = false, machineReferences) =>
1024
+ planIndexedState(machine, indexed, state as OwnedIndexedState, event, retainMicrosteps, machineReferences),
1025
+ initial: (args, machineReferences) => {
993
1026
  const inputArgs = machine.input === undefined
994
1027
  ? args
995
1028
  : args.length === 0
@@ -997,7 +1030,7 @@ const makeIndexedExecutionPlan = (
997
1030
  : [decodeInputSync(machine, machine.input, args[0])]
998
1031
  const initial = machine.initial(...inputArgs as any)
999
1032
  const normalized = normalizeConfigurationSync(machine, initial as Machine.Snapshot<any>)
1000
- const active = actorScope === undefined ? normalized : withActorScope(normalized, actorScope)
1033
+ const active = machineReferences === undefined ? normalized : withMachineReferences(normalized, machineReferences)
1001
1034
  validateInitialConfiguration(machine, active)
1002
1035
  const completed = completeConfigurationSync(machine, active, InitialEvent).configuration
1003
1036
  const configuration = ownedIndexedStateFromActive(indexed, completed)
@@ -0,0 +1,73 @@
1
+ /** Internal builders for schema-valued default state entry. */
2
+
3
+ import { hasProperty } from "effect/Predicate"
4
+ import type { Machine } from "../../Machine.js"
5
+ import { SnapshotBuilderStateTypeId } from "./symbols.js"
6
+ import * as Topology from "./topology.js"
7
+
8
+ const getNode = (machine: Machine.Any, path: string): Machine.StateNode => {
9
+ const node = machine.stateNodes.byPath.get(path)
10
+ if (node === undefined) {
11
+ throw new Error(`Machine expected state path "${path}" to exist`)
12
+ }
13
+ return node
14
+ }
15
+
16
+ const withFrom = <Method extends (value: unknown) => unknown>(method: Method) => {
17
+ Object.defineProperty(method, "from", {
18
+ value: (...args: ReadonlyArray<unknown>) => method(Topology.makeStateInput(args.length === 0 ? {} : args[0])),
19
+ enumerable: false
20
+ })
21
+ return method as Method & { readonly from: (...args: ReadonlyArray<unknown>) => unknown }
22
+ }
23
+
24
+ const makeCompletion = (values: Readonly<Record<string, unknown>>): object => {
25
+ const completion = {}
26
+ Object.defineProperty(completion, SnapshotBuilderStateTypeId, {
27
+ value: values,
28
+ enumerable: false
29
+ })
30
+ return completion
31
+ }
32
+
33
+ const makeParallelBuilder = (
34
+ machine: Machine.Any,
35
+ node: Machine.StateNode,
36
+ values: Readonly<Record<string, unknown>>
37
+ ): object => {
38
+ const builder = makeCompletion(values) as Record<string | symbol, unknown>
39
+ for (const childPath of node.children) {
40
+ const child = getNode(machine, childPath)
41
+ if (child.schema === undefined || hasProperty(values, child.key)) continue
42
+ builder[child.key] = withFrom((value: unknown) => {
43
+ const nextValues: Record<string, unknown> = Object.assign({}, values)
44
+ nextValues[child.key] = value
45
+ return makeParallelBuilder(machine, node, nextValues)
46
+ })
47
+ }
48
+ return builder
49
+ }
50
+
51
+ export const makeStateInitializeBuilder = (machine: Machine.Any, path: string): object => {
52
+ const node = getNode(machine, path)
53
+ if (node.type === "parallel") {
54
+ return makeParallelBuilder(machine, node, {})
55
+ }
56
+ if (node.type !== "compound") {
57
+ throw new Error(`Machine state "${path}" cannot initialize child states`)
58
+ }
59
+ const child = getNode(machine, node.initial)
60
+ if (child.schema === undefined) {
61
+ throw new Error(`Machine state "${path}" has no schema-valued initial child`)
62
+ }
63
+ return withFrom((value: unknown) => makeCompletion({ [child.key]: value }))
64
+ }
65
+
66
+ export const getStateInitializeValues = (path: string, result: unknown): Readonly<Record<string, unknown>> => {
67
+ if (typeof result !== "object" || result === null || !hasProperty(result, SnapshotBuilderStateTypeId)) {
68
+ throw new Error(`Machine initialize handler for "${path}" must return its builder result`)
69
+ }
70
+ return (result as { readonly [SnapshotBuilderStateTypeId]: Readonly<Record<string, unknown>> })[
71
+ SnapshotBuilderStateTypeId
72
+ ]
73
+ }