@typeonce/effect-machine 0.31.2 → 0.32.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 (151) hide show
  1. package/README.md +145 -140
  2. package/dist/Machine.d.ts +428 -254
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +91 -111
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/atom.d.ts +3 -4
  7. package/dist/internal/machine/atom.d.ts.map +1 -1
  8. package/dist/internal/machine/atom.js +0 -2
  9. package/dist/internal/machine/atom.js.map +1 -1
  10. package/dist/internal/machine/childRegistry.d.ts +52 -0
  11. package/dist/internal/machine/childRegistry.d.ts.map +1 -0
  12. package/dist/internal/machine/childRegistry.js +74 -0
  13. package/dist/internal/machine/childRegistry.js.map +1 -0
  14. package/dist/internal/machine/cluster.d.ts +2 -3
  15. package/dist/internal/machine/cluster.d.ts.map +1 -1
  16. package/dist/internal/machine/cluster.js.map +1 -1
  17. package/dist/internal/machine/configuration.d.ts.map +1 -1
  18. package/dist/internal/machine/configuration.js +14 -4
  19. package/dist/internal/machine/configuration.js.map +1 -1
  20. package/dist/internal/machine/executionPlan.d.ts +1 -0
  21. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  22. package/dist/internal/machine/executionPlan.js +97 -53
  23. package/dist/internal/machine/executionPlan.js.map +1 -1
  24. package/dist/internal/machine/invocation.d.ts.map +1 -1
  25. package/dist/internal/machine/invocation.js +4 -2
  26. package/dist/internal/machine/invocation.js.map +1 -1
  27. package/dist/internal/machine/invocationDefinition.d.ts +35 -0
  28. package/dist/internal/machine/invocationDefinition.d.ts.map +1 -0
  29. package/dist/internal/machine/invocationDefinition.js +21 -0
  30. package/dist/internal/machine/invocationDefinition.js.map +1 -0
  31. package/dist/internal/machine/invocationEvent.d.ts +0 -2
  32. package/dist/internal/machine/invocationEvent.d.ts.map +1 -1
  33. package/dist/internal/machine/invocationEvent.js +0 -8
  34. package/dist/internal/machine/invocationEvent.js.map +1 -1
  35. package/dist/internal/machine/machine.d.ts +21 -43
  36. package/dist/internal/machine/machine.d.ts.map +1 -1
  37. package/dist/internal/machine/machine.js +98 -374
  38. package/dist/internal/machine/machine.js.map +1 -1
  39. package/dist/internal/machine/planner.d.ts +13 -19
  40. package/dist/internal/machine/planner.d.ts.map +1 -1
  41. package/dist/internal/machine/planner.js +63 -35
  42. package/dist/internal/machine/planner.js.map +1 -1
  43. package/dist/internal/machine/process.d.ts +6 -8
  44. package/dist/internal/machine/process.d.ts.map +1 -1
  45. package/dist/internal/machine/process.js +1 -0
  46. package/dist/internal/machine/process.js.map +1 -1
  47. package/dist/internal/machine/protocol.d.ts +2 -2
  48. package/dist/internal/machine/protocol.d.ts.map +1 -1
  49. package/dist/internal/machine/protocol.js +4 -18
  50. package/dist/internal/machine/protocol.js.map +1 -1
  51. package/dist/internal/machine/readiness.d.ts +1 -1
  52. package/dist/internal/machine/readiness.d.ts.map +1 -1
  53. package/dist/internal/machine/requirements.d.ts +4 -0
  54. package/dist/internal/machine/requirements.d.ts.map +1 -0
  55. package/dist/internal/machine/requirements.js +2 -0
  56. package/dist/internal/machine/requirements.js.map +1 -0
  57. package/dist/internal/machine/runtime.d.ts +4 -58
  58. package/dist/internal/machine/runtime.d.ts.map +1 -1
  59. package/dist/internal/machine/runtime.js +1 -69
  60. package/dist/internal/machine/runtime.js.map +1 -1
  61. package/dist/internal/machine/serialization.d.ts.map +1 -1
  62. package/dist/internal/machine/serialization.js +16 -0
  63. package/dist/internal/machine/serialization.js.map +1 -1
  64. package/dist/internal/machine/stateDefinition.d.ts +3 -1
  65. package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
  66. package/dist/internal/machine/stateDefinition.js +36 -2
  67. package/dist/internal/machine/stateDefinition.js.map +1 -1
  68. package/dist/internal/machine/targetBuilder.d.ts +16 -0
  69. package/dist/internal/machine/targetBuilder.d.ts.map +1 -0
  70. package/dist/internal/machine/targetBuilder.js +333 -0
  71. package/dist/internal/machine/targetBuilder.js.map +1 -0
  72. package/dist/internal/machine/topology.js +3 -3
  73. package/dist/internal/machine/topology.js.map +1 -1
  74. package/dist/internal/machine/transition.d.ts +16 -0
  75. package/dist/internal/machine/transition.d.ts.map +1 -0
  76. package/dist/internal/machine/transition.js +2 -0
  77. package/dist/internal/machine/transition.js.map +1 -0
  78. package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
  79. package/dist/internal/testing/machine/finiteModel.js +10 -12
  80. package/dist/internal/testing/machine/finiteModel.js.map +1 -1
  81. package/dist/internal/testing/machine/format.d.ts +7 -0
  82. package/dist/internal/testing/machine/format.d.ts.map +1 -0
  83. package/dist/internal/testing/machine/format.js +104 -0
  84. package/dist/internal/testing/machine/format.js.map +1 -0
  85. package/dist/internal/testing/machine/probe.d.ts +1 -1
  86. package/dist/internal/testing/machine/probe.d.ts.map +1 -1
  87. package/dist/internal/testing/machine/probe.js +1 -1
  88. package/dist/internal/testing/machine/probe.js.map +1 -1
  89. package/dist/internal/testing/machine/referenceModel.d.ts.map +1 -1
  90. package/dist/internal/testing/machine/referenceModel.js +15 -19
  91. package/dist/internal/testing/machine/referenceModel.js.map +1 -1
  92. package/dist/internal/testing/machine/trace.d.ts +1 -1
  93. package/dist/internal/testing/machine/trace.d.ts.map +1 -1
  94. package/dist/internal/testing/machine/trace.js +3 -3
  95. package/dist/internal/testing/machine/trace.js.map +1 -1
  96. package/dist/internal/testing/machine/value.d.ts +8 -0
  97. package/dist/internal/testing/machine/value.d.ts.map +1 -0
  98. package/dist/internal/testing/machine/value.js +82 -0
  99. package/dist/internal/testing/machine/value.js.map +1 -0
  100. package/dist/internal/testing/machine/verification.d.ts +2 -13
  101. package/dist/internal/testing/machine/verification.d.ts.map +1 -1
  102. package/dist/internal/testing/machine/verification.js +21 -113
  103. package/dist/internal/testing/machine/verification.js.map +1 -1
  104. package/dist/testing/MachineTest.d.ts +89 -104
  105. package/dist/testing/MachineTest.d.ts.map +1 -1
  106. package/dist/testing/MachineTest.js +70 -84
  107. package/dist/testing/MachineTest.js.map +1 -1
  108. package/dist/unstable/cluster/ClusterMachine.d.ts +10 -13
  109. package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
  110. package/dist/unstable/cluster/ClusterMachine.js +5 -8
  111. package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
  112. package/dist/unstable/reactivity/AtomMachine.d.ts +26 -61
  113. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  114. package/dist/unstable/reactivity/AtomMachine.js +17 -26
  115. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  116. package/docs/agent-guide.md +45 -45
  117. package/docs/effect-atom-react.md +35 -63
  118. package/docs/machine-review.md +7 -7
  119. package/docs/root-api.md +214 -0
  120. package/package.json +1 -1
  121. package/src/Machine.ts +778 -437
  122. package/src/internal/machine/atom.ts +3 -13
  123. package/src/internal/machine/childRegistry.ts +141 -0
  124. package/src/internal/machine/cluster.ts +5 -14
  125. package/src/internal/machine/configuration.ts +20 -4
  126. package/src/internal/machine/executionPlan.ts +109 -52
  127. package/src/internal/machine/invocation.ts +10 -6
  128. package/src/internal/machine/invocationDefinition.ts +52 -0
  129. package/src/internal/machine/invocationEvent.ts +0 -8
  130. package/src/internal/machine/machine.ts +154 -679
  131. package/src/internal/machine/planner.ts +74 -62
  132. package/src/internal/machine/process.ts +14 -21
  133. package/src/internal/machine/protocol.ts +9 -32
  134. package/src/internal/machine/readiness.ts +1 -0
  135. package/src/internal/machine/requirements.ts +11 -0
  136. package/src/internal/machine/runtime.ts +17 -186
  137. package/src/internal/machine/serialization.ts +22 -0
  138. package/src/internal/machine/stateDefinition.ts +36 -3
  139. package/src/internal/machine/targetBuilder.ts +525 -0
  140. package/src/internal/machine/topology.ts +3 -3
  141. package/src/internal/machine/transition.ts +26 -0
  142. package/src/internal/testing/machine/finiteModel.ts +22 -23
  143. package/src/internal/testing/machine/format.ts +120 -0
  144. package/src/internal/testing/machine/probe.ts +4 -4
  145. package/src/internal/testing/machine/referenceModel.ts +25 -17
  146. package/src/internal/testing/machine/trace.ts +4 -4
  147. package/src/internal/testing/machine/value.ts +77 -0
  148. package/src/internal/testing/machine/verification.ts +904 -1109
  149. package/src/testing/MachineTest.ts +103 -114
  150. package/src/unstable/cluster/ClusterMachine.ts +10 -20
  151. package/src/unstable/reactivity/AtomMachine.ts +42 -79
@@ -0,0 +1,120 @@
1
+ import type { Machine } from "../../../Machine.js"
2
+ import type { InitialTrace, Microstep, RunFailure, Trace, TraceStep } from "../../../testing/MachineTest.js"
3
+ type AnyMachine = Machine.Any
4
+
5
+ const canonicalize = (value: unknown, active: WeakSet<object>): unknown => {
6
+ if (typeof value === "number" && (!Number.isFinite(value) || Object.is(value, -0))) {
7
+ return { $number: Object.is(value, -0) ? "-0" : String(value) }
8
+ }
9
+ if (value === undefined) return { $undefined: true }
10
+ if (typeof value === "bigint") return { $bigint: String(value) }
11
+ if (typeof value === "symbol") return { $symbol: String(value) }
12
+ if (typeof value === "function") return { $function: value.name || "anonymous" }
13
+ if (typeof value !== "object" || value === null) return value
14
+ if (active.has(value)) return { $circular: true }
15
+ active.add(value)
16
+ let result: unknown
17
+ if (value instanceof Error) {
18
+ result = {
19
+ $error: value.name,
20
+ message: value.message,
21
+ ...Object.fromEntries(
22
+ Object.keys(value).sort().map((
23
+ key
24
+ ) => [key, canonicalize((value as unknown as Record<string, unknown>)[key], active)])
25
+ )
26
+ }
27
+ } else if (Array.isArray(value)) {
28
+ result = value.map((item) => canonicalize(item, active))
29
+ } else if (value instanceof RegExp) {
30
+ result = { $regexp: value.source, flags: value.flags, lastIndex: value.lastIndex }
31
+ } else if (value instanceof Date) {
32
+ result = { $date: Number.isNaN(value.getTime()) ? "Invalid Date" : value.toISOString() }
33
+ } else if (value instanceof Map) {
34
+ result = {
35
+ $map: Array.from(value, ([key, item]) => [canonicalize(key, active), canonicalize(item, active)]).sort((a, b) =>
36
+ JSON.stringify(a).localeCompare(JSON.stringify(b))
37
+ )
38
+ }
39
+ } else if (value instanceof Set) {
40
+ result = {
41
+ $set: Array.from(value, (item) => canonicalize(item, active)).sort((a, b) =>
42
+ JSON.stringify(a).localeCompare(JSON.stringify(b))
43
+ )
44
+ }
45
+ } else {
46
+ result = Object.fromEntries(
47
+ Object.keys(value).sort().map((key) => [key, canonicalize((value as Record<string, unknown>)[key], active)])
48
+ )
49
+ }
50
+ active.delete(value)
51
+ return result
52
+ }
53
+
54
+ export const formatValue = (value: unknown): string => JSON.stringify(canonicalize(value, new WeakSet()))
55
+
56
+ const formatConfiguration = (paths: ReadonlyArray<string>): string =>
57
+ `[${paths.map((path) => path === "" ? "(root)" : path).join(", ")}]`
58
+
59
+ const formatMicrosteps = <M extends AnyMachine>(microsteps: ReadonlyArray<Microstep<M, any>>): Array<string> =>
60
+ microsteps.map((microstep, index) => {
61
+ const transitions = microstep.transitions.map((transition) => ({
62
+ source: transition.source,
63
+ trigger: transition.trigger,
64
+ reenter: transition.reenter,
65
+ branchIndex: transition.branchIndex,
66
+ target: transition.target,
67
+ resolvedTarget: transition.resolvedTarget
68
+ }))
69
+ return ` microstep ${index}: event=${formatValue(microstep.event)} changed=${String(microstep.changed)} ` +
70
+ `transitions=${formatValue(transitions)} exit=${formatConfiguration(microstep.exitPaths)} ` +
71
+ `entry=${formatConfiguration(microstep.entryPaths)} commands=${microstep.commands.length} ` +
72
+ `raised=${formatValue(microstep.raisedEvents)} emitted=${formatValue(microstep.emittedEvents)} ` +
73
+ `next=${formatValue(microstep.next)}`
74
+ })
75
+
76
+ const formatInitial = <M extends AnyMachine>(initial: InitialTrace<M>): Array<string> => [
77
+ `initial: startingConfiguration=${formatConfiguration(initial.startingConfiguration)} ` +
78
+ `startingState=${formatValue(initial.startingState)} initialEntry=${
79
+ formatConfiguration(initial.initialEntryPaths)
80
+ } ` +
81
+ `configuration=${formatConfiguration(initial.configuration)} state=${formatValue(initial.plan.state)} ` +
82
+ `done=${String(initial.plan.done)} output=${formatValue(initial.plan.output)} ` +
83
+ `commands=${initial.plan.commands.length} emitted=${formatValue(initial.plan.emittedEvents)}`,
84
+ ...formatMicrosteps(initial.plan.microsteps)
85
+ ]
86
+
87
+ const formatStep = <M extends AnyMachine>(step: TraceStep<M>): Array<string> => [
88
+ `step ${step.index}: event=${formatValue(step.event)} before=${formatConfiguration(step.beforeConfiguration)} ` +
89
+ `after=${formatConfiguration(step.afterConfiguration)} state=${formatValue(step.after)} ` +
90
+ `done=${String(step.plan.done)} output=${formatValue(step.plan.output)} ` +
91
+ `commands=${step.plan.commands.length} emitted=${formatValue(step.plan.emittedEvents)}`,
92
+ ...formatMicrosteps(step.plan.microsteps)
93
+ ]
94
+
95
+ const isRunFailure = <M extends AnyMachine, Cause>(
96
+ trace: Trace<M> | RunFailure<Cause, M>
97
+ ): trace is RunFailure<Cause, M> => "_tag" in trace && trace._tag === "MachineTestRunFailure"
98
+
99
+ export const formatTrace = <M extends AnyMachine, Cause>(trace: Trace<M> | RunFailure<Cause, M>): string => {
100
+ const lines = [`scenario: ${formatValue(trace.scenario)}`]
101
+ if (isRunFailure(trace)) {
102
+ if (trace.initial !== undefined) {
103
+ lines.push(...formatInitial(trace.initial))
104
+ for (const step of trace.steps) {
105
+ lines.push(...formatStep(step))
106
+ }
107
+ }
108
+ lines.push(
109
+ `failure: phase=${trace.phase} eventIndex=${formatValue(trace.eventIndex)} ` +
110
+ `event=${formatValue(trace.event)} cause=${formatValue(trace.cause)}`
111
+ )
112
+ return lines.join("\n")
113
+ }
114
+ lines.push(...formatInitial(trace.initial))
115
+ for (const step of trace.steps) {
116
+ lines.push(...formatStep(step))
117
+ }
118
+ lines.push(`final: configuration=${formatConfiguration(trace.finalConfiguration)} state=${formatValue(trace.final)}`)
119
+ return lines.join("\n")
120
+ }
@@ -27,14 +27,14 @@ export const probe = <M extends AnyMachine, Error, Output>(
27
27
  machine: M,
28
28
  ref: Machine.MachineRef<
29
29
  Machine.Machine.Snapshot<Machine.Machine.States<M>>,
30
- Machine.Machine.InputEvent<M>,
30
+ Machine.Machine.EventInput<Machine.Machine.InputEvent<M>>,
31
31
  Error,
32
32
  Output
33
33
  >
34
34
  ): Effect.Effect<Probe<M, Error, Output>, ProbeUnavailableError> => {
35
35
  const acknowledged = (ref as Runtime.MachineRef<
36
36
  Machine.Machine.Snapshot<Machine.Machine.States<M>>,
37
- Machine.Machine.InputEvent<M>,
37
+ Machine.Machine.EventInput<Machine.Machine.InputEvent<M>>,
38
38
  Error,
39
39
  Output
40
40
  >)[Runtime.acknowledgedSend]
@@ -61,12 +61,12 @@ export const probe = <M extends AnyMachine, Error, Output>(
61
61
  ) => boolean
62
62
  ) => ({ _tag: "Until", predicate } as const)
63
63
  }),
64
- sendAndAwait: (event: Machine.Machine.InputEvent<M>) =>
64
+ sendAndAwait: (event: Machine.Machine.EventInput<Machine.Machine.InputEvent<M>>) =>
65
65
  acknowledged.call(ref, event).pipe(
66
66
  Effect.map(({ after, before, plan }) => {
67
67
  const eventPlan = plan as ProbePlan<M>
68
68
  return Object.freeze({
69
- event,
69
+ event: eventPlan.event,
70
70
  before,
71
71
  plan: eventPlan,
72
72
  after,
@@ -573,10 +573,7 @@ const runtimeTargetPath = (index: ModelIndex, transition: FiniteTransition): str
573
573
  if (target.node._tag === "Choice") {
574
574
  return runtimeTargetPath(index, { ...transition, target: resolveChoicePath(index, target.path) })
575
575
  }
576
- // A same-root branch builder identifies the concrete initialized leaf. A
577
- // full builder replaces the root with a complete snapshot and identifies
578
- // that snapshot's root even when it contains initialized descendants.
579
- if (source.root !== target.root) return target.path
576
+ // Branch builders preserve the deepest concrete selection within their declared bound.
580
577
 
581
578
  const initial = (path: string): string => {
582
579
  const current = getState(index, path)
@@ -603,7 +600,7 @@ const runtimeTargetPath = (index: ModelIndex, transition: FiniteTransition): str
603
600
  const next = target.path.slice(current.path.length + 1).split(".")[0]!
604
601
  return inspect(`${current.path}.${next}`)
605
602
  }
606
- return inspect(source.root)
603
+ return inspect(source.root === target.root ? source.root : target.root)
607
604
  }
608
605
 
609
606
  const entryChoicePath = (index: ModelIndex, path: string): string | undefined => {
@@ -916,8 +913,6 @@ const transitionRecord = (index: ModelIndex, transition: FiniteTransition): Refe
916
913
  ? transition.target
917
914
  : transition.target === undefined
918
915
  ? runtimeTargetPath(index, transition)
919
- : getState(index, transition.source).root !== getState(index, transition.target).root
920
- ? transition.target
921
916
  : entryChoicePath(index, transition.target) ?? runtimeTargetPath(index, transition),
922
917
  resolvedTarget: transition.target === undefined
923
918
  ? runtimeTargetPath(index, transition)
@@ -1269,7 +1264,7 @@ const projectState = (snapshot: unknown): ActualStateProjection => {
1269
1264
  const values: Record<string, unknown> = {}
1270
1265
  const visit = (current: unknown): void => {
1271
1266
  if (!isRecord(current)) return
1272
- if (typeof current.path === "string") {
1267
+ if (typeof current.path === "string" && current.path !== "") {
1273
1268
  activePaths.push(current.path)
1274
1269
  values[current.path] = current.value
1275
1270
  }
@@ -1282,7 +1277,9 @@ const projectState = (snapshot: unknown): ActualStateProjection => {
1282
1277
  return {
1283
1278
  activePaths,
1284
1279
  values,
1285
- completions: isRecord(snapshot) && Array.isArray(snapshot.completed) ? snapshot.completed : [],
1280
+ completions: isRecord(snapshot) && Array.isArray(snapshot.completed)
1281
+ ? snapshot.completed.filter((completion) => !isRecord(completion) || completion.path !== "")
1282
+ : [],
1286
1283
  history: isRecord(snapshot) && isRecord(snapshot.history) ? snapshot.history : {}
1287
1284
  }
1288
1285
  }
@@ -1402,7 +1399,13 @@ export const verifyModelTrace = (
1402
1399
  add(
1403
1400
  location,
1404
1401
  `${field}.history`,
1405
- historyOrderIndependent(expected.history),
1402
+ historyOrderIndependent(
1403
+ Object.fromEntries(
1404
+ Object.entries(expected.history).map((
1405
+ [path, record]
1406
+ ) => [path, { ...record, active: ["", ...record.active] }])
1407
+ )
1408
+ ),
1406
1409
  historyOrderIndependent(projected.history)
1407
1410
  )
1408
1411
  }
@@ -1422,26 +1425,26 @@ export const verifyModelTrace = (
1422
1425
  add(
1423
1426
  initialLocation,
1424
1427
  "initial.startingConfiguration",
1425
- reference.initial.startingState.activePaths,
1428
+ ["", ...reference.initial.startingState.activePaths],
1426
1429
  get(actualInitial, "startingConfiguration")
1427
1430
  )
1428
1431
  add(
1429
1432
  initialLocation,
1430
1433
  "initial.initialEntryPaths",
1431
- reference.initial.initialEntryPaths,
1434
+ ["", ...reference.initial.initialEntryPaths],
1432
1435
  get(actualInitial, "initialEntryPaths")
1433
1436
  )
1434
1437
  add(
1435
1438
  initialLocation,
1436
1439
  "initial.plan.initialEntryPaths",
1437
- reference.initial.initialEntryPaths,
1440
+ ["", ...reference.initial.initialEntryPaths],
1438
1441
  get(actualInitialPlan, "initialEntryPaths")
1439
1442
  )
1440
1443
  compareState(initialLocation, "initial.plan.state", reference.initial.state, get(actualInitialPlan, "state"))
1441
1444
  add(
1442
1445
  initialLocation,
1443
1446
  "initial.configuration",
1444
- reference.initial.state.activePaths,
1447
+ ["", ...reference.initial.state.activePaths],
1445
1448
  get(actualInitial, "configuration")
1446
1449
  )
1447
1450
  add(
@@ -1463,7 +1466,7 @@ export const verifyModelTrace = (
1463
1466
  add(location, "step.index", expected.index, get(actual, "index"))
1464
1467
  add(location, "step.event", expected.event, eventTag(get(actual, "event")))
1465
1468
  compareState(location, "step.before", expected.before, get(actual, "before"))
1466
- add(location, "step.beforeConfiguration", expected.before.activePaths, get(actual, "beforeConfiguration"))
1469
+ add(location, "step.beforeConfiguration", ["", ...expected.before.activePaths], get(actual, "beforeConfiguration"))
1467
1470
 
1468
1471
  const actualMicrosteps = array(get(actualPlan, "microsteps"))
1469
1472
  add(location, "step.plan.microsteps.length", expected.microsteps.length, actualMicrosteps.length)
@@ -1490,14 +1493,19 @@ export const verifyModelTrace = (
1490
1493
 
1491
1494
  compareState(location, "step.plan.next", expected.after, get(actualPlan, "next"))
1492
1495
  compareState(location, "step.after", expected.after, get(actual, "after"))
1493
- add(location, "step.afterConfiguration", expected.after.activePaths, get(actual, "afterConfiguration"))
1496
+ add(location, "step.afterConfiguration", ["", ...expected.after.activePaths], get(actual, "afterConfiguration"))
1494
1497
  add(location, "step.plan.done", expected.done, get(actualPlan, "done"))
1495
1498
  add(location, "step.plan.output", expected.output, get(actualPlan, "output"))
1496
1499
  }
1497
1500
 
1498
1501
  const finalLocation: ModelVerificationLocation = { phase: "final" }
1499
1502
  compareState(finalLocation, "trace.final", reference.final, get(actualTrace, "final"))
1500
- add(finalLocation, "trace.finalConfiguration", reference.final.activePaths, get(actualTrace, "finalConfiguration"))
1503
+ add(
1504
+ finalLocation,
1505
+ "trace.finalConfiguration",
1506
+ ["", ...reference.final.activePaths],
1507
+ get(actualTrace, "finalConfiguration")
1508
+ )
1501
1509
 
1502
1510
  return mismatches.length === 0
1503
1511
  ? Effect.void
@@ -57,7 +57,7 @@ export const rawConfigurationPaths = <M extends AnyMachine>(
57
57
  export const appendTrace = <M extends AnyMachine>(
58
58
  machine: ReadyMachine<M>,
59
59
  trace: Trace<M>,
60
- event: Machine.Machine.InputEvent<M>,
60
+ event: Machine.Machine.EventInput<Machine.Machine.InputEvent<M>>,
61
61
  scenario: Scenario<M>
62
62
  ): Effect.Effect<Trace<M>, RunFailure<RunError<M>, M>, RunServices<M>> => {
63
63
  const index = trace.steps.length
@@ -83,13 +83,13 @@ export const appendTrace = <M extends AnyMachine>(
83
83
  index,
84
84
  before,
85
85
  beforeConfiguration,
86
- event,
86
+ event: plan.event,
87
87
  plan,
88
88
  after: plan.next,
89
89
  afterConfiguration: rawConfigurationPaths(machine, plan.next)
90
90
  }
91
91
  return {
92
- scenario,
92
+ scenario: { ...scenario, events: [...trace.steps.map((step) => step.event), plan.event] },
93
93
  initial: trace.initial,
94
94
  steps: [...trace.steps, step],
95
95
  final: plan.next,
@@ -135,7 +135,7 @@ export const run: <M extends AnyMachine>(
135
135
  configuration: rawConfigurationPaths(machine, initial.state)
136
136
  }
137
137
  let trace: Trace<M> = {
138
- scenario,
138
+ scenario: { ...scenario, events: [] },
139
139
  initial: initialTrace,
140
140
  steps: [],
141
141
  final: initial.state,
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Compares decoded trace data without serializing it or invoking getters.
3
+ * Collection order and cycles must agree. Decoding may copy a shared value,
4
+ * so aliases outside the current comparison path do not require identity.
5
+ * Functions, symbols, and objects with opaque state retain identity semantics.
6
+ */
7
+ export const sameValue = (left: unknown, right: unknown): boolean => {
8
+ const leftToRight = new WeakMap<object, object>()
9
+ const rightToLeft = new WeakMap<object, object>()
10
+ const compare = (a: unknown, b: unknown): boolean => {
11
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
12
+ return Object.is(a, b)
13
+ }
14
+ const knownRight = leftToRight.get(a)
15
+ const knownLeft = rightToLeft.get(b)
16
+ if (knownRight !== undefined || knownLeft !== undefined) return knownRight === b && knownLeft === a
17
+ leftToRight.set(a, b)
18
+ rightToLeft.set(b, a)
19
+ try {
20
+ if (a === b) return true
21
+ if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) return false
22
+
23
+ if (a instanceof Date && b instanceof Date && !Object.is(a.getTime(), b.getTime())) return false
24
+ if (a instanceof RegExp && b instanceof RegExp && (a.source !== b.source || a.flags !== b.flags)) return false
25
+ if (a instanceof ArrayBuffer && b instanceof ArrayBuffer) {
26
+ if (!sameBytes(new Uint8Array(a), new Uint8Array(b))) return false
27
+ } else if (
28
+ typeof SharedArrayBuffer !== "undefined" && a instanceof SharedArrayBuffer && b instanceof SharedArrayBuffer
29
+ ) {
30
+ if (!sameBytes(new Uint8Array(a), new Uint8Array(b))) return false
31
+ } else if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
32
+ if (
33
+ !sameBytes(
34
+ new Uint8Array(a.buffer, a.byteOffset, a.byteLength),
35
+ new Uint8Array(b.buffer, b.byteOffset, b.byteLength)
36
+ )
37
+ ) return false
38
+ } else if (a instanceof Map && b instanceof Map) {
39
+ if (a.size !== b.size) return false
40
+ const entries = Array.from(b)
41
+ if (
42
+ !Array.from(a).every(([key, value], index) =>
43
+ compare(key, entries[index]![0]) && compare(value, entries[index]![1])
44
+ )
45
+ ) return false
46
+ } else if (a instanceof Set && b instanceof Set) {
47
+ if (a.size !== b.size) return false
48
+ const values = Array.from(b)
49
+ if (!Array.from(a).every((value, index) => compare(value, values[index]))) return false
50
+ } else if (a instanceof WeakMap || a instanceof WeakSet || a instanceof Promise) {
51
+ return false
52
+ }
53
+
54
+ const keys = Reflect.ownKeys(a)
55
+ if (keys.length !== Reflect.ownKeys(b).length) return false
56
+ if (
57
+ keys.length === 0 && Object.getPrototypeOf(a) !== null && Object.getPrototypeOf(a) !== Object.prototype &&
58
+ !(a instanceof Date || a instanceof RegExp || a instanceof Map || a instanceof Set ||
59
+ a instanceof ArrayBuffer || ArrayBuffer.isView(a)) &&
60
+ !(typeof SharedArrayBuffer !== "undefined" && a instanceof SharedArrayBuffer)
61
+ ) return false
62
+ return keys.every((key) => {
63
+ const x = Object.getOwnPropertyDescriptor(a, key)!
64
+ const y = Object.getOwnPropertyDescriptor(b, key)
65
+ if (y === undefined || x.enumerable !== y.enumerable || ("value" in x) !== ("value" in y)) return false
66
+ return "value" in x ? compare(x.value, y.value) : x.get === y.get && x.set === y.set
67
+ })
68
+ } finally {
69
+ leftToRight.delete(a)
70
+ rightToLeft.delete(b)
71
+ }
72
+ }
73
+ return compare(left, right)
74
+ }
75
+
76
+ const sameBytes = (left: Uint8Array, right: Uint8Array): boolean =>
77
+ left.byteLength === right.byteLength && left.every((byte, index) => byte === right[index])