@typeonce/effect-machine 0.31.1 → 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.
- package/README.md +145 -140
- package/dist/Machine.d.ts +428 -254
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +91 -111
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/atom.d.ts +3 -4
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/atom.js +0 -2
- package/dist/internal/machine/atom.js.map +1 -1
- package/dist/internal/machine/childRegistry.d.ts +52 -0
- package/dist/internal/machine/childRegistry.d.ts.map +1 -0
- package/dist/internal/machine/childRegistry.js +74 -0
- package/dist/internal/machine/childRegistry.js.map +1 -0
- package/dist/internal/machine/cluster.d.ts +2 -3
- package/dist/internal/machine/cluster.d.ts.map +1 -1
- package/dist/internal/machine/cluster.js.map +1 -1
- package/dist/internal/machine/configuration.d.ts.map +1 -1
- package/dist/internal/machine/configuration.js +14 -4
- package/dist/internal/machine/configuration.js.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts +1 -0
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +97 -53
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +4 -2
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/invocationDefinition.d.ts +35 -0
- package/dist/internal/machine/invocationDefinition.d.ts.map +1 -0
- package/dist/internal/machine/invocationDefinition.js +21 -0
- package/dist/internal/machine/invocationDefinition.js.map +1 -0
- package/dist/internal/machine/invocationEvent.d.ts +0 -2
- package/dist/internal/machine/invocationEvent.d.ts.map +1 -1
- package/dist/internal/machine/invocationEvent.js +0 -8
- package/dist/internal/machine/invocationEvent.js.map +1 -1
- package/dist/internal/machine/machine.d.ts +21 -43
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +98 -374
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +13 -19
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +71 -39
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/process.d.ts +6 -8
- package/dist/internal/machine/process.d.ts.map +1 -1
- package/dist/internal/machine/process.js +1 -0
- package/dist/internal/machine/process.js.map +1 -1
- package/dist/internal/machine/protocol.d.ts +2 -2
- package/dist/internal/machine/protocol.d.ts.map +1 -1
- package/dist/internal/machine/protocol.js +4 -18
- package/dist/internal/machine/protocol.js.map +1 -1
- package/dist/internal/machine/readiness.d.ts +1 -1
- package/dist/internal/machine/readiness.d.ts.map +1 -1
- package/dist/internal/machine/requirements.d.ts +4 -0
- package/dist/internal/machine/requirements.d.ts.map +1 -0
- package/dist/internal/machine/requirements.js +2 -0
- package/dist/internal/machine/requirements.js.map +1 -0
- package/dist/internal/machine/runtime.d.ts +4 -58
- package/dist/internal/machine/runtime.d.ts.map +1 -1
- package/dist/internal/machine/runtime.js +1 -69
- package/dist/internal/machine/runtime.js.map +1 -1
- package/dist/internal/machine/serialization.d.ts.map +1 -1
- package/dist/internal/machine/serialization.js +16 -0
- package/dist/internal/machine/serialization.js.map +1 -1
- package/dist/internal/machine/stateDefinition.d.ts +3 -1
- package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
- package/dist/internal/machine/stateDefinition.js +36 -2
- package/dist/internal/machine/stateDefinition.js.map +1 -1
- package/dist/internal/machine/targetBuilder.d.ts +16 -0
- package/dist/internal/machine/targetBuilder.d.ts.map +1 -0
- package/dist/internal/machine/targetBuilder.js +333 -0
- package/dist/internal/machine/targetBuilder.js.map +1 -0
- package/dist/internal/machine/topology.js +3 -3
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/machine/transition.d.ts +16 -0
- package/dist/internal/machine/transition.d.ts.map +1 -0
- package/dist/internal/machine/transition.js +2 -0
- package/dist/internal/machine/transition.js.map +1 -0
- package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
- package/dist/internal/testing/machine/finiteModel.js +10 -12
- package/dist/internal/testing/machine/finiteModel.js.map +1 -1
- package/dist/internal/testing/machine/format.d.ts +7 -0
- package/dist/internal/testing/machine/format.d.ts.map +1 -0
- package/dist/internal/testing/machine/format.js +104 -0
- package/dist/internal/testing/machine/format.js.map +1 -0
- package/dist/internal/testing/machine/probe.d.ts +1 -1
- package/dist/internal/testing/machine/probe.d.ts.map +1 -1
- package/dist/internal/testing/machine/probe.js +1 -1
- package/dist/internal/testing/machine/probe.js.map +1 -1
- package/dist/internal/testing/machine/referenceModel.d.ts.map +1 -1
- package/dist/internal/testing/machine/referenceModel.js +15 -19
- package/dist/internal/testing/machine/referenceModel.js.map +1 -1
- package/dist/internal/testing/machine/trace.d.ts +1 -1
- package/dist/internal/testing/machine/trace.d.ts.map +1 -1
- package/dist/internal/testing/machine/trace.js +3 -3
- package/dist/internal/testing/machine/trace.js.map +1 -1
- package/dist/internal/testing/machine/value.d.ts +8 -0
- package/dist/internal/testing/machine/value.d.ts.map +1 -0
- package/dist/internal/testing/machine/value.js +82 -0
- package/dist/internal/testing/machine/value.js.map +1 -0
- package/dist/internal/testing/machine/verification.d.ts +2 -13
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +21 -113
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +89 -104
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js +70 -84
- package/dist/testing/MachineTest.js.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.d.ts +10 -13
- package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.js +5 -8
- package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +26 -61
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js +17 -26
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/agent-guide.md +45 -45
- package/docs/effect-atom-react.md +35 -63
- package/docs/machine-review.md +7 -7
- package/docs/root-api.md +214 -0
- package/package.json +1 -1
- package/src/Machine.ts +778 -437
- package/src/internal/machine/atom.ts +3 -13
- package/src/internal/machine/childRegistry.ts +141 -0
- package/src/internal/machine/cluster.ts +5 -14
- package/src/internal/machine/configuration.ts +20 -4
- package/src/internal/machine/executionPlan.ts +109 -52
- package/src/internal/machine/invocation.ts +10 -6
- package/src/internal/machine/invocationDefinition.ts +52 -0
- package/src/internal/machine/invocationEvent.ts +0 -8
- package/src/internal/machine/machine.ts +154 -679
- package/src/internal/machine/planner.ts +82 -66
- package/src/internal/machine/process.ts +14 -21
- package/src/internal/machine/protocol.ts +9 -32
- package/src/internal/machine/readiness.ts +1 -0
- package/src/internal/machine/requirements.ts +11 -0
- package/src/internal/machine/runtime.ts +17 -186
- package/src/internal/machine/serialization.ts +22 -0
- package/src/internal/machine/stateDefinition.ts +36 -3
- package/src/internal/machine/targetBuilder.ts +525 -0
- package/src/internal/machine/topology.ts +3 -3
- package/src/internal/machine/transition.ts +26 -0
- package/src/internal/testing/machine/finiteModel.ts +22 -23
- package/src/internal/testing/machine/format.ts +120 -0
- package/src/internal/testing/machine/probe.ts +4 -4
- package/src/internal/testing/machine/referenceModel.ts +25 -17
- package/src/internal/testing/machine/trace.ts +4 -4
- package/src/internal/testing/machine/value.ts +77 -0
- package/src/internal/testing/machine/verification.ts +904 -1109
- package/src/testing/MachineTest.ts +103 -114
- package/src/unstable/cluster/ClusterMachine.ts +10 -20
- 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
|
-
//
|
|
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)
|
|
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(
|
|
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(
|
|
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])
|