@typeonce/effect-machine 0.15.0 → 0.17.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 (85) hide show
  1. package/README.md +115 -95
  2. package/dist/Machine.d.ts +464 -337
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +72 -107
  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/atom.d.ts +7 -7
  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/cluster.d.ts +2 -2
  14. package/dist/internal/machine/cluster.d.ts.map +1 -1
  15. package/dist/internal/machine/cluster.js +6 -5
  16. package/dist/internal/machine/cluster.js.map +1 -1
  17. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  18. package/dist/internal/machine/executionPlan.js +16 -4
  19. package/dist/internal/machine/executionPlan.js.map +1 -1
  20. package/dist/internal/machine/invocation.d.ts +1 -1
  21. package/dist/internal/machine/invocation.d.ts.map +1 -1
  22. package/dist/internal/machine/invocation.js +25 -3
  23. package/dist/internal/machine/invocation.js.map +1 -1
  24. package/dist/internal/machine/invocationEvent.d.ts +8 -0
  25. package/dist/internal/machine/invocationEvent.d.ts.map +1 -1
  26. package/dist/internal/machine/invocationEvent.js +8 -0
  27. package/dist/internal/machine/invocationEvent.js.map +1 -1
  28. package/dist/internal/machine/machine.d.ts +8 -6
  29. package/dist/internal/machine/machine.d.ts.map +1 -1
  30. package/dist/internal/machine/machine.js +308 -71
  31. package/dist/internal/machine/machine.js.map +1 -1
  32. package/dist/internal/machine/planner.d.ts +26 -4
  33. package/dist/internal/machine/planner.d.ts.map +1 -1
  34. package/dist/internal/machine/planner.js +76 -31
  35. package/dist/internal/machine/planner.js.map +1 -1
  36. package/dist/internal/machine/runtime.d.ts +1 -0
  37. package/dist/internal/machine/runtime.d.ts.map +1 -1
  38. package/dist/internal/machine/runtime.js +25 -16
  39. package/dist/internal/machine/runtime.js.map +1 -1
  40. package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
  41. package/dist/internal/machine/stateDefinition.js +14 -0
  42. package/dist/internal/machine/stateDefinition.js.map +1 -1
  43. package/dist/internal/machine/topology.d.ts +20 -0
  44. package/dist/internal/machine/topology.d.ts.map +1 -1
  45. package/dist/internal/machine/topology.js +33 -6
  46. package/dist/internal/machine/topology.js.map +1 -1
  47. package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
  48. package/dist/internal/testing/machine/finiteModel.js +15 -20
  49. package/dist/internal/testing/machine/finiteModel.js.map +1 -1
  50. package/dist/internal/testing/machine/transitionCoverage.d.ts.map +1 -1
  51. package/dist/internal/testing/machine/transitionCoverage.js +8 -2
  52. package/dist/internal/testing/machine/transitionCoverage.js.map +1 -1
  53. package/dist/internal/testing/machine/verification.d.ts.map +1 -1
  54. package/dist/internal/testing/machine/verification.js +6 -0
  55. package/dist/internal/testing/machine/verification.js.map +1 -1
  56. package/dist/testing/MachineTest.d.ts +13 -12
  57. package/dist/testing/MachineTest.d.ts.map +1 -1
  58. package/dist/testing/MachineTest.js +5 -8
  59. package/dist/testing/MachineTest.js.map +1 -1
  60. package/dist/unstable/cluster/ClusterMachine.d.ts +2 -2
  61. package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
  62. package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
  63. package/dist/unstable/reactivity/AtomMachine.d.ts +12 -12
  64. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  65. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  66. package/docs/agent-guide.md +182 -159
  67. package/package.json +1 -1
  68. package/src/Machine.ts +1073 -700
  69. package/src/internal/machine/activities.ts +7 -0
  70. package/src/internal/machine/atom.ts +20 -15
  71. package/src/internal/machine/cluster.ts +14 -9
  72. package/src/internal/machine/executionPlan.ts +14 -4
  73. package/src/internal/machine/invocation.ts +39 -4
  74. package/src/internal/machine/invocationEvent.ts +16 -0
  75. package/src/internal/machine/machine.ts +460 -86
  76. package/src/internal/machine/planner.ts +106 -30
  77. package/src/internal/machine/runtime.ts +61 -25
  78. package/src/internal/machine/stateDefinition.ts +39 -0
  79. package/src/internal/machine/topology.ts +59 -2
  80. package/src/internal/testing/machine/finiteModel.ts +18 -21
  81. package/src/internal/testing/machine/transitionCoverage.ts +8 -2
  82. package/src/internal/testing/machine/verification.ts +11 -0
  83. package/src/testing/MachineTest.ts +16 -11
  84. package/src/unstable/cluster/ClusterMachine.ts +8 -4
  85. package/src/unstable/reactivity/AtomMachine.ts +19 -12
@@ -7,13 +7,7 @@
7
7
  import * as Cause from "effect/Cause"
8
8
  import * as Effect from "effect/Effect"
9
9
  import type * as Schema from "effect/Schema"
10
- import type {
11
- Enqueue,
12
- InitialEvent as MachineInitialEvent,
13
- Machine,
14
- MachineReferences,
15
- MachineTarget
16
- } from "../../Machine.js"
10
+ import type { Enqueue, InitialEvent as MachineInitialEvent, Machine, MachineTarget } from "../../Machine.js"
17
11
  import { getTargetBuilder, makeCollector, type RuntimeCommand } from "./command.js"
18
12
  import {
19
13
  type ActiveConfiguration,
@@ -53,6 +47,7 @@ import {
53
47
  getNode,
54
48
  type InitialTarget as InitialTargetInstruction,
55
49
  isChoiceTarget,
50
+ isDeclined,
56
51
  isHistoryTarget,
57
52
  isInitialTarget,
58
53
  isNoTarget,
@@ -71,6 +66,7 @@ export type MicrostepPlan<State, Event, E, R> = {
71
66
  readonly trigger: Machine.TransitionTrigger
72
67
  readonly reenter: boolean
73
68
  readonly branchIndex: number
69
+ readonly branchKey: string | undefined
74
70
  readonly target: string | undefined
75
71
  readonly resolvedTarget: string | undefined
76
72
  }>
@@ -103,11 +99,12 @@ export type MacrostepPlan<State, Event, E, R, Output> =
103
99
  export type TransitionHandler<States extends Machine.StateSchemas, E, R, Context> = (
104
100
  context: Context,
105
101
  enqueue: Enqueue<any, any>
106
- ) => Machine.HandlerResult<States, E, R>
102
+ ) => Machine.HandlerResult<States, E, R> | Machine.Declined
107
103
 
108
104
  type TransitionEvaluation<States extends Machine.StateSchemas, E, R> = {
109
- readonly result: Machine.HandlerResult<States, E, R>
105
+ readonly result: Machine.HandlerResult<States, E, R> | Machine.Declined
110
106
  readonly branchIndex: number
107
+ readonly branchKey: string | undefined
111
108
  }
112
109
 
113
110
  type TransitionEvaluator<States extends Machine.StateSchemas, E, R, Context> = (
@@ -115,24 +112,42 @@ type TransitionEvaluator<States extends Machine.StateSchemas, E, R, Context> = (
115
112
  enqueue: Enqueue<any, any>
116
113
  ) => TransitionEvaluation<States, E, R>
117
114
 
118
- const rootMachineReferences = new WeakMap<Machine.Any, MachineReferences<any, any>>()
119
- const scopedMachineReferences = new WeakMap<Machine.Any, MachineReferences<any, any>>()
115
+ type PlanningMachineReferences = {
116
+ readonly self: MachineTarget<any>
117
+ readonly parent: MachineTarget<any> | undefined
118
+ }
119
+
120
+ type BehaviorMachineReferences = {
121
+ readonly self: MachineTarget<any>
122
+ readonly parent?: MachineTarget<any> | undefined
123
+ }
124
+
125
+ const rootMachineReferences = new WeakMap<Machine.Any, BehaviorMachineReferences>()
126
+ const scopedMachineReferences = new WeakMap<Machine.Any, BehaviorMachineReferences>()
127
+
128
+ const exposeMachineReferences = (
129
+ machine: Machine.Any,
130
+ machineReferences: PlanningMachineReferences
131
+ ): BehaviorMachineReferences =>
132
+ machine.parent === undefined
133
+ ? { self: machineReferences.self }
134
+ : { self: machineReferences.self, parent: machineReferences.parent }
120
135
 
121
136
  export const withMachineReferences = (
122
137
  machine: Machine.Any,
123
- machineReferences: MachineReferences<any, any>
138
+ machineReferences: PlanningMachineReferences
124
139
  ): Machine.Any => {
125
140
  const scoped = Object.create(machine) as Machine.Any
126
- scopedMachineReferences.set(scoped, { self: machineReferences.self, parent: machineReferences.parent })
141
+ scopedMachineReferences.set(scoped, exposeMachineReferences(machine, machineReferences))
127
142
  return scoped
128
143
  }
129
144
 
130
145
  const resolveMachineReferences = (
131
146
  machine: Machine.Any,
132
147
  configuration: ActiveConfiguration
133
- ): MachineReferences<any, any> => {
148
+ ): BehaviorMachineReferences => {
134
149
  const scope = configuration.machineReferences
135
- if (scope !== undefined) return scope
150
+ if (scope !== undefined) return exposeMachineReferences(machine, scope)
136
151
  const machineScope = scopedMachineReferences.get(machine)
137
152
  if (machineScope !== undefined) return machineScope
138
153
  const cached = rootMachineReferences.get(machine)
@@ -142,7 +157,7 @@ const resolveMachineReferences = (
142
157
  sessionId: "Machine.plan",
143
158
  send: () => Effect.fail(new StoppedError())
144
159
  }
145
- const root = { self, parent: undefined }
160
+ const root = exposeMachineReferences(machine, { self, parent: undefined })
146
161
  rootMachineReferences.set(machine, root)
147
162
  return root
148
163
  }
@@ -151,6 +166,7 @@ type EventTransition<States extends Machine.StateSchemas, E, R, Context> =
151
166
  | TransitionHandler<States, E, R, Context>
152
167
  | {
153
168
  readonly reenter?: boolean
169
+ readonly declinable?: boolean
154
170
  readonly targets?: ReadonlyArray<string>
155
171
  readonly transition: TransitionHandler<States, E, R, Context>
156
172
  readonly evaluate?: TransitionEvaluator<States, E, R, Context>
@@ -158,6 +174,7 @@ type EventTransition<States extends Machine.StateSchemas, E, R, Context> =
158
174
 
159
175
  export type MicrostepTransition<States extends Machine.StateSchemas, E, R, Context> = {
160
176
  readonly reenter: boolean
177
+ readonly declinable: boolean
161
178
  readonly targets: ReadonlyArray<string> | undefined
162
179
  readonly transition: TransitionHandler<States, E, R, Context>
163
180
  readonly evaluate: TransitionEvaluator<States, E, R, Context> | undefined
@@ -170,9 +187,10 @@ export const normalizeTransition = <States extends Machine.StateSchemas, E, R, C
170
187
  return undefined
171
188
  }
172
189
  return typeof transition === "function"
173
- ? { reenter: false, targets: undefined, transition, evaluate: undefined }
190
+ ? { reenter: false, declinable: false, targets: undefined, transition, evaluate: undefined }
174
191
  : {
175
192
  reenter: transition.reenter === true,
193
+ declinable: transition.declinable === true,
176
194
  targets: transition.targets,
177
195
  transition: transition.transition,
178
196
  evaluate: transition.evaluate
@@ -214,11 +232,16 @@ const collectTransition = <
214
232
  ) => {
215
233
  const collected = makeCollector<Event>(machine)
216
234
  const evaluated = evaluate === undefined
217
- ? { result: transition(context, collected.enqueue), branchIndex: 0 }
235
+ ? { result: transition(context, collected.enqueue), branchIndex: 0, branchKey: undefined }
218
236
  : evaluate(context, collected.enqueue)
237
+ if (isDeclined(evaluated.result)) {
238
+ return { declined: true as const }
239
+ }
219
240
  return {
241
+ declined: false as const,
220
242
  state: isNoTarget(evaluated.result) ? undefined : evaluated.result,
221
243
  branchIndex: evaluated.branchIndex,
244
+ branchKey: evaluated.branchKey,
222
245
  commands: collected.commands,
223
246
  raisedEvents: collected.raisedEvents,
224
247
  emittedEvents: collected.emittedEvents
@@ -518,11 +541,35 @@ export type SelectedTransition<States extends Machine.StateSchemas, E, R, Contex
518
541
  readonly trigger: Machine.TransitionTrigger
519
542
  readonly transition: MicrostepTransition<States, E, R, Context>
520
543
  readonly context: Context
544
+ readonly collected?: {
545
+ readonly declined: false
546
+ readonly state: unknown
547
+ readonly branchIndex: number
548
+ readonly branchKey: string | undefined
549
+ readonly commands: ReadonlyArray<RuntimeCommand>
550
+ readonly raisedEvents: ReadonlyArray<unknown>
551
+ readonly emittedEvents: ReadonlyArray<unknown>
552
+ }
553
+ }
554
+
555
+ const resolveDeclinableCandidate = <States extends Machine.StateSchemas, E, R, Context>(
556
+ machine: Machine.Any,
557
+ selection: SelectedTransition<States, E, R, Context>
558
+ ): SelectedTransition<States, E, R, Context> | undefined => {
559
+ if (!selection.transition.declinable) return selection
560
+ const collected = collectTransition(
561
+ machine,
562
+ selection.transition.transition,
563
+ selection.context,
564
+ selection.transition.evaluate
565
+ )
566
+ return collected.declined ? undefined : { ...selection, collected }
521
567
  }
522
568
 
523
569
  export type EvaluatedTransition<States extends Machine.StateSchemas, Event, E, R, Context> = {
524
570
  readonly selection: SelectedTransition<States, E, R, Context>
525
571
  readonly branchIndex: number
572
+ readonly branchKey: string | undefined
526
573
  readonly unresolvedTarget:
527
574
  | Machine.Snapshot<States>
528
575
  | Machine.Target<States, Machine.StateIdentifier<States>>
@@ -544,6 +591,7 @@ export type EvaluatedTransition<States extends Machine.StateSchemas, Event, E, R
544
591
  readonly trigger: Machine.TransitionTrigger
545
592
  readonly reenter: false
546
593
  readonly branchIndex: number
594
+ readonly branchKey: string | undefined
547
595
  readonly target: string
548
596
  readonly resolvedTarget: string
549
597
  }>
@@ -754,15 +802,16 @@ const selectAlwaysTransitions = <
754
802
  >
755
803
  > = []
756
804
  const selectedSources = new Set<string>()
805
+ const evaluatedSources = new Map<string, SelectedTransition<States, E, R, any> | undefined>()
757
806
  let snapshot: Machine.Snapshot<States> | undefined
758
807
  const capturedSnapshot = () => snapshot ??= snapshotFromConfiguration<States>(machine, configuration)
759
808
  for (const leaf of getActiveLeafPaths(machine, configuration)) {
760
809
  for (const path of getLeafCandidatePaths(machine, leaf)) {
761
810
  const always = normalizeTransition(machine.handlers[path]?.always)
762
811
  if (always !== undefined) {
763
- if (!selectedSources.has(path)) {
764
- selectedSources.add(path)
765
- selected.push({
812
+ let candidate = evaluatedSources.get(path)
813
+ if (!evaluatedSources.has(path)) {
814
+ candidate = resolveDeclinableCandidate(machine, {
766
815
  sourcePath: path,
767
816
  leafPath: leaf,
768
817
  trigger: { type: "always" },
@@ -791,6 +840,12 @@ const selectAlwaysTransitions = <
791
840
  target: getTargetBuilder(machine, path)
792
841
  }
793
842
  })
843
+ evaluatedSources.set(path, candidate)
844
+ }
845
+ if (candidate === undefined) continue
846
+ if (!selectedSources.has(path)) {
847
+ selectedSources.add(path)
848
+ selected.push(candidate as (typeof selected)[number])
794
849
  }
795
850
  break
796
851
  }
@@ -833,7 +888,7 @@ const selectDoneTransitions = <
833
888
  const onDone = normalizeTransition(machine.handlers[completion.path]?.onDone)
834
889
  if (onDone !== undefined && !selectedSources.has(completion.path)) {
835
890
  selectedSources.add(completion.path)
836
- selected.push({
891
+ const candidate = resolveDeclinableCandidate(machine, {
837
892
  sourcePath: completion.path,
838
893
  leafPath: getActiveLeafPathFrom(machine, configuration, completion.path),
839
894
  trigger: { type: "done" },
@@ -852,6 +907,7 @@ const selectDoneTransitions = <
852
907
  capturedSnapshot()
853
908
  )
854
909
  })
910
+ if (candidate !== undefined) selected.push(candidate)
855
911
  }
856
912
  }
857
913
  return selected
@@ -892,15 +948,16 @@ const selectEventTransitions = <
892
948
  >
893
949
  > = []
894
950
  const selectedSources = new Set<string>()
951
+ const evaluatedSources = new Map<string, SelectedTransition<States, E, R, any> | undefined>()
895
952
  let snapshot: Machine.Snapshot<States> | undefined
896
953
  const capturedSnapshot = () => snapshot ??= snapshotFromConfiguration<States>(machine, configuration)
897
954
  for (const leaf of getActiveLeafPaths(machine, configuration)) {
898
955
  for (const path of getLeafCandidatePaths(machine, leaf)) {
899
956
  const transition = normalizeTransition(machine.handlers[path]?.on?.[event._tag])
900
957
  if (transition !== undefined) {
901
- if (!selectedSources.has(path)) {
902
- selectedSources.add(path)
903
- selected.push({
958
+ let candidate = evaluatedSources.get(path)
959
+ if (!evaluatedSources.has(path)) {
960
+ candidate = resolveDeclinableCandidate(machine, {
904
961
  sourcePath: path,
905
962
  leafPath: leaf,
906
963
  trigger: { type: "event", event: event._tag },
@@ -926,6 +983,12 @@ const selectEventTransitions = <
926
983
  Machine.TagOf<Events[number]>
927
984
  >(machine as any, configuration, path, event, capturedSnapshot())
928
985
  })
986
+ evaluatedSources.set(path, candidate)
987
+ }
988
+ if (candidate === undefined) continue
989
+ if (!selectedSources.has(path)) {
990
+ selectedSources.add(path)
991
+ selected.push(candidate as (typeof selected)[number])
929
992
  }
930
993
  break
931
994
  }
@@ -952,7 +1015,9 @@ const selectInvocationTransition = <
952
1015
  return String(id) === event.id
953
1016
  })
954
1017
  if (invoke === undefined) return []
955
- const handler = event.type === "done"
1018
+ const handler = event.type === "element"
1019
+ ? invoke.onElement
1020
+ : event.type === "done"
956
1021
  ? invoke.onDone
957
1022
  : event.type === "failure"
958
1023
  ? invoke.onFailure
@@ -968,19 +1033,22 @@ const selectInvocationTransition = <
968
1033
  snapshot,
969
1034
  target: getTargetBuilder(machine, event.path),
970
1035
  id: event.id,
971
- ...(event.type === "done"
1036
+ ...(event.type === "element"
1037
+ ? { element: event.element }
1038
+ : event.type === "done"
972
1039
  ? { output: event.output }
973
1040
  : event.type === "failure"
974
1041
  ? { error: event.error }
975
1042
  : { snapshot: event.snapshot })
976
1043
  }
977
- return [{
1044
+ const candidate = resolveDeclinableCandidate(machine, {
978
1045
  sourcePath: event.path,
979
1046
  leafPath: getActiveLeafPathFrom(machine, configuration, event.path),
980
1047
  trigger: { type: "invoke", id: event.id, outcome: event.type },
981
1048
  transition: transition as unknown as MicrostepTransition<States, E, R, any>,
982
1049
  context
983
- }]
1050
+ })
1051
+ return candidate === undefined ? [] : [candidate]
984
1052
  }
985
1053
 
986
1054
  export const getTargetNodePath = <const States extends Machine.StateSchemas>(
@@ -1150,6 +1218,7 @@ interface ResolvedChoiceTransition {
1150
1218
  readonly trigger: Machine.TransitionTrigger
1151
1219
  readonly reenter: false
1152
1220
  readonly branchIndex: number
1221
+ readonly branchKey: string | undefined
1153
1222
  readonly target: string
1154
1223
  readonly resolvedTarget: string
1155
1224
  }
@@ -1228,6 +1297,7 @@ function resolveChoiceTarget(
1228
1297
  trigger: { type: "choice" },
1229
1298
  reenter: false,
1230
1299
  branchIndex: collected.branchIndex,
1300
+ branchKey: collected.branchKey,
1231
1301
  target: returnedPath,
1232
1302
  resolvedTarget: nested?.target.path ?? returnedPath
1233
1303
  })
@@ -1264,12 +1334,15 @@ const collectEvaluatedTransition = <
1264
1334
  selection: SelectedTransition<States, E, R, Context>
1265
1335
  ) => {
1266
1336
  const stateIdentifier = selection.leafPath
1267
- const transitionResult = collectTransition<States, Event, E, R, Context>(
1337
+ const transitionResult = selection.collected ?? collectTransition<States, Event, E, R, Context>(
1268
1338
  machine,
1269
1339
  selection.transition.transition,
1270
1340
  selection.context,
1271
1341
  selection.transition.evaluate
1272
1342
  )
1343
+ if (transitionResult.declined) {
1344
+ throw new Error("Machine transition returned decline without declaring declinable: true")
1345
+ }
1273
1346
  const unresolvedTarget = transitionResult.state === undefined
1274
1347
  ? undefined
1275
1348
  : transitionResult.state as
@@ -1398,6 +1471,7 @@ const collectEvaluatedTransition = <
1398
1471
  return {
1399
1472
  selection,
1400
1473
  branchIndex: transitionResult.branchIndex,
1474
+ branchKey: transitionResult.branchKey,
1401
1475
  unresolvedTarget,
1402
1476
  target,
1403
1477
  commands: [
@@ -1444,6 +1518,7 @@ const collectEvaluatedTransition = <
1444
1518
  return {
1445
1519
  selection,
1446
1520
  branchIndex: transitionResult.branchIndex,
1521
+ branchKey: transitionResult.branchKey,
1447
1522
  unresolvedTarget,
1448
1523
  target,
1449
1524
  commands: [
@@ -1799,6 +1874,7 @@ const microstep = <
1799
1874
  trigger: transition.selection.trigger,
1800
1875
  reenter: transition.selection.transition.reenter,
1801
1876
  branchIndex: transition.branchIndex,
1877
+ branchKey: transition.branchKey,
1802
1878
  target: transition.unresolvedTarget === undefined ? undefined : getTargetNodePath(transition.unresolvedTarget),
1803
1879
  resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target)
1804
1880
  },
@@ -355,6 +355,9 @@ interface ProcessAddress<in Event> {
355
355
  source: Inspection.Subject | undefined,
356
356
  causedBy: Inspection.Causation | undefined
357
357
  ) => Effect.Effect<void, StoppedError>
358
+ readonly [acknowledgedSend]?: (
359
+ event: Event
360
+ ) => Effect.Effect<AcknowledgedDelivery<unknown>, unknown | StoppedError>
358
361
  }
359
362
 
360
363
  const isMachineTarget = (value: unknown): value is MachineTarget<unknown> =>
@@ -843,7 +846,10 @@ class OwnedChildRuntimeImpl implements OwnedChildRuntime {
843
846
  private readonly registry: ChildRegistry,
844
847
  private readonly self: ProcessAddress<any>,
845
848
  private readonly runtime: ProcessRuntime,
846
- private readonly services?: Context.Context<any>
849
+ private readonly services?: Context.Context<any>,
850
+ private readonly sendAcknowledged?: (
851
+ event: unknown
852
+ ) => Effect.Effect<AcknowledgedDelivery<unknown>, unknown | StoppedError>
847
853
  ) {}
848
854
 
849
855
  private has(key: string): boolean {
@@ -886,7 +892,15 @@ class OwnedChildRuntimeImpl implements OwnedChildRuntime {
886
892
  ...this.self,
887
893
  send: (event) => options.sendParent(isCurrent, event),
888
894
  sendInspected: (event, source, causedBy) =>
889
- isCurrent() ? sendMachineTarget(this.self, event, source, causedBy) : Effect.void
895
+ isCurrent() ? sendMachineTarget(this.self, event, source, causedBy) : Effect.void,
896
+ ...(this.sendAcknowledged === undefined
897
+ ? undefined
898
+ : {
899
+ [acknowledgedSend]: (event: unknown) =>
900
+ isCurrent()
901
+ ? this.sendAcknowledged!(event)
902
+ : Effect.interrupt
903
+ })
890
904
  }
891
905
  const startOptions: StartInternalOptions = {
892
906
  detached: true,
@@ -1048,7 +1062,10 @@ const childlessRuntime: ChildRuntime = {
1048
1062
  const makeChildRuntimeSync = (
1049
1063
  self: ProcessAddress<any>,
1050
1064
  runtime: ProcessRuntime,
1051
- services?: Context.Context<any>
1065
+ services?: Context.Context<any>,
1066
+ sendAcknowledged?: (
1067
+ event: unknown
1068
+ ) => Effect.Effect<AcknowledgedDelivery<unknown>, unknown | StoppedError>
1052
1069
  ): ChildRuntime => {
1053
1070
  // Child-registry decisions are synchronous and every access below runs in
1054
1071
  // one Effect.sync / Effect.suspend step. Keep the unobserved representation
@@ -1290,15 +1307,18 @@ const makeChildRuntimeSync = (
1290
1307
  changes,
1291
1308
  sendTo,
1292
1309
  stop,
1293
- owned: new OwnedChildRuntimeImpl(registry, self, runtime, services)
1310
+ owned: new OwnedChildRuntimeImpl(registry, self, runtime, services, sendAcknowledged)
1294
1311
  }
1295
1312
  }
1296
1313
 
1297
1314
  const makeChildRuntime = (
1298
1315
  self: ProcessAddress<any>,
1299
1316
  runtime: ProcessRuntime,
1300
- services?: Context.Context<any>
1301
- ): Effect.Effect<ChildRuntime> => Effect.sync(() => makeChildRuntimeSync(self, runtime, services))
1317
+ services?: Context.Context<any>,
1318
+ sendAcknowledged?: (
1319
+ event: unknown
1320
+ ) => Effect.Effect<AcknowledgedDelivery<unknown>, unknown | StoppedError>
1321
+ ): Effect.Effect<ChildRuntime> => Effect.sync(() => makeChildRuntimeSync(self, runtime, services, sendAcknowledged))
1302
1322
 
1303
1323
  // `Machine.logic` permits an arbitrary Effect program, including programs that
1304
1324
  // suspend or supervise their own fibers. Keep its two-fiber worker/supervisor
@@ -1376,6 +1396,23 @@ const startGenericInternal: <
1376
1396
  Effect.tap(() => Effect.sync(() => publishInspectedSent(inspector, subject!, message)))
1377
1397
  )
1378
1398
  })
1399
+ const sendAcknowledged:
1400
+ | ((event: Event) => Effect.Effect<AcknowledgedDelivery<State>, Error | StoppedError>)
1401
+ | undefined = logic.execution?._tag !== "Compiled"
1402
+ ? undefined
1403
+ : (event) =>
1404
+ Effect.uninterruptibleMask((restore) =>
1405
+ Deferred.make<AcknowledgedDelivery<unknown>, unknown>().pipe(
1406
+ Effect.flatMap((deferred) => {
1407
+ const offered = inspector === undefined
1408
+ ? offerDirect({ [AcknowledgedMessageTypeId]: true as const, event, deferred })
1409
+ : offerInspected!(event, undefined, undefined, deferred)
1410
+ return offered.pipe(Effect.andThen(restore(Deferred.await(deferred))))
1411
+ }),
1412
+ Effect.map((delivery) => delivery as AcknowledgedDelivery<State>)
1413
+ )
1414
+ ) as Effect.Effect<AcknowledgedDelivery<State>, Error | StoppedError>
1415
+
1379
1416
  const self: ProcessAddress<Event> = {
1380
1417
  id,
1381
1418
  sessionId,
@@ -1396,22 +1433,6 @@ const startGenericInternal: <
1396
1433
  ? undefined
1397
1434
  : { inspectionSubject: subject!, sendInspected: offerInspected! })
1398
1435
  }
1399
- const sendAcknowledged:
1400
- | ((event: Event) => Effect.Effect<AcknowledgedDelivery<State>, Error | StoppedError>)
1401
- | undefined = logic.execution?._tag !== "Compiled"
1402
- ? undefined
1403
- : (event) =>
1404
- Effect.uninterruptibleMask((restore) =>
1405
- Deferred.make<AcknowledgedDelivery<unknown>, unknown>().pipe(
1406
- Effect.flatMap((deferred) => {
1407
- const offered = inspector === undefined
1408
- ? offerDirect({ [AcknowledgedMessageTypeId]: true as const, event, deferred })
1409
- : offerInspected!(event, undefined, undefined, deferred)
1410
- return offered.pipe(Effect.andThen(restore(Deferred.await(deferred))))
1411
- }),
1412
- Effect.map((delivery) => delivery as AcknowledgedDelivery<State>)
1413
- )
1414
- ) as Effect.Effect<AcknowledgedDelivery<State>, Error | StoppedError>
1415
1436
 
1416
1437
  let {
1417
1438
  changes: childChanges,
@@ -1431,7 +1452,12 @@ const startGenericInternal: <
1431
1452
  sendTo,
1432
1453
  spawn,
1433
1454
  stop: stopChild
1434
- } = yield* makeChildRuntime(self, runtime))
1455
+ } = yield* makeChildRuntime(
1456
+ self,
1457
+ runtime,
1458
+ undefined,
1459
+ sendAcknowledged === undefined ? undefined : (event) => sendAcknowledged(event as Event)
1460
+ ))
1435
1461
  }
1436
1462
  const cleanupStartupFailure = <A, E>(exit: Exit.Exit<A, E>): Effect.Effect<void> => {
1437
1463
  if (Exit.isSuccess(exit)) return Effect.void
@@ -2227,7 +2253,12 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
2227
2253
  initializeCompiledSync(): Effect.Effect<MachineRef<any, any, any, any>, unknown> {
2228
2254
  this.publishCreated()
2229
2255
  if (!this.execution.childless) {
2230
- this.childRuntime = makeChildRuntimeSync(this.address, this.options.runtime, this.services)
2256
+ this.childRuntime = makeChildRuntimeSync(
2257
+ this.address,
2258
+ this.options.runtime,
2259
+ this.services,
2260
+ (event) => this.sendAcknowledgedEffect(event)
2261
+ )
2231
2262
  }
2232
2263
  const parent = this.options.parent
2233
2264
  const sendParent = this.options.sendParent ?? (parent === undefined
@@ -2304,7 +2335,12 @@ class CompiledProcess implements MachineRef<any, any, any, any> {
2304
2335
  return Effect.gen(function*() {
2305
2336
  self.publishCreated()
2306
2337
  if (!self.execution.childless) {
2307
- self.childRuntime = yield* makeChildRuntime(self.address, self.options.runtime, self.services)
2338
+ self.childRuntime = yield* makeChildRuntime(
2339
+ self.address,
2340
+ self.options.runtime,
2341
+ self.services,
2342
+ (event) => self.sendAcknowledgedEffect(event)
2343
+ )
2308
2344
  }
2309
2345
  const parent = self.options.parent
2310
2346
  const sendParent = self.options.sendParent ?? (parent === undefined
@@ -138,6 +138,22 @@ const assertPlainRecord: (
138
138
  }
139
139
  }
140
140
 
141
+ const validateTargetSelectorChildKey = (
142
+ boundary: StateDefinitionBoundary,
143
+ path: string,
144
+ states: unknown,
145
+ key: "initial" | "with",
146
+ message: string
147
+ ): void => {
148
+ if (typeof states !== "object" || states === null || Array.isArray(states) || !hasOwn(states, key)) return
149
+ const child = (states as Readonly<Record<string, unknown>>)[key]
150
+ if (
151
+ typeof child === "object" && child !== null && !Schema.isSchema(child) &&
152
+ (child as Readonly<Record<string, unknown>>).type === "history"
153
+ ) return
154
+ fail(boundary, `${path}.${key}`, message)
155
+ }
156
+
141
157
  const assertAllowedProperties = (
142
158
  boundary: StateDefinitionBoundary,
143
159
  path: string,
@@ -252,6 +268,22 @@ const validateStateTree = (
252
268
  if (typeof node.initial !== "string") {
253
269
  fail(boundary, `${path}.initial`, "compound states must declare an initial child key")
254
270
  }
271
+ validateTargetSelectorChildKey(
272
+ boundary,
273
+ path,
274
+ node.states,
275
+ "initial",
276
+ "active and choice child states cannot use the reserved target selector key \"initial\""
277
+ )
278
+ if (valued) {
279
+ validateTargetSelectorChildKey(
280
+ boundary,
281
+ path,
282
+ node.states,
283
+ "with",
284
+ "schema-backed compound child states cannot use the reserved local target selector key \"with\""
285
+ )
286
+ }
255
287
  const initialKey = node.initial as string
256
288
  validateStateTree(boundary, node.states, path, true)
257
289
  if (!hasOwn(node.states as object, initialKey)) {
@@ -270,6 +302,13 @@ const validateStateTree = (
270
302
  if (!hasOwn(node, "states")) {
271
303
  fail(boundary, `${path}.states`, "parallel states must declare child regions")
272
304
  }
305
+ validateTargetSelectorChildKey(
306
+ boundary,
307
+ path,
308
+ node.states,
309
+ "initial",
310
+ "active and choice child states cannot use the reserved target selector key \"initial\""
311
+ )
273
312
  validateStateTree(boundary, node.states, path, true)
274
313
  }
275
314
  }