@mantaq/traversal 0.2.0 → 0.3.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/dist/index.d.mts +41 -34
- package/dist/index.mjs +213 -169
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { AnyActor, Context, ErrorInfo, InternalEvent, Snapshot, TransitionInfo } from "@mantaq/core";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
payload?: unknown;
|
|
7
|
-
}
|
|
8
|
-
/** Simplified handler return — matches TransitionResult with erased generics */
|
|
2
|
+
/**
|
|
3
|
+
* Type guard for untyped transition-handler results. The single sanctioned
|
|
4
|
+
* `as` site for graph invocation: handler output crosses the untyped boundary.
|
|
5
|
+
*/
|
|
9
6
|
interface HandlerResult {
|
|
10
7
|
state?: {
|
|
11
8
|
name?: string;
|
|
@@ -14,6 +11,11 @@ interface HandlerResult {
|
|
|
14
11
|
type?: string;
|
|
15
12
|
}>;
|
|
16
13
|
}
|
|
14
|
+
/** Synthetic event object for graph introspection — matches minimal InternalEvent shape */
|
|
15
|
+
interface SyntheticEvent {
|
|
16
|
+
type: string;
|
|
17
|
+
payload?: unknown;
|
|
18
|
+
}
|
|
17
19
|
type TransitionHandler = (event: SyntheticEvent, options: {
|
|
18
20
|
context: Context<Record<string, unknown>>;
|
|
19
21
|
actor: AnyActor<unknown>;
|
|
@@ -31,6 +33,13 @@ interface GraphNode {
|
|
|
31
33
|
isActive: boolean;
|
|
32
34
|
isFinal: boolean;
|
|
33
35
|
isInitial?: boolean;
|
|
36
|
+
/** The `*` source of `onAny` edges: any non-final state. */
|
|
37
|
+
isAny?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** An effect a state runs on entry. `sends`: declared event types; null = not declared. */
|
|
40
|
+
interface GraphEffect {
|
|
41
|
+
name: string;
|
|
42
|
+
sends: string[] | null;
|
|
34
43
|
}
|
|
35
44
|
interface GraphEdge {
|
|
36
45
|
id: string;
|
|
@@ -40,10 +49,16 @@ interface GraphEdge {
|
|
|
40
49
|
isActive: boolean;
|
|
41
50
|
isInternal?: boolean;
|
|
42
51
|
isUndetermined?: boolean;
|
|
52
|
+
/** Handled without a state change: the registration declares no `to`. */
|
|
53
|
+
isStay?: boolean;
|
|
54
|
+
/** Target comes from the builder's `to` list, not from running the handler. */
|
|
55
|
+
isDeclared?: boolean;
|
|
43
56
|
payload?: {
|
|
44
57
|
action?: string;
|
|
45
58
|
};
|
|
46
59
|
contexts: string[];
|
|
60
|
+
/** Effect self-loops only: the effects the state runs on entry, in registration order. */
|
|
61
|
+
effects?: GraphEffect[];
|
|
47
62
|
}
|
|
48
63
|
interface ActorGraph {
|
|
49
64
|
nodes: GraphNode[];
|
|
@@ -68,8 +83,6 @@ interface HistoryEntry {
|
|
|
68
83
|
type: "state_visit" | "transition" | "effect" | "send";
|
|
69
84
|
data: StateVisit | TransitionRecord | EffectRecord | SendRecord;
|
|
70
85
|
}
|
|
71
|
-
//#endregion
|
|
72
|
-
//#region src/history.d.ts
|
|
73
86
|
interface History {
|
|
74
87
|
append(entry: HistoryEntry): void;
|
|
75
88
|
entries(): readonly HistoryEntry[];
|
|
@@ -85,28 +98,25 @@ interface History {
|
|
|
85
98
|
* History records the runtime trace of an instrumented actor. A closure over
|
|
86
99
|
* the entry list keeps the recorder mutable without exposing the raw array.
|
|
87
100
|
*/
|
|
88
|
-
declare function createHistory(): History;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
declare const
|
|
92
|
-
declare function
|
|
101
|
+
export declare function createHistory(): History;
|
|
102
|
+
export declare const INITIAL_NODE_ID = "__initial__";
|
|
103
|
+
/** Source of `onAny` edges: any non-final state of the machine (`region.*` inside a region). */
|
|
104
|
+
export declare const ANY_NODE_NAME = "*";
|
|
105
|
+
export declare function isAnyNode(id: string): boolean;
|
|
106
|
+
export declare function collectActiveStates(snapshot: Snapshot, options: {
|
|
93
107
|
prefix: string;
|
|
94
108
|
activeSet: Set<string>;
|
|
95
109
|
}): void;
|
|
96
|
-
declare function buildGraph<C>(actor: AnyActor<C>, options?: {
|
|
110
|
+
export declare function buildGraph<C>(actor: AnyActor<C>, options?: {
|
|
97
111
|
internalIds?: Set<string>;
|
|
98
112
|
sampleContext?: Record<string, unknown>;
|
|
99
113
|
sampleContexts?: Record<string, Record<string, unknown>>;
|
|
100
114
|
}): ActorGraph;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
declare function
|
|
104
|
-
declare function
|
|
105
|
-
declare function
|
|
106
|
-
declare function parseContextRecord(context: unknown): Record<string, unknown>;
|
|
107
|
-
declare function parseInternalEventIds(options: unknown): Set<string>;
|
|
108
|
-
//#endregion
|
|
109
|
-
//#region src/instrument.d.ts
|
|
115
|
+
export declare function parseStates(options: unknown): ReadonlyArray<StateDef>;
|
|
116
|
+
export declare function parseTransitionMap(source: unknown): TransitionDispatchMap;
|
|
117
|
+
export declare function parseInitialName(options: unknown): string | undefined;
|
|
118
|
+
export declare function parseContextRecord(context: unknown): Record<string, unknown>;
|
|
119
|
+
export declare function parseInternalEventIds(options: unknown): Set<string>;
|
|
110
120
|
type SubscriberEventName = "change" | "done" | "transition" | "error" | "output";
|
|
111
121
|
type SubscriberHandler<C, E extends SubscriberEventName> = E extends "change" ? (snapshot: Snapshot<C>, prev: Snapshot<C>) => void : E extends "done" ? () => void : E extends "transition" ? (info: TransitionInfo) => void : E extends "error" ? (info: ErrorInfo) => void : E extends "output" ? (event: InternalEvent) => void : never;
|
|
112
122
|
interface InstrumentedActor<C = Record<string, unknown>> {
|
|
@@ -129,24 +139,21 @@ interface InstrumentedActor<C = Record<string, unknown>> {
|
|
|
129
139
|
inject(event: InternalEvent): void;
|
|
130
140
|
dispose(): void;
|
|
131
141
|
}
|
|
132
|
-
declare function instrument<C>(actor: AnyActor<C>): InstrumentedActor<C>;
|
|
133
|
-
|
|
134
|
-
//#region src/algorithms.d.ts
|
|
135
|
-
declare function reachable(graph: ActorGraph, endpoints: {
|
|
142
|
+
export declare function instrument<C>(actor: AnyActor<C>): InstrumentedActor<C>;
|
|
143
|
+
export declare function reachable(graph: ActorGraph, endpoints: {
|
|
136
144
|
fromId: string;
|
|
137
145
|
toId: string;
|
|
138
146
|
}): boolean;
|
|
139
|
-
declare function allPaths(graph: ActorGraph, endpoints: {
|
|
147
|
+
export declare function allPaths(graph: ActorGraph, endpoints: {
|
|
140
148
|
fromId: string;
|
|
141
149
|
toId: string;
|
|
142
150
|
}): string[][];
|
|
143
|
-
declare function findCycles(graph: ActorGraph): string[][];
|
|
144
|
-
declare function unreachableNodes(graph: ActorGraph, options: {
|
|
151
|
+
export declare function findCycles(graph: ActorGraph): string[][];
|
|
152
|
+
export declare function unreachableNodes(graph: ActorGraph, options: {
|
|
145
153
|
fromId: string;
|
|
146
154
|
}): string[];
|
|
147
|
-
declare function shortestPath(graph: ActorGraph, endpoints: {
|
|
155
|
+
export declare function shortestPath(graph: ActorGraph, endpoints: {
|
|
148
156
|
fromId: string;
|
|
149
157
|
toId: string;
|
|
150
158
|
}): string[] | undefined;
|
|
151
|
-
|
|
152
|
-
export { type ActorGraph, type EffectRecord, type GraphEdge, type GraphNode, type History, type HistoryEntry, INITIAL_NODE_ID, type InstrumentedActor, type SendRecord, type StateDef, type StateVisit, type SyntheticEvent, type TransitionDispatchMap, type TransitionHandler, type TransitionRecord, allPaths, buildGraph, collectActiveStates, createHistory, findCycles, instrument, parseContextRecord, parseInitialName, parseInternalEventIds, parseStates, parseTransitionMap, reachable, shortestPath, unreachableNodes };
|
|
159
|
+
export type { ActorGraph, EffectRecord, GraphEdge, GraphNode, History, HistoryEntry, InstrumentedActor, SendRecord, StateDef, StateVisit, SyntheticEvent, TransitionDispatchMap, TransitionHandler, TransitionRecord };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Context, isStateRef } from "@mantaq/core";
|
|
2
2
|
import { Either } from "@mantaq/utils";
|
|
3
|
-
|
|
3
|
+
// src/history.ts
|
|
4
4
|
function entriesOfType(recordedEntries, options) {
|
|
5
5
|
return recordedEntries.filter((entry) => entry.type === options.type).map((entry) => entry.data);
|
|
6
6
|
}
|
|
@@ -53,15 +53,177 @@ function createHistory() {
|
|
|
53
53
|
reset
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
// src/graph-ids.ts
|
|
57
|
+
const INITIAL_NODE_ID = "__initial__";
|
|
58
|
+
/** Source of `onAny` edges: any non-final state of the machine (`region.*` inside a region). */
|
|
59
|
+
const ANY_NODE_NAME = "*";
|
|
60
|
+
function isAnyNode(id) {
|
|
61
|
+
return id === "*" || id.endsWith(`.*`);
|
|
62
|
+
}
|
|
63
|
+
function collectActiveStates(snapshot, options) {
|
|
64
|
+
if (!snapshot.path) return;
|
|
65
|
+
const currentName = snapshot.path[snapshot.path.length - 1];
|
|
66
|
+
if (!currentName) return;
|
|
67
|
+
const fullId = nodeId({
|
|
68
|
+
prefix: options.prefix,
|
|
69
|
+
name: currentName
|
|
70
|
+
});
|
|
71
|
+
options.activeSet.add(fullId);
|
|
72
|
+
if (snapshot.regions && !snapshot.done && !snapshot.error) for (const [regionName, regionSnapshot] of Object.entries(snapshot.regions)) collectActiveStates(regionSnapshot, {
|
|
73
|
+
prefix: regionName,
|
|
74
|
+
activeSet: options.activeSet
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function nodeId(identity) {
|
|
78
|
+
return identity.prefix ? `${identity.prefix}.${identity.name}` : identity.name;
|
|
79
|
+
}
|
|
80
|
+
function collectActorsFromSnapshot(snapshot) {
|
|
81
|
+
const activeSet = /* @__PURE__ */ new Set();
|
|
82
|
+
collectActiveStates(snapshot, {
|
|
83
|
+
prefix: "",
|
|
84
|
+
activeSet
|
|
85
|
+
});
|
|
86
|
+
return activeSet;
|
|
87
|
+
}
|
|
88
|
+
// src/is-handler-result.ts
|
|
58
89
|
function isHandlerResult(value) {
|
|
59
90
|
if (typeof value !== "object" || value === null) return false;
|
|
60
91
|
const candidate = value;
|
|
61
92
|
return (candidate.state === void 0 || typeof candidate.state === "object" && candidate.state !== null && typeof candidate.state.name === "string") && (candidate.emit === void 0 || Array.isArray(candidate.emit));
|
|
62
93
|
}
|
|
63
|
-
|
|
64
|
-
|
|
94
|
+
// src/graph-edges.ts
|
|
95
|
+
function invokeHandler(handler, options) {
|
|
96
|
+
return Either.from(() => {
|
|
97
|
+
const result = handler({
|
|
98
|
+
type: options.eventId,
|
|
99
|
+
payload: {}
|
|
100
|
+
}, {
|
|
101
|
+
context: Context({
|
|
102
|
+
get: () => options.context,
|
|
103
|
+
set: () => {}
|
|
104
|
+
}),
|
|
105
|
+
actor: options.actor
|
|
106
|
+
});
|
|
107
|
+
return isHandlerResult(result) ? {
|
|
108
|
+
targetName: result.state?.name,
|
|
109
|
+
emitNames: result.emit?.map((emitted) => emitted.type).filter((eventType) => Boolean(eventType)) ?? []
|
|
110
|
+
} : {
|
|
111
|
+
targetName: void 0,
|
|
112
|
+
emitNames: []
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function probeAll(registration, pass) {
|
|
117
|
+
const probes = [];
|
|
118
|
+
for (const ctxName of pass.traversal.contextNames) {
|
|
119
|
+
const sampleContext = pass.traversal.contexts[ctxName];
|
|
120
|
+
if (sampleContext === void 0) continue;
|
|
121
|
+
const handled = Either.getRight(invokeHandler(registration.handler, {
|
|
122
|
+
eventId: registration.eventId,
|
|
123
|
+
context: sampleContext,
|
|
124
|
+
actor: pass.traversal.actor
|
|
125
|
+
}));
|
|
126
|
+
if (handled !== void 0) probes.push({
|
|
127
|
+
ctxName,
|
|
128
|
+
handled
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return probes;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* One edge per declared target, a `stay` edge when `to` is empty, or one
|
|
135
|
+
* undetermined edge when the actor carries no declaration. Probing only labels
|
|
136
|
+
* edges: which sample contexts reach each target, and what it emits.
|
|
137
|
+
*/
|
|
138
|
+
function addRegistration(registration, pass) {
|
|
139
|
+
const probes = probeAll(registration, pass);
|
|
140
|
+
const declared = registration.declared ?? [];
|
|
141
|
+
const targets = declared.length > 0 ? declared : [void 0];
|
|
142
|
+
const undetermined = registration.declared === void 0;
|
|
143
|
+
for (const targetName of targets) {
|
|
144
|
+
const matching = undetermined ? probes : probes.filter((probe) => probe.handled.targetName === targetName);
|
|
145
|
+
const emitNames = [...new Set(matching.flatMap((probe) => probe.handled.emitNames))].sort();
|
|
146
|
+
const targetId = targetName ? nodeId({
|
|
147
|
+
prefix: pass.traversal.pathPrefix,
|
|
148
|
+
name: targetName
|
|
149
|
+
}) : pass.sourceId;
|
|
150
|
+
const suffix = targetName ? targetId : undetermined ? "undetermined" : "stay";
|
|
151
|
+
const edgeId = `${pass.sourceId}-${registration.eventId}-${suffix}`;
|
|
152
|
+
pass.edgeMap.set(edgeId, {
|
|
153
|
+
id: edgeId,
|
|
154
|
+
source: pass.sourceId,
|
|
155
|
+
target: targetId,
|
|
156
|
+
label: registration.eventId,
|
|
157
|
+
isActive: pass.isActive,
|
|
158
|
+
isInternal: pass.traversal.internalIds?.has(registration.eventId),
|
|
159
|
+
isUndetermined: undetermined,
|
|
160
|
+
isStay: !undetermined && targetName === void 0,
|
|
161
|
+
isDeclared: !undetermined,
|
|
162
|
+
contexts: matching.map((probe) => probe.ctxName),
|
|
163
|
+
...emitNames.length > 0 && { payload: { action: `emit(${emitNames.join(", ")})` } }
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function registrations(handlers, declared) {
|
|
168
|
+
return Object.entries(handlers ?? {}).map(([eventId, handler]) => ({
|
|
169
|
+
eventId,
|
|
170
|
+
handler,
|
|
171
|
+
declared: declared?.[eventId]
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
function stateEdges(edges) {
|
|
175
|
+
const sourceId = nodeId({
|
|
176
|
+
prefix: edges.traversal.pathPrefix,
|
|
177
|
+
name: edges.state.name
|
|
178
|
+
});
|
|
179
|
+
const pass = {
|
|
180
|
+
edgeMap: /* @__PURE__ */ new Map(),
|
|
181
|
+
sourceId,
|
|
182
|
+
isActive: edges.traversal.activeSet.has(sourceId),
|
|
183
|
+
traversal: edges.traversal
|
|
184
|
+
};
|
|
185
|
+
const own = registrations(edges.transitions[edges.state.name], edges.targets[edges.state.name]);
|
|
186
|
+
for (const registration of own) addRegistration(registration, pass);
|
|
187
|
+
return [...pass.edgeMap.values()];
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* `onAny` edges leave one `*` source, once per target: "from any non-final
|
|
191
|
+
* state". Expanding them per state would claim source/target pairs the code
|
|
192
|
+
* never states. A state's own handler for the same event runs first; when it
|
|
193
|
+
* stays, `onAny` may still transition, so both edges are kept.
|
|
194
|
+
*/
|
|
195
|
+
function anyEdges(edges) {
|
|
196
|
+
const prefix = edges.traversal.pathPrefix;
|
|
197
|
+
const pass = {
|
|
198
|
+
edgeMap: /* @__PURE__ */ new Map(),
|
|
199
|
+
sourceId: nodeId({
|
|
200
|
+
prefix,
|
|
201
|
+
name: "*"
|
|
202
|
+
}),
|
|
203
|
+
isActive: edges.states.some((state) => !state.isFinal && edges.traversal.activeSet.has(nodeId({
|
|
204
|
+
prefix,
|
|
205
|
+
name: state.name
|
|
206
|
+
}))),
|
|
207
|
+
traversal: edges.traversal
|
|
208
|
+
};
|
|
209
|
+
for (const registration of registrations(edges.transitions["Any"], edges.targets["Any"])) addRegistration(registration, pass);
|
|
210
|
+
return [...pass.edgeMap.values()];
|
|
211
|
+
}
|
|
212
|
+
function buildEdgesFromTransitions(edges) {
|
|
213
|
+
const scope = {
|
|
214
|
+
transitions: edges.transitions,
|
|
215
|
+
targets: edges.targets ?? {},
|
|
216
|
+
traversal: edges.traversal
|
|
217
|
+
};
|
|
218
|
+
return [...edges.states.flatMap((state) => stateEdges({
|
|
219
|
+
...scope,
|
|
220
|
+
state
|
|
221
|
+
})), ...anyEdges({
|
|
222
|
+
...scope,
|
|
223
|
+
states: edges.states
|
|
224
|
+
})];
|
|
225
|
+
}
|
|
226
|
+
// src/parse-graph.ts
|
|
65
227
|
function parseStates(options) {
|
|
66
228
|
const states = options?.states;
|
|
67
229
|
if (!Array.isArray(states)) return [];
|
|
@@ -79,6 +241,19 @@ function parseTransitionMap(source) {
|
|
|
79
241
|
}
|
|
80
242
|
return dispatchMap;
|
|
81
243
|
}
|
|
244
|
+
function parseTargetMap(source) {
|
|
245
|
+
const targetMap = {};
|
|
246
|
+
if (typeof source !== "object" || source === null) return targetMap;
|
|
247
|
+
for (const [stateName, eventMap] of Object.entries(source)) {
|
|
248
|
+
if (typeof eventMap !== "object" || eventMap === null) continue;
|
|
249
|
+
for (const [eventType, names] of Object.entries(eventMap)) {
|
|
250
|
+
if (!Array.isArray(names)) continue;
|
|
251
|
+
const declared = names.filter((name) => typeof name === "string");
|
|
252
|
+
(targetMap[stateName] ??= {})[eventType] = declared;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return targetMap;
|
|
256
|
+
}
|
|
82
257
|
function parseInitialName(options) {
|
|
83
258
|
const initial = options?.initial;
|
|
84
259
|
if (initial === void 0 || initial === null) return void 0;
|
|
@@ -101,26 +276,7 @@ function parseInternalEventIds(options) {
|
|
|
101
276
|
}
|
|
102
277
|
return eventIds;
|
|
103
278
|
}
|
|
104
|
-
|
|
105
|
-
//#region src/graph.ts
|
|
106
|
-
const INITIAL_NODE_ID = "__initial__";
|
|
107
|
-
function collectActiveStates(snapshot, options) {
|
|
108
|
-
if (!snapshot.path) return;
|
|
109
|
-
const currentName = snapshot.path[snapshot.path.length - 1];
|
|
110
|
-
if (!currentName) return;
|
|
111
|
-
const fullId = nodeId({
|
|
112
|
-
prefix: options.prefix,
|
|
113
|
-
name: currentName
|
|
114
|
-
});
|
|
115
|
-
options.activeSet.add(fullId);
|
|
116
|
-
if (snapshot.regions && !snapshot.done && !snapshot.error) for (const [regionName, regionSnapshot] of Object.entries(snapshot.regions)) collectActiveStates(regionSnapshot, {
|
|
117
|
-
prefix: regionName,
|
|
118
|
-
activeSet: options.activeSet
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
function nodeId(identity) {
|
|
122
|
-
return identity.prefix ? `${identity.prefix}.${identity.name}` : identity.name;
|
|
123
|
-
}
|
|
279
|
+
// src/graph.ts
|
|
124
280
|
function buildNodesFromStates(states, options) {
|
|
125
281
|
return states.map((stateEntry) => {
|
|
126
282
|
const graphId = nodeId({
|
|
@@ -135,144 +291,32 @@ function buildNodesFromStates(states, options) {
|
|
|
135
291
|
};
|
|
136
292
|
});
|
|
137
293
|
}
|
|
138
|
-
function invokeHandler(handler, options) {
|
|
139
|
-
return Either.from(() => {
|
|
140
|
-
const result = handler({
|
|
141
|
-
type: options.eventId,
|
|
142
|
-
payload: {}
|
|
143
|
-
}, {
|
|
144
|
-
context: Context({
|
|
145
|
-
get: () => options.context,
|
|
146
|
-
set: () => {}
|
|
147
|
-
}),
|
|
148
|
-
actor: options.actor
|
|
149
|
-
});
|
|
150
|
-
return isHandlerResult(result) ? {
|
|
151
|
-
targetName: result.state?.name,
|
|
152
|
-
emitNames: result.emit?.map((emitted) => emitted.type).filter((eventType) => Boolean(eventType)) ?? []
|
|
153
|
-
} : {
|
|
154
|
-
targetName: void 0,
|
|
155
|
-
emitNames: []
|
|
156
|
-
};
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
function upsertEdge(sink) {
|
|
160
|
-
const undetermined = !sink.handled.targetName;
|
|
161
|
-
const targetId = sink.handled.targetName ? nodeId({
|
|
162
|
-
prefix: sink.traversal.pathPrefix,
|
|
163
|
-
name: sink.handled.targetName
|
|
164
|
-
}) : `${sink.sourceId}-undetermined-${sink.eventId}`;
|
|
165
|
-
const edgeId = `${sink.sourceId}-${sink.eventId}-${targetId}`;
|
|
166
|
-
const existing = sink.edgeMap.get(edgeId);
|
|
167
|
-
if (existing) existing.contexts.push(sink.ctxName);
|
|
168
|
-
else sink.edgeMap.set(edgeId, {
|
|
169
|
-
id: edgeId,
|
|
170
|
-
source: sink.sourceId,
|
|
171
|
-
target: targetId,
|
|
172
|
-
label: sink.eventId,
|
|
173
|
-
isActive: sink.traversal.activeSet.has(sink.sourceId),
|
|
174
|
-
isInternal: sink.traversal.internalIds?.has(sink.eventId),
|
|
175
|
-
isUndetermined: undetermined,
|
|
176
|
-
contexts: [sink.ctxName],
|
|
177
|
-
...sink.handled.emitNames.length > 0 && { payload: { action: `emit(${sink.handled.emitNames.join(", ")})` } }
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
function processStateTransitions(options) {
|
|
181
|
-
const stateTransitionedEvents = /* @__PURE__ */ new Set();
|
|
182
|
-
for (const [eventId, handler] of Object.entries(options.stateTransitions)) for (const ctxName of options.pass.traversal.contextNames) {
|
|
183
|
-
const sampleContext = options.pass.traversal.contexts[ctxName];
|
|
184
|
-
if (sampleContext === void 0) continue;
|
|
185
|
-
const handled = Either.getOrElse(invokeHandler(handler, {
|
|
186
|
-
eventId,
|
|
187
|
-
context: sampleContext,
|
|
188
|
-
actor: options.pass.traversal.actor
|
|
189
|
-
}), { onLeft: () => ({
|
|
190
|
-
targetName: void 0,
|
|
191
|
-
emitNames: []
|
|
192
|
-
}) });
|
|
193
|
-
if (handled.targetName) stateTransitionedEvents.add(eventId);
|
|
194
|
-
upsertEdge({
|
|
195
|
-
edgeMap: options.pass.edgeMap,
|
|
196
|
-
sourceId: options.pass.sourceId,
|
|
197
|
-
ctxName,
|
|
198
|
-
traversal: options.pass.traversal,
|
|
199
|
-
eventId,
|
|
200
|
-
handled
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
return stateTransitionedEvents;
|
|
204
|
-
}
|
|
205
|
-
function mergeAnyTransitions(options) {
|
|
206
|
-
for (const [eventId, handler] of Object.entries(options.anyTransitions)) {
|
|
207
|
-
if (options.stateTransitionedEvents.has(eventId)) continue;
|
|
208
|
-
for (const ctxName of options.pass.traversal.contextNames) {
|
|
209
|
-
const sampleContext = options.pass.traversal.contexts[ctxName];
|
|
210
|
-
if (sampleContext === void 0) continue;
|
|
211
|
-
const handled = Either.getOrElse(invokeHandler(handler, {
|
|
212
|
-
eventId,
|
|
213
|
-
context: sampleContext,
|
|
214
|
-
actor: options.pass.traversal.actor
|
|
215
|
-
}), { onLeft: () => ({
|
|
216
|
-
targetName: void 0,
|
|
217
|
-
emitNames: []
|
|
218
|
-
}) });
|
|
219
|
-
upsertEdge({
|
|
220
|
-
edgeMap: options.pass.edgeMap,
|
|
221
|
-
sourceId: options.pass.sourceId,
|
|
222
|
-
ctxName,
|
|
223
|
-
traversal: options.pass.traversal,
|
|
224
|
-
eventId,
|
|
225
|
-
handled
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
function collectTransitionsForState(edges) {
|
|
231
|
-
const edgeMap = /* @__PURE__ */ new Map();
|
|
232
|
-
const pass = {
|
|
233
|
-
edgeMap,
|
|
234
|
-
sourceId: edges.sourceId,
|
|
235
|
-
traversal: edges.traversal
|
|
236
|
-
};
|
|
237
|
-
const stateTransitionedEvents = edges.stateTransitions ? processStateTransitions({
|
|
238
|
-
stateTransitions: edges.stateTransitions,
|
|
239
|
-
pass
|
|
240
|
-
}) : /* @__PURE__ */ new Set();
|
|
241
|
-
if (edges.anyTransitions) mergeAnyTransitions({
|
|
242
|
-
anyTransitions: edges.anyTransitions,
|
|
243
|
-
stateTransitionedEvents,
|
|
244
|
-
pass
|
|
245
|
-
});
|
|
246
|
-
return edgeMap;
|
|
247
|
-
}
|
|
248
|
-
function buildEdgesFromTransitions(edges) {
|
|
249
|
-
const allEdges = [];
|
|
250
|
-
const anyTransitions = edges.transitions["Any"];
|
|
251
|
-
for (const stateEntry of edges.states) {
|
|
252
|
-
const sourceId = nodeId({
|
|
253
|
-
prefix: edges.traversal.pathPrefix,
|
|
254
|
-
name: stateEntry.name
|
|
255
|
-
});
|
|
256
|
-
const edgeMap = collectTransitionsForState({
|
|
257
|
-
stateTransitions: edges.transitions[stateEntry.name],
|
|
258
|
-
anyTransitions,
|
|
259
|
-
sourceId,
|
|
260
|
-
traversal: edges.traversal
|
|
261
|
-
});
|
|
262
|
-
allEdges.push(...edgeMap.values());
|
|
263
|
-
}
|
|
264
|
-
return allEdges;
|
|
265
|
-
}
|
|
266
294
|
function addNodesForActor(actor, layout) {
|
|
267
|
-
|
|
295
|
+
const nodes = buildNodesFromStates(parseStates(actor.options), {
|
|
268
296
|
activeSet: layout.activeSet,
|
|
269
297
|
pathPrefix: layout.pathPrefix
|
|
270
298
|
});
|
|
299
|
+
const anyTransitions = parseTransitionMap(actor.options?.transitions)["Any"];
|
|
300
|
+
if (anyTransitions !== void 0 && Object.keys(anyTransitions).length > 0) {
|
|
301
|
+
const id = nodeId({
|
|
302
|
+
prefix: layout.pathPrefix,
|
|
303
|
+
name: "*"
|
|
304
|
+
});
|
|
305
|
+
nodes.push({
|
|
306
|
+
id,
|
|
307
|
+
label: "any state",
|
|
308
|
+
isActive: false,
|
|
309
|
+
isFinal: false,
|
|
310
|
+
isAny: true
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
return nodes;
|
|
271
314
|
}
|
|
272
315
|
function addEdgesForActor(actor, layout) {
|
|
273
316
|
return buildEdgesFromTransitions({
|
|
274
317
|
states: parseStates(actor.options),
|
|
275
318
|
transitions: parseTransitionMap(actor.options?.transitions),
|
|
319
|
+
targets: parseTargetMap(actor.options?.targets),
|
|
276
320
|
traversal: layout.traversal
|
|
277
321
|
});
|
|
278
322
|
}
|
|
@@ -295,6 +339,16 @@ function recurseRegions(actor, layout) {
|
|
|
295
339
|
edges: childEdges
|
|
296
340
|
};
|
|
297
341
|
}
|
|
342
|
+
function readEffect(entry) {
|
|
343
|
+
if (typeof entry !== "object" || entry === null) return [];
|
|
344
|
+
const name = Reflect.get(entry, "name");
|
|
345
|
+
if (typeof name !== "string") return [];
|
|
346
|
+
const sends = Reflect.get(entry, "sends");
|
|
347
|
+
return [{
|
|
348
|
+
name,
|
|
349
|
+
sends: Array.isArray(sends) ? sends.filter((type) => typeof type === "string") : null
|
|
350
|
+
}];
|
|
351
|
+
}
|
|
298
352
|
function addEffectSelfLoops(actor, layout) {
|
|
299
353
|
const selfLoops = [];
|
|
300
354
|
const effects = actor.options?.effects;
|
|
@@ -311,7 +365,8 @@ function addEffectSelfLoops(actor, layout) {
|
|
|
311
365
|
label: `effect:${stateName}`,
|
|
312
366
|
isActive: layout.activeSet.has(loopId),
|
|
313
367
|
isInternal: true,
|
|
314
|
-
contexts: []
|
|
368
|
+
contexts: [],
|
|
369
|
+
effects: effectFns.flatMap((entry) => readEffect(entry))
|
|
315
370
|
});
|
|
316
371
|
}
|
|
317
372
|
}
|
|
@@ -363,14 +418,6 @@ function addInitialNode(actor, layout) {
|
|
|
363
418
|
contexts: []
|
|
364
419
|
});
|
|
365
420
|
}
|
|
366
|
-
function collectActorsFromSnapshot(snapshot) {
|
|
367
|
-
const activeSet = /* @__PURE__ */ new Set();
|
|
368
|
-
collectActiveStates(snapshot, {
|
|
369
|
-
prefix: "",
|
|
370
|
-
activeSet
|
|
371
|
-
});
|
|
372
|
-
return activeSet;
|
|
373
|
-
}
|
|
374
421
|
function collectNamedContexts(named) {
|
|
375
422
|
return named?.sampleContexts ?? (named?.sampleContext ? { default: named.sampleContext } : void 0);
|
|
376
423
|
}
|
|
@@ -403,8 +450,7 @@ function buildGraph(actor, options) {
|
|
|
403
450
|
edges: []
|
|
404
451
|
}) });
|
|
405
452
|
}
|
|
406
|
-
|
|
407
|
-
//#region src/instrument.ts
|
|
453
|
+
// src/instrument.ts
|
|
408
454
|
function wrapWithProxy(actor, wrapped) {
|
|
409
455
|
function forwardOn(event, options) {
|
|
410
456
|
return actor.on(event, options);
|
|
@@ -520,8 +566,7 @@ function attach(childActor, branch) {
|
|
|
520
566
|
})
|
|
521
567
|
});
|
|
522
568
|
}
|
|
523
|
-
|
|
524
|
-
//#region src/algorithms.ts
|
|
569
|
+
// src/algorithms.ts
|
|
525
570
|
function buildAdjacency(graph) {
|
|
526
571
|
const adjacency = {};
|
|
527
572
|
for (const node of graph.nodes) adjacency[node.id] = [];
|
|
@@ -652,5 +697,4 @@ function shortestPath(graph, endpoints) {
|
|
|
652
697
|
});
|
|
653
698
|
}
|
|
654
699
|
}
|
|
655
|
-
|
|
656
|
-
export { INITIAL_NODE_ID, allPaths, buildGraph, collectActiveStates, createHistory, findCycles, instrument, parseContextRecord, parseInitialName, parseInternalEventIds, parseStates, parseTransitionMap, reachable, shortestPath, unreachableNodes };
|
|
700
|
+
export { ANY_NODE_NAME, INITIAL_NODE_ID, allPaths, buildGraph, collectActiveStates, createHistory, findCycles, instrument, isAnyNode, parseContextRecord, parseInitialName, parseInternalEventIds, parseStates, parseTransitionMap, reachable, shortestPath, unreachableNodes };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mantaq/traversal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Graph extraction, traversal algorithms, and runtime tracking for mantaq actors.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -20,16 +20,16 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@mantaq/core": "0.
|
|
23
|
+
"@mantaq/core": "0.5.0",
|
|
24
24
|
"@mantaq/utils": "0.0.4"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^26.2.0",
|
|
28
28
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
29
|
-
"@vitest/coverage-v8": "
|
|
29
|
+
"@vitest/coverage-v8": "5.0.5",
|
|
30
30
|
"typescript": "6.0.3",
|
|
31
|
-
"vite": "npm:@voidzero-dev/vite-plus-core@
|
|
32
|
-
"vite-plus": "
|
|
31
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.4.0",
|
|
32
|
+
"vite-plus": "1.4.0"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "vp pack",
|