@typeonce/effect-machine 0.16.0 → 0.18.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 (68) hide show
  1. package/README.md +157 -150
  2. package/dist/Machine.d.ts +416 -592
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +55 -136
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/atom.d.ts +7 -7
  7. package/dist/internal/machine/atom.d.ts.map +1 -1
  8. package/dist/internal/machine/atom.js.map +1 -1
  9. package/dist/internal/machine/cluster.d.ts +2 -2
  10. package/dist/internal/machine/cluster.d.ts.map +1 -1
  11. package/dist/internal/machine/cluster.js +6 -5
  12. package/dist/internal/machine/cluster.js.map +1 -1
  13. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  14. package/dist/internal/machine/executionPlan.js +10 -3
  15. package/dist/internal/machine/executionPlan.js.map +1 -1
  16. package/dist/internal/machine/invocation.d.ts.map +1 -1
  17. package/dist/internal/machine/invocation.js +1 -1
  18. package/dist/internal/machine/invocation.js.map +1 -1
  19. package/dist/internal/machine/machine.d.ts +8 -6
  20. package/dist/internal/machine/machine.d.ts.map +1 -1
  21. package/dist/internal/machine/machine.js +239 -35
  22. package/dist/internal/machine/machine.js.map +1 -1
  23. package/dist/internal/machine/planner.d.ts +19 -4
  24. package/dist/internal/machine/planner.d.ts.map +1 -1
  25. package/dist/internal/machine/planner.js +56 -20
  26. package/dist/internal/machine/planner.js.map +1 -1
  27. package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
  28. package/dist/internal/machine/stateDefinition.js +14 -0
  29. package/dist/internal/machine/stateDefinition.js.map +1 -1
  30. package/dist/internal/machine/topology.d.ts +9 -0
  31. package/dist/internal/machine/topology.d.ts.map +1 -1
  32. package/dist/internal/machine/topology.js +16 -0
  33. package/dist/internal/machine/topology.js.map +1 -1
  34. package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
  35. package/dist/internal/testing/machine/finiteModel.js +15 -20
  36. package/dist/internal/testing/machine/finiteModel.js.map +1 -1
  37. package/dist/internal/testing/machine/transitionCoverage.d.ts.map +1 -1
  38. package/dist/internal/testing/machine/transitionCoverage.js +2 -0
  39. package/dist/internal/testing/machine/transitionCoverage.js.map +1 -1
  40. package/dist/testing/MachineTest.d.ts +15 -15
  41. package/dist/testing/MachineTest.d.ts.map +1 -1
  42. package/dist/testing/MachineTest.js +5 -8
  43. package/dist/testing/MachineTest.js.map +1 -1
  44. package/dist/unstable/cluster/ClusterMachine.d.ts +2 -2
  45. package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
  46. package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
  47. package/dist/unstable/reactivity/AtomMachine.d.ts +13 -13
  48. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  49. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  50. package/docs/agent-guide.md +229 -193
  51. package/package.json +1 -1
  52. package/src/Machine.ts +1686 -2234
  53. package/src/internal/machine/atom.ts +20 -15
  54. package/src/internal/machine/cluster.ts +14 -9
  55. package/src/internal/machine/executionPlan.ts +8 -3
  56. package/src/internal/machine/invocation.ts +1 -1
  57. package/src/internal/machine/machine.ts +365 -48
  58. package/src/internal/machine/planner.ts +89 -27
  59. package/src/internal/machine/stateDefinition.ts +39 -0
  60. package/src/internal/machine/topology.ts +28 -0
  61. package/src/internal/testing/machine/exploration.ts +1 -1
  62. package/src/internal/testing/machine/finiteModel.ts +18 -21
  63. package/src/internal/testing/machine/trace.ts +1 -1
  64. package/src/internal/testing/machine/transitionCoverage.ts +2 -0
  65. package/src/internal/testing/machine/verification.ts +1 -1
  66. package/src/testing/MachineTest.ts +18 -15
  67. package/src/unstable/cluster/ClusterMachine.ts +8 -4
  68. package/src/unstable/reactivity/AtomMachine.ts +20 -13
@@ -19,6 +19,8 @@ import type {
19
19
  MachineRef,
20
20
  MachineSchemaDecodeError,
21
21
  MachineSchemaEncodeError,
22
+ Parent,
23
+ ParentMode,
22
24
  Runtime,
23
25
  RuntimeOutcome,
24
26
  SpawnOptions,
@@ -49,8 +51,10 @@ export {
49
51
  export { ChildMachineLogicTypeId, InitialEventTypeId, SnapshotBuilderStateTypeId } from "./symbols.js"
50
52
 
51
53
  const TypeId = "~effect/Machine"
54
+ const ParentTypeId = "~effect/Machine/Parent"
52
55
  export const InvokeTypeId: unique symbol = Symbol.for("effect/Machine/Invoke")
53
56
  export const TransitionTypeId: unique symbol = Symbol.for("effect/Machine/Transition")
57
+ const InvokeBuilderDescriptorTypeId: unique symbol = Symbol("effect/Machine/InvokeBuilderDescriptor")
54
58
  const ChildMachineTypeId = "~effect/Machine/ChildMachine"
55
59
  type IsAny<A> = 0 extends 1 & A ? true : false
56
60
  type MachineRuntimeRequirement = internalRuntime.MachineRuntime
@@ -112,8 +116,6 @@ const Proto = {
112
116
  }
113
117
  }
114
118
 
115
- const makeBoundInvoke = (config: unknown): unknown => config
116
-
117
119
  const makeWithHandlers = (
118
120
  self: Definition.Any,
119
121
  handlers: Machine.StateConfigs<any, any, any, any, any, any, any>
@@ -123,7 +125,7 @@ const makeWithHandlers = (
123
125
  machine.events = self.events
124
126
  machine.internalEvents = self.internalEvents
125
127
  machine.emittedEvents = self.emittedEvents
126
- machine.parentEvents = self.parentEvents
128
+ machine.parent = self.parent
127
129
  machine.input = self.input
128
130
  machine.id = self.id
129
131
  machine.initial = self.initial
@@ -131,7 +133,6 @@ const makeWithHandlers = (
131
133
  machine.stateNodes = self.stateNodes
132
134
  machine.makeTargetBuilder = self.makeTargetBuilder
133
135
  machine.handlers = handlers
134
- machine.invoke = makeBoundInvoke
135
136
  Protocol.copyProtocol(self, machine)
136
137
  return machine
137
138
  }
@@ -139,6 +140,7 @@ const makeWithHandlers = (
139
140
  type DefinitionBranch = {
140
141
  readonly target: (selector: unknown) => unknown
141
142
  readonly resolve?: (context: any, enqueue: unknown) => unknown
143
+ readonly declinable?: boolean
142
144
  }
143
145
 
144
146
  type CapturedBranch = DefinitionBranch & {
@@ -156,6 +158,189 @@ type CapturedNamedBranch = {
156
158
  readonly selection: Topology.TargetSelection
157
159
  }
158
160
 
161
+ const TransitionBuilderDescriptorTypeId: unique symbol = Symbol("effect/Machine/TransitionBuilderDescriptor")
162
+
163
+ type DirectTransitionDescriptor = {
164
+ readonly [TransitionBuilderDescriptorTypeId]: typeof TransitionBuilderDescriptorTypeId
165
+ readonly type: "direct"
166
+ readonly selection: Topology.TargetSelection
167
+ readonly resolve?: (context: any, enqueue: unknown) => unknown
168
+ readonly reenter: boolean
169
+ readonly declinable: boolean
170
+ }
171
+
172
+ type BranchesTransitionDescriptor = {
173
+ readonly [TransitionBuilderDescriptorTypeId]: typeof TransitionBuilderDescriptorTypeId
174
+ readonly type: "branches"
175
+ readonly declarations: unknown
176
+ readonly resolve: (context: any, enqueue: unknown) => unknown
177
+ readonly reenter: boolean
178
+ readonly declinable: boolean
179
+ }
180
+
181
+ type TransitionBuilderDescriptor = DirectTransitionDescriptor | BranchesTransitionDescriptor
182
+
183
+ const InitialBuilderDescriptorTypeId: unique symbol = Symbol("effect/Machine/InitialBuilderDescriptor")
184
+
185
+ type InitialBuilderDescriptor = {
186
+ readonly [InitialBuilderDescriptorTypeId]: typeof InitialBuilderDescriptorTypeId
187
+ readonly selection: Topology.TargetSelection
188
+ readonly resolve: (context: any) => unknown
189
+ }
190
+
191
+ const plainTargetSelection = (selection: Topology.TargetSelection): Topology.TargetSelection =>
192
+ selection.kind === "none"
193
+ ? Topology.noneTargetSelection
194
+ : Topology.makeTargetSelection(selection.kind, selection.path, selection.scope)
195
+
196
+ const transitionOptions = (options: unknown): { readonly reenter: boolean; readonly declinable: boolean } => {
197
+ const configuration = typeof options === "object" && options !== null
198
+ ? options as { readonly reenter?: unknown; readonly declinable?: unknown }
199
+ : {}
200
+ return {
201
+ reenter: configuration.reenter === true,
202
+ declinable: configuration.declinable === true
203
+ }
204
+ }
205
+
206
+ const makeDirectTransitionDescriptor = (
207
+ selection: Topology.TargetSelection,
208
+ resolve: ((context: any, enqueue: unknown) => unknown) | undefined,
209
+ options: unknown
210
+ ): DirectTransitionDescriptor => {
211
+ const shared: Omit<DirectTransitionDescriptor, "resolve"> = {
212
+ [TransitionBuilderDescriptorTypeId]: TransitionBuilderDescriptorTypeId,
213
+ type: "direct",
214
+ selection: plainTargetSelection(selection),
215
+ ...transitionOptions(options)
216
+ }
217
+ return resolve === undefined
218
+ ? Object.freeze(shared)
219
+ : Object.freeze({ ...shared, resolve })
220
+ }
221
+
222
+ const decorateTransitionSelection = (selection: Topology.TargetSelection): Topology.TargetSelection =>
223
+ Object.freeze({
224
+ ...selection,
225
+ resolve: (resolve: (context: any, enqueue: unknown) => unknown, options?: unknown) =>
226
+ makeDirectTransitionDescriptor(selection, resolve, options),
227
+ reenter: () => makeDirectTransitionDescriptor(selection, undefined, { reenter: true })
228
+ })
229
+
230
+ const noneTransitionSelection = decorateTransitionSelection(Topology.noneTargetSelection)
231
+
232
+ const makeInitialBuilderDescriptor = (
233
+ selection: Topology.TargetSelection,
234
+ resolve: (context: any) => unknown
235
+ ): InitialBuilderDescriptor =>
236
+ Object.freeze({
237
+ [InitialBuilderDescriptorTypeId]: InitialBuilderDescriptorTypeId,
238
+ selection: plainTargetSelection(selection),
239
+ resolve
240
+ })
241
+
242
+ const decorateInitialSelection = (selection: Topology.TargetSelection): Topology.TargetSelection =>
243
+ Object.freeze({
244
+ ...selection,
245
+ resolve: (resolve: (context: any) => unknown) => makeInitialBuilderDescriptor(selection, resolve)
246
+ })
247
+
248
+ const decorateInitialSelectorNode = (node: unknown): unknown => {
249
+ if (Topology.isTargetSelection(node)) return decorateInitialSelection(node)
250
+ if (typeof node === "function") {
251
+ const wrapped = ((...args: ReadonlyArray<unknown>) => decorateInitialSelection(node(...args))) as
252
+ & ((...args: ReadonlyArray<unknown>) => unknown)
253
+ & Record<string, unknown>
254
+ for (const key of Object.keys(node)) {
255
+ wrapped[key] = decorateInitialSelectorNode((node as unknown as Record<string, unknown>)[key])
256
+ }
257
+ return Object.freeze(wrapped)
258
+ }
259
+ if (typeof node === "object" && node !== null) {
260
+ const wrapped: Record<string, unknown> = {}
261
+ for (const key of Object.keys(node)) {
262
+ wrapped[key] = decorateInitialSelectorNode((node as Record<string, unknown>)[key])
263
+ }
264
+ return Object.freeze(wrapped)
265
+ }
266
+ return node
267
+ }
268
+
269
+ const decorateTransitionSelectorNode = (node: unknown): unknown => {
270
+ if (Topology.isTargetSelection(node)) {
271
+ return node === Topology.noneTargetSelection ? noneTransitionSelection : decorateTransitionSelection(node)
272
+ }
273
+ if (typeof node === "function") {
274
+ const wrapped = ((...args: ReadonlyArray<unknown>) => decorateTransitionSelection(node(...args))) as
275
+ & ((...args: ReadonlyArray<unknown>) => unknown)
276
+ & Record<string, unknown>
277
+ for (const key of Object.keys(node)) {
278
+ wrapped[key] = decorateTransitionSelectorNode((node as unknown as Record<string, unknown>)[key])
279
+ }
280
+ return Object.freeze(wrapped)
281
+ }
282
+ if (typeof node === "object" && node !== null) {
283
+ const wrapped: Record<string, unknown> = {}
284
+ for (const key of Object.keys(node)) {
285
+ wrapped[key] = decorateTransitionSelectorNode((node as Record<string, unknown>)[key])
286
+ }
287
+ return Object.freeze(wrapped)
288
+ }
289
+ return node
290
+ }
291
+
292
+ const makeTransitionSelector = (
293
+ stateNodes: Machine.StateNodes,
294
+ source: string
295
+ ): unknown => {
296
+ const selector = {
297
+ ...decorateTransitionSelectorNode(makeTargetSelector(stateNodes, source)) as Record<string, unknown>
298
+ }
299
+ selector.branches = (declarations: unknown) =>
300
+ Object.freeze({
301
+ resolve: (
302
+ resolve: (context: any, enqueue: unknown) => unknown,
303
+ options?: unknown
304
+ ): BranchesTransitionDescriptor =>
305
+ Object.freeze({
306
+ [TransitionBuilderDescriptorTypeId]: TransitionBuilderDescriptorTypeId,
307
+ type: "branches",
308
+ declarations,
309
+ resolve,
310
+ ...transitionOptions(options)
311
+ })
312
+ })
313
+ return Object.freeze(selector)
314
+ }
315
+
316
+ const normalizeTransitionBuilder = (
317
+ transition: (selector: unknown) => unknown,
318
+ stateNodes: Machine.StateNodes,
319
+ path: string
320
+ ): unknown => {
321
+ const result = transition(makeTransitionSelector(stateNodes, path))
322
+ if (Topology.isTargetSelection(result)) {
323
+ const selection = plainTargetSelection(result)
324
+ return { target: () => selection }
325
+ }
326
+ if (!hasProperty(result, TransitionBuilderDescriptorTypeId)) return result
327
+ const descriptor = result as TransitionBuilderDescriptor
328
+ if (descriptor.type === "branches") {
329
+ return {
330
+ branches: () => descriptor.declarations,
331
+ resolve: descriptor.resolve,
332
+ reenter: descriptor.reenter,
333
+ declinable: descriptor.declinable
334
+ }
335
+ }
336
+ return {
337
+ target: () => descriptor.selection,
338
+ resolve: descriptor.resolve,
339
+ reenter: descriptor.reenter,
340
+ declinable: descriptor.declinable
341
+ }
342
+ }
343
+
159
344
  const transitionTargetSelection = (
160
345
  selection: Topology.TargetSelection
161
346
  ): Machine.TransitionTargetSelection =>
@@ -172,6 +357,12 @@ const makeSelectionMethod = (
172
357
  ): () => Topology.TargetSelection =>
173
358
  () => Topology.makeTargetSelection(kind, path, scope)
174
359
 
360
+ const makeSelectionValue = (
361
+ kind: Topology.TargetSelectionKind,
362
+ path: string | undefined,
363
+ scope: Topology.TargetSelectionScope
364
+ ): Topology.TargetSelection => Topology.makeTargetSelection(kind, path, scope)
365
+
175
366
  const addSelectionChildren = (
176
367
  builder: Record<string, unknown>,
177
368
  stateNodes: Machine.StateNodes,
@@ -194,7 +385,7 @@ const makeSelectionNode = (
194
385
  const method = makeSelectionMethod(kind, path, scope) as unknown as Record<string, unknown>
195
386
  if (node.type !== "atomic" && node.type !== "final" && node.type !== "choice" && node.type !== "history") {
196
387
  Object.defineProperty(method, "initial", {
197
- value: makeSelectionMethod("initial", path, scope),
388
+ value: makeSelectionValue("initial", path, scope),
198
389
  enumerable: true
199
390
  })
200
391
  if (scope === "local" || scope === "branch") {
@@ -212,7 +403,7 @@ const makeHistorySelectionTree = (
212
403
  for (const node of stateNodes.byPath.values()) {
213
404
  if (node.parent !== parent) continue
214
405
  if (node.type === "history") {
215
- builder[node.key] = makeSelectionMethod("history", node.path, "full")
406
+ builder[node.key] = makeSelectionValue("history", node.path, "full")
216
407
  } else if (node.type !== "choice") {
217
408
  const children = makeHistorySelectionTree(stateNodes, node.path)
218
409
  if (Object.keys(children).length > 0) builder[node.key] = children
@@ -239,12 +430,12 @@ const makeTargetSelector = (
239
430
  if (localScope !== undefined) {
240
431
  const localScopeNode = getTargetBuilderNode(stateNodes, localScope)
241
432
  if (localScopeNode.schema !== undefined) {
242
- local.with = makeSelectionMethod("state", localScope, "local")
433
+ local.with = makeSelectionValue("state", localScope, "local")
243
434
  }
244
435
  addSelectionChildren(local, stateNodes, localScope, "local")
245
436
  }
246
437
  return {
247
- none: makeSelectionMethod("none", undefined, "local"),
438
+ none: Topology.noneTargetSelection,
248
439
  local,
249
440
  branch,
250
441
  full,
@@ -309,7 +500,8 @@ const getSelectionBuilder = (
309
500
  return builder
310
501
  }
311
502
 
312
- const constructSelectedTarget = (builder: any): unknown => typeof builder === "function" ? builder() : builder.from()
503
+ const constructSelectedTarget = (builder: any): unknown =>
504
+ typeof builder?.from === "function" ? builder.from() : builder()
313
505
 
314
506
  const validateResolvedSelection = (
315
507
  result: unknown,
@@ -352,7 +544,14 @@ const runCapturedBranch = (
352
544
  const resolverContext = { ...context }
353
545
  if (branch.selection.kind === "none") delete resolverContext.target
354
546
  else resolverContext.target = selectedTarget
547
+ if (branch.declinable === true) resolverContext.decline = Topology.makeDeclined
355
548
  const resolved = branch.resolve(resolverContext, enqueue)
549
+ if (Topology.isDeclined(resolved)) {
550
+ if (branch.declinable !== true) {
551
+ throw new Error(`Machine transition for state "${source}" returned decline without declaring declinable: true`)
552
+ }
553
+ return resolved
554
+ }
356
555
  validateResolvedSelection(resolved, branch.selection, stateNodes)
357
556
  return resolved === undefined ? constructSelectedTarget(selectedTarget) : resolved
358
557
  }
@@ -394,7 +593,7 @@ const captureNamedBranches = (
394
593
  if (title !== undefined && (typeof title !== "string" || title.length === 0)) {
395
594
  throw new Error(`Machine transition branch "${key}" title must be a non-empty string`)
396
595
  }
397
- return Object.freeze({ key, title: title ?? key, selection: target })
596
+ return Object.freeze({ key, title: title ?? key, selection: plainTargetSelection(target) })
398
597
  }))
399
598
  }
400
599
 
@@ -474,17 +673,24 @@ const validateSelectedBranchResult = (
474
673
  }
475
674
 
476
675
  const captureTransition = (
477
- transition: unknown,
676
+ rawTransition: unknown,
478
677
  stateNodes: Machine.StateNodes,
479
678
  path: string,
480
679
  trigger: PropertyKey
481
680
  ): unknown => {
681
+ if (typeof rawTransition !== "function") {
682
+ throw new Error(
683
+ `Machine transition for state "${path}" on "${String(trigger)}" must be a target-first callback`
684
+ )
685
+ }
686
+ const transition = normalizeTransitionBuilder(rawTransition as (selector: unknown) => unknown, stateNodes, path)
482
687
  if (typeof transition !== "object" || transition === null) {
483
688
  throw new Error(`Machine transition for state "${path}" on "${String(trigger)}" must be an object`)
484
689
  }
485
690
  const definition = transition as Record<PropertyKey, unknown>
486
691
  const selector = makeTargetSelector(stateNodes, path)
487
692
  const reenter = definition.reenter === true
693
+ const declinable = definition.declinable === true
488
694
  if (hasProperty(definition, "branches")) {
489
695
  const branching = definition as { readonly branches: unknown; readonly resolve?: unknown }
490
696
  if (typeof branching.branches !== "function" || typeof branching.resolve !== "function") {
@@ -499,7 +705,18 @@ const captureTransition = (
499
705
  const resolverContext = { ...context }
500
706
  delete resolverContext.target
501
707
  resolverContext.select = makeBranchSelectors(context, branches, owner, stateNodes, path)
708
+ if (declinable) resolverContext.decline = Topology.makeDeclined
502
709
  const selected = resolve(resolverContext, enqueue)
710
+ if (Topology.isDeclined(selected)) {
711
+ if (!declinable) {
712
+ throw new Error(
713
+ `Machine branching transition for state "${path}" on "${
714
+ String(trigger)
715
+ }" returned decline without declaring declinable: true`
716
+ )
717
+ }
718
+ return { result: selected, branchIndex: -1, branchKey: undefined }
719
+ }
503
720
  if (!Topology.isSelectedBranch(selected) || selected.owner !== owner) {
504
721
  throw new Error(
505
722
  `Machine branching transition for state "${path}" on "${String(trigger)}" must select one declared branch`
@@ -518,6 +735,7 @@ const captureTransition = (
518
735
  }
519
736
  return {
520
737
  reenter,
738
+ declinable,
521
739
  targets: [
522
740
  ...new Set(
523
741
  branches.flatMap((branch) => branch.selection.path === undefined ? [] : [branch.selection.path])
@@ -544,6 +762,7 @@ const captureTransition = (
544
762
  })
545
763
  return {
546
764
  reenter,
765
+ declinable,
547
766
  targets: branch.selection.path === undefined ? [] : [branch.selection.path],
548
767
  branches: [{
549
768
  type: "direct" as const,
@@ -570,20 +789,75 @@ const captureEventHandlers = (
570
789
  return captured
571
790
  }
572
791
 
792
+ interface InvokeBuilderDescriptor {
793
+ readonly [InvokeBuilderDescriptorTypeId]: typeof InvokeBuilderDescriptorTypeId
794
+ readonly config: Readonly<Record<PropertyKey, unknown>>
795
+ }
796
+
797
+ type InvokeBuilderChannel = "onDone" | "onFailure" | "onElement" | "onSnapshot"
798
+
799
+ const makeInvokeBuilder = (
800
+ config: Readonly<Record<PropertyKey, unknown>>,
801
+ channels: ReadonlyArray<InvokeBuilderChannel>
802
+ ): InvokeBuilderDescriptor => {
803
+ const builder: Record<PropertyKey, unknown> = {
804
+ [InvokeBuilderDescriptorTypeId]: InvokeBuilderDescriptorTypeId as typeof InvokeBuilderDescriptorTypeId,
805
+ config
806
+ }
807
+ for (const channel of channels) {
808
+ if (!hasProperty(config, channel)) {
809
+ builder[channel] = (handler: unknown) => makeInvokeBuilder({ ...config, [channel]: handler }, channels)
810
+ }
811
+ }
812
+ return Object.freeze(builder) as unknown as InvokeBuilderDescriptor
813
+ }
814
+
815
+ const invokeSelector = Object.freeze({
816
+ effect: (id: string, effect: unknown) => makeInvokeBuilder({ id, effect }, ["onDone", "onFailure"]),
817
+ stream: (id: string, stream: unknown) => makeInvokeBuilder({ id, stream }, ["onElement", "onDone", "onFailure"]),
818
+ timer: (id: string, after: unknown) => makeInvokeBuilder({ id, after }, ["onDone"]),
819
+ logic: (id: string, options: Readonly<Record<PropertyKey, unknown>>) =>
820
+ makeInvokeBuilder({ id, ...options }, ["onSnapshot", "onDone", "onFailure"]),
821
+ child: (child: unknown, options?: Readonly<Record<PropertyKey, unknown>>) =>
822
+ makeInvokeBuilder(options === undefined ? { child } : { child, ...options }, [
823
+ "onSnapshot",
824
+ "onDone",
825
+ "onFailure"
826
+ ])
827
+ })
828
+
829
+ const invokeBuilderConfig = (value: unknown, path: string): Readonly<Record<PropertyKey, unknown>> => {
830
+ if (
831
+ typeof value !== "object" || value === null ||
832
+ !hasProperty(value, InvokeBuilderDescriptorTypeId) ||
833
+ value[InvokeBuilderDescriptorTypeId] !== InvokeBuilderDescriptorTypeId
834
+ ) {
835
+ throw new Error(`Machine invocation for state "${path}" must be constructed from its source selector`)
836
+ }
837
+ return (value as unknown as InvokeBuilderDescriptor).config
838
+ }
839
+
573
840
  const captureInvokeDefinition = (
574
841
  invoke: unknown,
575
842
  stateNodes: Machine.StateNodes,
576
843
  path: string
577
844
  ): unknown => {
578
- if (Array.isArray(invoke)) return invoke.map((item) => captureInvokeDefinition(item, stateNodes, path))
579
- if (typeof invoke !== "object" || invoke === null) return invoke
580
- const captured = { ...(invoke as Record<PropertyKey, unknown>) }
581
- for (const key of ["onElement", "onDone", "onFailure", "onSnapshot"] as const) {
582
- if (captured[key] !== undefined) {
583
- captured[key] = captureTransition(captured[key], stateNodes, path, key)
584
- }
845
+ if (typeof invoke !== "function") {
846
+ throw new Error(`Machine invocation for state "${path}" must be a source-first callback`)
585
847
  }
586
- return captured
848
+ const authored = invoke(invokeSelector)
849
+ const definitions = Array.isArray(authored) ? authored : [authored]
850
+ const capturedDefinitions = definitions.map((definition) => {
851
+ const captured = { ...invokeBuilderConfig(definition, path) }
852
+ for (const key of ["onElement", "onDone", "onFailure", "onSnapshot"] as const) {
853
+ if (captured[key] !== undefined) {
854
+ captured[key] = captureTransition(captured[key], stateNodes, path, key)
855
+ }
856
+ }
857
+ return captured
858
+ })
859
+ if (Array.isArray(authored)) return capturedDefinitions
860
+ return capturedDefinitions[0]
587
861
  }
588
862
 
589
863
  const flattenHandlers = (
@@ -1185,10 +1459,12 @@ const makeInitialSelector = (stateNodes: Machine.StateNodes): unknown => {
1185
1459
  const selector: Record<string, unknown> = {}
1186
1460
  for (const node of stateNodes.byPath.values()) {
1187
1461
  if (node.parent === undefined && node.type !== "history" && node.type !== "choice") {
1188
- selector[node.key] = makeSelectionNode(stateNodes, node.path, "initial")
1462
+ selector[node.key] = node.type === "atomic" || node.type === "final"
1463
+ ? makeSelectionMethod("state", node.path, "initial")
1464
+ : Object.freeze({ initial: makeSelectionValue("initial", node.path, "initial") })
1189
1465
  }
1190
1466
  }
1191
- return selector
1467
+ return Object.freeze(selector)
1192
1468
  }
1193
1469
 
1194
1470
  const getInitialSelectionBuilder = (
@@ -1207,15 +1483,37 @@ const getInitialSelectionBuilder = (
1207
1483
  }
1208
1484
 
1209
1485
  const captureInitialBranch = (
1210
- branch: unknown,
1211
- selector: unknown,
1486
+ definition: unknown,
1487
+ stateNodes: Machine.StateNodes,
1212
1488
  initialBuilder: Record<string, any>
1213
- ): CapturedBranch & { readonly builder: (...args: ReadonlyArray<any>) => unknown } => {
1214
- const captured = captureDefinitionBranch(branch, selector, "<machine>", "initial")
1215
- if (captured.selection.kind !== "state" && captured.selection.kind !== "initial") {
1489
+ ): {
1490
+ readonly selection: Topology.TargetSelection
1491
+ readonly resolve?: (context: any) => unknown
1492
+ readonly builder: (...args: ReadonlyArray<any>) => unknown
1493
+ } => {
1494
+ if (typeof definition !== "function") {
1495
+ throw new Error("Machine initial definition must be a target-first callback")
1496
+ }
1497
+ const result = definition(decorateInitialSelectorNode(makeInitialSelector(stateNodes)))
1498
+ let selection: Topology.TargetSelection
1499
+ let resolve: ((context: any) => unknown) | undefined
1500
+ if (Topology.isTargetSelection(result)) {
1501
+ selection = plainTargetSelection(result)
1502
+ } else if (hasProperty(result, InitialBuilderDescriptorTypeId)) {
1503
+ const descriptor = result as InitialBuilderDescriptor
1504
+ selection = descriptor.selection
1505
+ resolve = descriptor.resolve
1506
+ } else {
1507
+ throw new Error("Machine initial definition must select exactly one target")
1508
+ }
1509
+ if (selection.kind !== "state" && selection.kind !== "initial") {
1216
1510
  throw new Error("Machine initial target must select a top-level state or its declared initial entry")
1217
1511
  }
1218
- return { ...captured, builder: getInitialSelectionBuilder(initialBuilder, captured.selection) }
1512
+ const captured = {
1513
+ selection,
1514
+ builder: getInitialSelectionBuilder(initialBuilder, selection)
1515
+ }
1516
+ return resolve === undefined ? Object.freeze(captured) : Object.freeze({ ...captured, resolve })
1219
1517
  }
1220
1518
 
1221
1519
  const validateInitialSelection = (result: unknown, selection: Topology.TargetSelection): void => {
@@ -1237,17 +1535,13 @@ const compileInitial = (
1237
1535
  readonly initial: (input?: unknown) => unknown
1238
1536
  readonly definition: Machine.InitialDefinition
1239
1537
  } => {
1240
- if (typeof definition !== "object" || definition === null) {
1241
- throw new Error("Machine initial definition must be an object")
1242
- }
1243
- const selector = makeInitialSelector(stateNodes)
1244
1538
  const initialBuilder = makeSnapshotBuilder(states, { mode: "initial", prefix: "" }) as Record<string, any>
1245
- const branch = captureInitialBranch(definition, selector, initialBuilder)
1539
+ const branch = captureInitialBranch(definition, stateNodes, initialBuilder)
1246
1540
  return {
1247
1541
  initial: (input?: unknown) => {
1248
1542
  const result = branch.resolve === undefined
1249
- ? branch.builder()
1250
- : branch.resolve({ input, target: branch.builder }, undefined)
1543
+ ? constructSelectedTarget(branch.builder)
1544
+ : branch.resolve({ input, target: branch.builder })
1251
1545
  validateInitialSelection(result, branch.selection)
1252
1546
  return result
1253
1547
  },
@@ -1297,7 +1591,7 @@ type MakeConfig<
1297
1591
  InitialE,
1298
1592
  InitialR,
1299
1593
  InternalEvents extends ReadonlyArray<Machine.TaggedSchema>,
1300
- ParentEvents extends ReadonlyArray<Machine.TaggedSchema>
1594
+ ParentDeclaration extends Parent.Any | undefined
1301
1595
  > = {
1302
1596
  readonly id?: string
1303
1597
  readonly states: States & DefineStateTreeInput<NoInfer<States>>
@@ -1311,7 +1605,7 @@ type MakeConfig<
1311
1605
  NoInfer<InternalEvents>
1312
1606
  >
1313
1607
  readonly emittedEvents?: Machine.EventProtocol<"emitted", Emits>
1314
- readonly parentEvents?: Machine.EventProtocol<"public", ParentEvents>
1608
+ readonly parent?: ParentDeclaration
1315
1609
  readonly input?: Input
1316
1610
  readonly initial: unknown
1317
1611
  }
@@ -1324,7 +1618,7 @@ type MakeResult<
1324
1618
  InitialE,
1325
1619
  InitialR,
1326
1620
  InternalEvents extends ReadonlyArray<Machine.TaggedSchema>,
1327
- ParentEvents extends ReadonlyArray<Machine.TaggedSchema>
1621
+ ParentDeclaration extends Parent.Any | undefined
1328
1622
  > = Definition<
1329
1623
  States,
1330
1624
  readonly [...InputEvents, ...InternalEvents],
@@ -1335,7 +1629,7 @@ type MakeResult<
1335
1629
  Machine.TerminalOutput<States>,
1336
1630
  Emits,
1337
1631
  InputEvents,
1338
- ParentEvents
1632
+ Machine.ParentEventsOf<ParentDeclaration>
1339
1633
  >
1340
1634
 
1341
1635
  interface Make {
@@ -1347,11 +1641,11 @@ interface Make {
1347
1641
  InitialE = never,
1348
1642
  InitialR = never,
1349
1643
  const InternalEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly [],
1350
- const ParentEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly []
1644
+ const ParentDeclaration extends Parent.Any | undefined = undefined
1351
1645
  >(
1352
- config: MakeConfig<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentEvents>,
1646
+ config: MakeConfig<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentDeclaration>,
1353
1647
  ..._validation: ValidateDefinedStates<NoInfer<States>>
1354
- ): MakeResult<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentEvents>
1648
+ ): MakeResult<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentDeclaration>
1355
1649
  <
1356
1650
  const States extends Machine.StateSchemas,
1357
1651
  const InputEvents extends ReadonlyArray<Machine.TaggedSchema>,
@@ -1360,10 +1654,13 @@ interface Make {
1360
1654
  InitialE = never,
1361
1655
  InitialR = never,
1362
1656
  const InternalEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly [],
1363
- const ParentEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly []
1657
+ const ParentDeclaration extends Parent.Any | undefined = undefined
1364
1658
  >(
1365
1659
  config:
1366
- & Omit<MakeConfig<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentEvents>, "states">
1660
+ & Omit<
1661
+ MakeConfig<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentDeclaration>,
1662
+ "states"
1663
+ >
1367
1664
  & { readonly states: InvalidDefinedStateTreeInput<States> }
1368
1665
  ): never
1369
1666
  }
@@ -1376,7 +1673,7 @@ export const make: Make = (<
1376
1673
  InitialE = never,
1377
1674
  InitialR = never,
1378
1675
  const InternalEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly [],
1379
- const ParentEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly []
1676
+ const ParentDeclaration extends Parent.Any | undefined = undefined
1380
1677
  >(
1381
1678
  config: {
1382
1679
  readonly id?: string
@@ -1384,18 +1681,18 @@ export const make: Make = (<
1384
1681
  readonly events: Machine.EventProtocol<"public", InputEvents>
1385
1682
  readonly internalEvents?: Machine.EventProtocol<"internal", InternalEvents>
1386
1683
  readonly emittedEvents?: Machine.EventProtocol<"emitted", Emits>
1387
- readonly parentEvents?: Machine.EventProtocol<"public", ParentEvents>
1684
+ readonly parent?: ParentDeclaration
1388
1685
  readonly input?: Input
1389
1686
  readonly initial: unknown
1390
1687
  }
1391
- ): MakeResult<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentEvents> => {
1688
+ ): MakeResult<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentDeclaration> => {
1392
1689
  StateDefinition.validateStateDefinitions(config.states, "Machine.make")
1393
1690
  const self = Object.create(Proto)
1394
1691
  self.states = config.states
1395
1692
  self.events = config.events
1396
1693
  self.internalEvents = config.internalEvents ?? Protocol.makeEventProtocol("internal", [] as const)
1397
1694
  self.emittedEvents = config.emittedEvents ?? Protocol.makeEventProtocol("emitted", [] as const)
1398
- self.parentEvents = config.parentEvents ?? Protocol.makeEventProtocol("public", [] as const)
1695
+ self.parent = config.parent
1399
1696
  self.input = config.input
1400
1697
  self.id = config.id
1401
1698
  self.stateNodes = Topology.compileStateNodes(config.states)
@@ -1405,7 +1702,6 @@ export const make: Make = (<
1405
1702
  self.makeTargetBuilder = makeTargetBuilder(config.states, self.stateNodes)
1406
1703
  self.handlers = Object.create(null)
1407
1704
  self.handle = makeHandle(self)
1408
- self.invoke = makeBoundInvoke
1409
1705
  Protocol.setProtocol(self)
1410
1706
  return self
1411
1707
  }) as Make
@@ -1428,6 +1724,27 @@ export const events = <const Inputs extends ReadonlyArray<Machine.EventProtocolI
1428
1724
  flattenEventProtocolInputs("public", inputs)
1429
1725
  ) as Machine.EventProtocol<"public", Machine.EventProtocolInputSchemasOf<"public", Inputs>>
1430
1726
 
1727
+ const makeParent = <
1728
+ const Mode extends ParentMode,
1729
+ const Events extends ReadonlyArray<Machine.TaggedSchema>
1730
+ >(
1731
+ mode: Mode,
1732
+ events: Machine.EventProtocol<"public", Events>
1733
+ ): Parent<Mode, Events> => {
1734
+ if (!Protocol.isEventProtocol(events, "public")) {
1735
+ throw new Error("Machine parent declarations require a protocol created with Machine.events")
1736
+ }
1737
+ return Object.freeze({ [ParentTypeId]: ParentTypeId, mode, events })
1738
+ }
1739
+
1740
+ export const parent = <const Events extends ReadonlyArray<Machine.TaggedSchema>>(
1741
+ events: Machine.EventProtocol<"public", Events>
1742
+ ): Parent<"required", Events> => makeParent("required", events)
1743
+
1744
+ export const optionalParent = <const Events extends ReadonlyArray<Machine.TaggedSchema>>(
1745
+ events: Machine.EventProtocol<"public", Events>
1746
+ ): Parent<"optional", Events> => makeParent("optional", events)
1747
+
1431
1748
  export const internalEvents = <const Inputs extends ReadonlyArray<Machine.EventProtocolInput<"internal">>>(
1432
1749
  ...inputs: Inputs
1433
1750
  ): Machine.EventProtocol<"internal", Machine.EventProtocolInputSchemasOf<"internal", Inputs>> =>