@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 CHANGED
@@ -1,11 +1,8 @@
1
1
  import { AnyActor, Context, ErrorInfo, InternalEvent, Snapshot, TransitionInfo } from "@mantaq/core";
2
- //#region src/types.d.ts
3
- /** Synthetic event object for graph introspection — matches minimal InternalEvent shape */
4
- interface SyntheticEvent {
5
- type: string;
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
- //#endregion
90
- //#region src/graph.d.ts
91
- declare const INITIAL_NODE_ID = "__initial__";
92
- declare function collectActiveStates(snapshot: Snapshot, options: {
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
- //#endregion
102
- //#region src/parse-graph.d.ts
103
- declare function parseStates(options: unknown): ReadonlyArray<StateDef>;
104
- declare function parseTransitionMap(source: unknown): TransitionDispatchMap;
105
- declare function parseInitialName(options: unknown): string | undefined;
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
- //#endregion
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
- //#endregion
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
- //#region src/history.ts
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
- //#endregion
57
- //#region src/is-handler-result.ts
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
- //#endregion
64
- //#region src/parse-graph.ts
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
- //#endregion
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
- return buildNodesFromStates(parseStates(actor.options), {
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
- //#endregion
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
- //#endregion
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
- //#endregion
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.2.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.4.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": "4.1.10",
29
+ "@vitest/coverage-v8": "5.0.5",
30
30
  "typescript": "6.0.3",
31
- "vite": "npm:@voidzero-dev/vite-plus-core@0.2.8",
32
- "vite-plus": "0.2.8"
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",