@telorun/debug-ui 0.3.0 → 0.5.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/app-dist/assets/{index-CWG3ZSzH.css → index-C9ZwjlYK.css} +1 -1
- package/app-dist/assets/index-D2M9P905.js +126 -0
- package/app-dist/index.html +2 -2
- package/app-single/index.html +29 -29
- package/package.json +2 -2
- package/src/components/EventGraph.tsx +223 -17
- package/src/components/styles.css +165 -1
- package/src/filter.ts +6 -2
- package/src/graph.test.ts +270 -39
- package/src/graph.ts +302 -79
- package/app-dist/assets/index-DS4SK8dD.js +0 -126
package/src/graph.test.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
collapseTopology,
|
|
4
|
+
deriveGraph,
|
|
5
|
+
deriveInvocations,
|
|
6
|
+
subtreeGraph,
|
|
7
|
+
traceSubgraph,
|
|
8
|
+
} from "./graph.js";
|
|
3
9
|
import type { DebugFrame } from "./wire.js";
|
|
4
10
|
|
|
5
11
|
let seq = 0;
|
|
@@ -12,6 +18,48 @@ const ev = (event: string, payload?: unknown): DebugFrame => ({
|
|
|
12
18
|
const created = (kind: string, name: string, deps: { kind: string; name: string }[] = []) =>
|
|
13
19
|
ev(`${kind}.${name}.Created`, { resource: { kind, name }, dependencies: deps });
|
|
14
20
|
|
|
21
|
+
type Owner = { kind: string; name: string; id: string };
|
|
22
|
+
const idOf = (kind: string, name: string, ownerId?: string) =>
|
|
23
|
+
ownerId ? `${ownerId}/${kind}.${name}` : `${kind}.${name}`;
|
|
24
|
+
|
|
25
|
+
/** A `Created` carrying the hierarchical `id` (and `owner`, for a spawned child)
|
|
26
|
+
* the current kernel emits — the shape that disambiguates two instances of the
|
|
27
|
+
* same templated kind. */
|
|
28
|
+
const createdR = (
|
|
29
|
+
kind: string,
|
|
30
|
+
name: string,
|
|
31
|
+
opts: { owner?: Owner; deps?: { kind: string; name: string; id?: string }[] } = {},
|
|
32
|
+
) =>
|
|
33
|
+
ev(`${kind}.${name}.Created`, {
|
|
34
|
+
resource: { kind, name, id: idOf(kind, name, opts.owner?.id) },
|
|
35
|
+
...(opts.owner ? { owner: opts.owner } : {}),
|
|
36
|
+
dependencies: opts.deps ?? [],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/** A terminal dispatch event whose `ref` carries the hierarchical `id`. */
|
|
40
|
+
const dispId = (
|
|
41
|
+
name: string,
|
|
42
|
+
kind: string,
|
|
43
|
+
id: string,
|
|
44
|
+
outcome: Outcome,
|
|
45
|
+
suffix: string,
|
|
46
|
+
detail: Record<string, unknown> = {},
|
|
47
|
+
): DebugFrame =>
|
|
48
|
+
ev(`${name}.${suffix}`, { capability: "invoke", phase: "end", outcome, ref: { kind, name, id }, ...detail });
|
|
49
|
+
|
|
50
|
+
type Outcome = "ok" | "failed" | "rejected" | "cancelled";
|
|
51
|
+
|
|
52
|
+
/** A terminal dispatch (trace) event: the name drops the kind, the payload carries
|
|
53
|
+
* the structured trace contract `{ capability, phase, outcome, ref, … }`. */
|
|
54
|
+
const disp = (
|
|
55
|
+
name: string,
|
|
56
|
+
kind: string,
|
|
57
|
+
outcome: Outcome,
|
|
58
|
+
suffix: string,
|
|
59
|
+
detail: Record<string, unknown> = {},
|
|
60
|
+
): DebugFrame =>
|
|
61
|
+
ev(`${name}.${suffix}`, { capability: "invoke", phase: "end", outcome, ref: { kind, name }, ...detail });
|
|
62
|
+
|
|
15
63
|
describe("deriveGraph", () => {
|
|
16
64
|
it("creates a gray (created) node on Created", () => {
|
|
17
65
|
const { nodes } = deriveGraph([created("Http.Server", "api")]);
|
|
@@ -52,7 +100,7 @@ describe("deriveGraph", () => {
|
|
|
52
100
|
it("records an ok invocation with inputs and outputs", () => {
|
|
53
101
|
const { nodes } = deriveGraph([
|
|
54
102
|
created("Js.Script", "echo"),
|
|
55
|
-
|
|
103
|
+
disp("echo", "Js.Script", "ok", "Invoked", { inputs: { msg: "hi" }, outputs: { echoed: "hi" } }),
|
|
56
104
|
]);
|
|
57
105
|
expect(nodes[0].invokeCount).toBe(1);
|
|
58
106
|
expect(nodes[0].lastInvoke).toMatchObject({
|
|
@@ -63,18 +111,27 @@ describe("deriveGraph", () => {
|
|
|
63
111
|
});
|
|
64
112
|
});
|
|
65
113
|
|
|
66
|
-
it("
|
|
114
|
+
it("skips a phase:start event (no outcome) — only terminal events count", () => {
|
|
115
|
+
const { nodes } = deriveGraph([
|
|
116
|
+
created("Js.Script", "echo"),
|
|
117
|
+
ev("echo.Invoking", { capability: "invoke", phase: "start", ref: { kind: "Js.Script", name: "echo" }, inputs: {} }),
|
|
118
|
+
]);
|
|
119
|
+
expect(nodes[0].invokeCount).toBe(0);
|
|
120
|
+
expect(nodes[0].lastInvoke).toBeUndefined();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("records failure, rejection and cancellation outcomes from the payload", () => {
|
|
67
124
|
const base = created("Js.Script", "x");
|
|
68
125
|
expect(
|
|
69
|
-
deriveGraph([base,
|
|
126
|
+
deriveGraph([base, disp("x", "Js.Script", "failed", "InvokeFailed", { inputs: {}, name: "Error", message: "boom" })])
|
|
70
127
|
.nodes[0].lastInvoke?.outcome,
|
|
71
128
|
).toBe("failed");
|
|
72
129
|
expect(
|
|
73
|
-
deriveGraph([base,
|
|
130
|
+
deriveGraph([base, disp("x", "Js.Script", "rejected", "InvokeRejected", { inputs: {}, code: "E", message: "no" })])
|
|
74
131
|
.nodes[0].lastInvoke?.outcome,
|
|
75
132
|
).toBe("rejected");
|
|
76
133
|
expect(
|
|
77
|
-
deriveGraph([base,
|
|
134
|
+
deriveGraph([base, disp("x", "Js.Script", "cancelled", "InvokeCancelled", { inputs: {}, reason: "stop" })])
|
|
78
135
|
.nodes[0].lastInvoke?.outcome,
|
|
79
136
|
).toBe("cancelled");
|
|
80
137
|
});
|
|
@@ -82,29 +139,27 @@ describe("deriveGraph", () => {
|
|
|
82
139
|
it("treats InvokeRejected.Undeclared as a rejection on the right node", () => {
|
|
83
140
|
const { nodes } = deriveGraph([
|
|
84
141
|
created("Js.Script", "x"),
|
|
85
|
-
|
|
142
|
+
disp("x", "Js.Script", "rejected", "InvokeRejected.Undeclared", { inputs: {}, code: "E", message: "no" }),
|
|
86
143
|
]);
|
|
87
|
-
expect(nodes[0].lastInvoke).toMatchObject({ outcome: "rejected", suffix: "
|
|
144
|
+
expect(nodes[0].lastInvoke).toMatchObject({ outcome: "rejected", suffix: "Undeclared" });
|
|
88
145
|
});
|
|
89
146
|
|
|
90
147
|
it("counts repeated invocations and advances the activity index", () => {
|
|
91
148
|
const { nodes } = deriveGraph([
|
|
92
149
|
created("Js.Script", "x"),
|
|
93
|
-
|
|
94
|
-
|
|
150
|
+
disp("x", "Js.Script", "ok", "Invoked", { inputs: {}, outputs: 1 }),
|
|
151
|
+
disp("x", "Js.Script", "ok", "Invoked", { inputs: {}, outputs: 2 }),
|
|
95
152
|
]);
|
|
96
153
|
expect(nodes[0].invokeCount).toBe(2);
|
|
97
154
|
expect(nodes[0].lastInvoke?.outputs).toBe(2);
|
|
98
155
|
expect(nodes[0].lastActivitySeq).toBeGreaterThan(0);
|
|
99
156
|
});
|
|
100
157
|
|
|
101
|
-
it("attributes an invocation to the
|
|
102
|
-
// `Http.Server.api` and `Http.Server.api.sub` share a prefix; the event must
|
|
103
|
-
// land on the deeper node, not the shorter one.
|
|
158
|
+
it("attributes an invocation to the resource named in the payload ref", () => {
|
|
104
159
|
const { nodes } = deriveGraph([
|
|
105
160
|
created("Http.Server", "api"),
|
|
106
161
|
created("Http.Server.api", "sub"),
|
|
107
|
-
|
|
162
|
+
disp("sub", "Http.Server.api", "ok", "Invoked", { inputs: {}, outputs: "ok" }),
|
|
108
163
|
]);
|
|
109
164
|
expect(nodes.find((n) => n.name === "sub")?.invokeCount).toBe(1);
|
|
110
165
|
expect(nodes.find((n) => n.name === "api")?.invokeCount).toBe(0);
|
|
@@ -121,46 +176,222 @@ describe("deriveGraph", () => {
|
|
|
121
176
|
});
|
|
122
177
|
});
|
|
123
178
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
179
|
+
describe("owner grouping", () => {
|
|
180
|
+
const todos: Owner = { kind: "Crud.Resource", name: "todos", id: "Crud.Resource.todos" };
|
|
181
|
+
const users: Owner = { kind: "Crud.Resource", name: "users", id: "Crud.Resource.users" };
|
|
182
|
+
|
|
183
|
+
it("keeps same-named children of two templated instances from colliding", () => {
|
|
184
|
+
const { nodes } = deriveGraph([
|
|
185
|
+
createdR("Crud.Resource", "todos"),
|
|
186
|
+
createdR("Crud.Resource", "users"),
|
|
187
|
+
createdR("SqlRepo.Read", "reader", { owner: todos }),
|
|
188
|
+
createdR("SqlRepo.Read", "reader", { owner: users }),
|
|
189
|
+
]);
|
|
190
|
+
const readers = nodes.filter((n) => n.name === "reader");
|
|
191
|
+
expect(readers.map((n) => n.id).sort()).toEqual([
|
|
192
|
+
"Crud.Resource.todos/SqlRepo.Read.reader",
|
|
193
|
+
"Crud.Resource.users/SqlRepo.Read.reader",
|
|
194
|
+
]);
|
|
195
|
+
expect(readers.map((n) => n.ownerId).sort()).toEqual([
|
|
196
|
+
"Crud.Resource.todos",
|
|
197
|
+
"Crud.Resource.users",
|
|
198
|
+
]);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("attributes an invocation to the right instance via ref.id", () => {
|
|
202
|
+
const { nodes } = deriveGraph([
|
|
203
|
+
createdR("Crud.Resource", "todos"),
|
|
204
|
+
createdR("Crud.Resource", "users"),
|
|
205
|
+
createdR("SqlRepo.Read", "reader", { owner: todos }),
|
|
206
|
+
createdR("SqlRepo.Read", "reader", { owner: users }),
|
|
207
|
+
dispId("reader", "SqlRepo.Read", "Crud.Resource.todos/SqlRepo.Read.reader", "ok", "Invoked", {
|
|
208
|
+
inputs: {},
|
|
209
|
+
outputs: 1,
|
|
210
|
+
}),
|
|
211
|
+
]);
|
|
212
|
+
expect(nodes.find((n) => n.id === "Crud.Resource.todos/SqlRepo.Read.reader")?.invokeCount).toBe(1);
|
|
213
|
+
expect(nodes.find((n) => n.id === "Crud.Resource.users/SqlRepo.Read.reader")?.invokeCount).toBe(0);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("collapseTopology hides children until their owner is expanded", () => {
|
|
217
|
+
const graph = deriveGraph([
|
|
218
|
+
createdR("Crud.Resource", "todos"),
|
|
219
|
+
createdR("SqlRepo.Read", "reader", { owner: todos }),
|
|
220
|
+
createdR("SqlRepo.Create", "creator", { owner: todos }),
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
const collapsedDefault = collapseTopology(graph, new Set());
|
|
224
|
+
expect(collapsedDefault.nodes.map((n) => n.id)).toEqual(["Crud.Resource.todos"]);
|
|
225
|
+
expect(collapsedDefault.nodes[0]).toMatchObject({ childCount: 2, expanded: false });
|
|
226
|
+
|
|
227
|
+
const expanded = collapseTopology(graph, new Set(["Crud.Resource.todos"]));
|
|
228
|
+
expect(expanded.nodes.map((n) => n.id).sort()).toEqual([
|
|
229
|
+
"Crud.Resource.todos",
|
|
230
|
+
"Crud.Resource.todos/SqlRepo.Create.creator",
|
|
231
|
+
"Crud.Resource.todos/SqlRepo.Read.reader",
|
|
232
|
+
]);
|
|
233
|
+
expect(expanded.nodes.find((n) => n.id === "Crud.Resource.todos")).toMatchObject({ expanded: true });
|
|
234
|
+
|
|
235
|
+
// Revealed children stay attached to their owner via ownership edges.
|
|
236
|
+
const owns = expanded.edges.filter((e) => e.ownership);
|
|
237
|
+
expect(owns.map((e) => e.target).sort()).toEqual([
|
|
238
|
+
"Crud.Resource.todos/SqlRepo.Create.creator",
|
|
239
|
+
"Crud.Resource.todos/SqlRepo.Read.reader",
|
|
240
|
+
]);
|
|
241
|
+
expect(owns.every((e) => e.source === "Crud.Resource.todos")).toBe(true);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("adds no ownership edges while the owner is collapsed", () => {
|
|
245
|
+
const graph = deriveGraph([
|
|
246
|
+
createdR("Crud.Resource", "todos"),
|
|
247
|
+
createdR("SqlRepo.Read", "reader", { owner: todos }),
|
|
248
|
+
]);
|
|
249
|
+
expect(collapseTopology(graph, new Set()).edges.some((e) => e.ownership)).toBe(false);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("collapseTopology folds a hidden child's edge onto its owner", () => {
|
|
253
|
+
const graph = deriveGraph([
|
|
254
|
+
createdR("Sql.Connection", "db"),
|
|
255
|
+
createdR("Crud.Resource", "todos"),
|
|
256
|
+
createdR("SqlRepo.Read", "reader", {
|
|
257
|
+
owner: todos,
|
|
258
|
+
deps: [{ kind: "Sql.Connection", name: "db", id: "Sql.Connection.db" }],
|
|
259
|
+
}),
|
|
260
|
+
]);
|
|
261
|
+
const collapsed = collapseTopology(graph, new Set());
|
|
262
|
+
expect(collapsed.edges).toContainEqual({
|
|
263
|
+
id: "Crud.Resource.todos->Sql.Connection.db",
|
|
264
|
+
source: "Crud.Resource.todos",
|
|
265
|
+
target: "Sql.Connection.db",
|
|
266
|
+
});
|
|
267
|
+
// The child node itself is hidden while collapsed.
|
|
268
|
+
expect(collapsed.nodes.some((n) => n.id.endsWith("SqlRepo.Read.reader"))).toBe(false);
|
|
269
|
+
});
|
|
134
270
|
});
|
|
135
271
|
|
|
272
|
+
describe("subtreeGraph (drill-down pane)", () => {
|
|
273
|
+
const todos: Owner = { kind: "Crud.Resource", name: "todos", id: "Crud.Resource.todos" };
|
|
274
|
+
|
|
275
|
+
it("links the parent only to children not already reached by a sibling", () => {
|
|
276
|
+
const topology = deriveGraph([
|
|
277
|
+
createdR("Crud.Resource", "todos"),
|
|
278
|
+
createdR("Http.Api", "api", {
|
|
279
|
+
owner: todos,
|
|
280
|
+
deps: [{ kind: "SqlRepo.Read", name: "reader", id: "Crud.Resource.todos/SqlRepo.Read.reader" }],
|
|
281
|
+
}),
|
|
282
|
+
createdR("SqlRepo.Read", "reader", { owner: todos }),
|
|
283
|
+
]);
|
|
284
|
+
const sub = subtreeGraph(topology, "Crud.Resource.todos");
|
|
285
|
+
|
|
286
|
+
expect(sub.nodes.map((n) => n.id).sort()).toEqual([
|
|
287
|
+
"Crud.Resource.todos",
|
|
288
|
+
"Crud.Resource.todos/Http.Api.api",
|
|
289
|
+
"Crud.Resource.todos/SqlRepo.Read.reader",
|
|
290
|
+
]);
|
|
291
|
+
expect(sub.nodes.find((n) => n.id === "Crud.Resource.todos")?.expanded).toBe(true);
|
|
292
|
+
|
|
293
|
+
// The parent owns `api` directly; `reader` is reached through `api`, so the
|
|
294
|
+
// redundant parent→reader ownership edge is dropped.
|
|
295
|
+
const owns = sub.edges.filter((e) => e.ownership);
|
|
296
|
+
expect(owns.map((e) => e.target)).toEqual(["Crud.Resource.todos/Http.Api.api"]);
|
|
297
|
+
expect(owns.some((e) => e.target.endsWith("SqlRepo.Read.reader"))).toBe(false);
|
|
298
|
+
|
|
299
|
+
// The dependency edge among children (api → reader) is kept.
|
|
300
|
+
expect(
|
|
301
|
+
sub.edges.some(
|
|
302
|
+
(e) =>
|
|
303
|
+
!e.ownership &&
|
|
304
|
+
e.source === "Crud.Resource.todos/Http.Api.api" &&
|
|
305
|
+
e.target === "Crud.Resource.todos/SqlRepo.Read.reader",
|
|
306
|
+
),
|
|
307
|
+
).toBe(true);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("reports each child's own childCount and drills recursively", () => {
|
|
311
|
+
const reader: Owner = {
|
|
312
|
+
kind: "SqlRepo.Read",
|
|
313
|
+
name: "reader",
|
|
314
|
+
id: "Crud.Resource.todos/SqlRepo.Read.reader",
|
|
315
|
+
};
|
|
316
|
+
const topology = deriveGraph([
|
|
317
|
+
createdR("Crud.Resource", "todos"),
|
|
318
|
+
createdR("SqlRepo.Read", "reader", { owner: todos }),
|
|
319
|
+
createdR("Sql.Query", "reader-query", { owner: reader }),
|
|
320
|
+
]);
|
|
321
|
+
|
|
322
|
+
const lvl1 = subtreeGraph(topology, "Crud.Resource.todos");
|
|
323
|
+
expect(lvl1.nodes.find((n) => n.id === reader.id)?.childCount).toBe(1);
|
|
324
|
+
|
|
325
|
+
const lvl2 = subtreeGraph(topology, reader.id);
|
|
326
|
+
expect(lvl2.nodes.map((n) => n.id).sort()).toEqual([
|
|
327
|
+
"Crud.Resource.todos/SqlRepo.Read.reader",
|
|
328
|
+
"Crud.Resource.todos/SqlRepo.Read.reader/Sql.Query.reader-query",
|
|
329
|
+
]);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("drops dependency edges that leave the subtree", () => {
|
|
333
|
+
const topology = deriveGraph([
|
|
334
|
+
createdR("Sql.Connection", "db"),
|
|
335
|
+
createdR("Crud.Resource", "todos"),
|
|
336
|
+
createdR("SqlRepo.Read", "reader", {
|
|
337
|
+
owner: todos,
|
|
338
|
+
deps: [{ kind: "Sql.Connection", name: "db", id: "Sql.Connection.db" }],
|
|
339
|
+
}),
|
|
340
|
+
]);
|
|
341
|
+
const sub = subtreeGraph(topology, "Crud.Resource.todos");
|
|
342
|
+
expect(sub.nodes.some((n) => n.id === "Sql.Connection.db")).toBe(false);
|
|
343
|
+
expect(sub.edges.some((e) => e.target === "Sql.Connection.db")).toBe(false);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
/** A terminal dispatch event carrying trace ids — `spanId` / `parentSpanId`. */
|
|
348
|
+
const inv = (
|
|
349
|
+
name: string,
|
|
350
|
+
kind: string,
|
|
351
|
+
spanId: number,
|
|
352
|
+
parentSpanId: number | undefined,
|
|
353
|
+
outcome: Outcome,
|
|
354
|
+
suffix: string,
|
|
355
|
+
detail: Record<string, unknown> = {},
|
|
356
|
+
): DebugFrame =>
|
|
357
|
+
ev(`${name}.${suffix}`, {
|
|
358
|
+
spanId,
|
|
359
|
+
parentSpanId,
|
|
360
|
+
capability: "invoke",
|
|
361
|
+
phase: "end",
|
|
362
|
+
outcome,
|
|
363
|
+
ref: { kind, name },
|
|
364
|
+
...detail,
|
|
365
|
+
});
|
|
366
|
+
|
|
136
367
|
describe("deriveInvocations", () => {
|
|
137
|
-
it("is empty when events carry no
|
|
138
|
-
const t = deriveInvocations([
|
|
368
|
+
it("is empty when events carry no span ids (tracing off)", () => {
|
|
369
|
+
const t = deriveInvocations([disp("x", "Js.Script", "ok", "Invoked", { outputs: 1 })]);
|
|
139
370
|
expect(t.roots).toHaveLength(0);
|
|
140
371
|
expect(t.byId.size).toBe(0);
|
|
141
372
|
});
|
|
142
373
|
|
|
143
374
|
it("collects a root invocation with its resource, inputs and outputs", () => {
|
|
144
|
-
const t = deriveInvocations([inv("Http.Server
|
|
375
|
+
const t = deriveInvocations([inv("api", "Http.Server", 1, undefined, "ok", "Invoked", { inputs: { a: 1 }, outputs: "ok" })]);
|
|
145
376
|
expect(t.roots).toHaveLength(1);
|
|
146
377
|
expect(t.roots[0]).toMatchObject({ id: 1, kind: "Http.Server", name: "api", outcome: "ok", inputs: { a: 1 }, outputs: "ok" });
|
|
147
378
|
});
|
|
148
379
|
|
|
149
380
|
it("links children to parents and keeps only roots at the top", () => {
|
|
150
381
|
const t = deriveInvocations([
|
|
151
|
-
inv("
|
|
152
|
-
inv("
|
|
153
|
-
inv("
|
|
382
|
+
inv("root", "A", 1, undefined, "ok", "Invoked", { outputs: 1 }),
|
|
383
|
+
inv("child", "B", 2, 1, "ok", "Invoked", { outputs: 2 }),
|
|
384
|
+
inv("grand", "C", 3, 2, "ok", "Invoked", { outputs: 3 }),
|
|
154
385
|
]);
|
|
155
386
|
expect(t.roots.map((r) => r.id)).toEqual([1]);
|
|
156
387
|
expect(t.childrenOf.get(1)).toEqual([2]);
|
|
157
388
|
expect(t.childrenOf.get(2)).toEqual([3]);
|
|
158
389
|
});
|
|
159
390
|
|
|
160
|
-
it("dedupes the InvokeRejected.Undeclared echo by id", () => {
|
|
391
|
+
it("dedupes the InvokeRejected.Undeclared echo by id, keeping the primary", () => {
|
|
161
392
|
const t = deriveInvocations([
|
|
162
|
-
inv("
|
|
163
|
-
inv("
|
|
393
|
+
inv("x", "A", 1, undefined, "rejected", "InvokeRejected", { code: "E" }),
|
|
394
|
+
inv("x", "A", 1, undefined, "rejected", "InvokeRejected.Undeclared", { code: "E" }),
|
|
164
395
|
]);
|
|
165
396
|
expect(t.byId.size).toBe(1);
|
|
166
397
|
expect(t.byId.get(1)).toMatchObject({ outcome: "rejected", suffix: "InvokeRejected" });
|
|
@@ -170,9 +401,9 @@ describe("deriveInvocations", () => {
|
|
|
170
401
|
describe("traceSubgraph", () => {
|
|
171
402
|
it("returns only the participating resources wired by call edges", () => {
|
|
172
403
|
const t = deriveInvocations([
|
|
173
|
-
inv("
|
|
174
|
-
inv("
|
|
175
|
-
inv("
|
|
404
|
+
inv("root", "A", 1, undefined, "ok", "Invoked", { outputs: 1 }),
|
|
405
|
+
inv("child", "B", 2, 1, "ok", "Invoked", { outputs: 2 }),
|
|
406
|
+
inv("other", "C", 9, undefined, "ok", "Invoked", { outputs: 9 }), // a different trace
|
|
176
407
|
]);
|
|
177
408
|
const g = traceSubgraph(t, 1);
|
|
178
409
|
expect(g.nodes.map((n) => n.id).sort()).toEqual(["A.root", "B.child"]);
|
|
@@ -182,9 +413,9 @@ describe("traceSubgraph", () => {
|
|
|
182
413
|
|
|
183
414
|
it("collapses repeated calls to one node holding every invocation", () => {
|
|
184
415
|
const t = deriveInvocations([
|
|
185
|
-
inv("
|
|
186
|
-
inv("
|
|
187
|
-
inv("
|
|
416
|
+
inv("root", "A", 1, undefined, "ok", "Invoked", { outputs: 1 }),
|
|
417
|
+
inv("svc", "B", 2, 1, "ok", "Invoked", { outputs: 2 }),
|
|
418
|
+
inv("svc", "B", 3, 1, "ok", "Invoked", { outputs: 3 }),
|
|
188
419
|
]);
|
|
189
420
|
const g = traceSubgraph(t, 1);
|
|
190
421
|
const svc = g.nodes.find((n) => n.id === "B.svc");
|