@webpieces/nx-webpieces-rules 0.4.907 → 0.4.909
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 +10 -1
- package/package.json +7 -7
- package/src/lib/api-usage/external-systems.js +2 -0
- package/src/lib/api-usage/external-systems.js.map +1 -1
- package/src/lib/runtime-details.js +7 -1
- package/src/lib/runtime-details.js.map +1 -1
- package/src/lib/runtime-external-triggers.d.ts +14 -0
- package/src/lib/runtime-external-triggers.js +45 -0
- package/src/lib/runtime-external-triggers.js.map +1 -0
- package/src/lib/runtime-graph-io.js +29 -1
- package/src/lib/runtime-graph-io.js.map +1 -1
- package/src/lib/runtime-graph-levels.d.ts +2 -20
- package/src/lib/runtime-graph-levels.js +8 -30
- package/src/lib/runtime-graph-levels.js.map +1 -1
- package/src/lib/runtime-graph-model.d.ts +21 -13
- package/src/lib/runtime-graph-model.js +18 -0
- package/src/lib/runtime-graph-model.js.map +1 -1
- package/src/lib/runtime-graph.d.ts +1 -1
- package/src/lib/runtime-graph.js +15 -6
- package/src/lib/runtime-graph.js.map +1 -1
- package/src/lib/runtime-node-levels.d.ts +13 -0
- package/src/lib/runtime-node-levels.js +118 -0
- package/src/lib/runtime-node-levels.js.map +1 -0
- package/src/lib/runtime-remote-calls.js +2 -0
- package/src/lib/runtime-remote-calls.js.map +1 -1
- package/src/lib/runtime-viewer-adapter.js +40 -5
- package/src/lib/runtime-viewer-adapter.js.map +1 -1
- package/src/lib/runtime-visualizer.js +46 -45
- package/src/lib/runtime-visualizer.js.map +1 -1
- package/src/lib/runtime-viz-theme.js +2 -2
- package/src/lib/runtime-viz-theme.js.map +1 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RuntimeNodeLevels = void 0;
|
|
4
|
+
exports.runtimeAdjacency = runtimeAdjacency;
|
|
5
|
+
const runtime_graph_model_1 = require("./runtime-graph-model");
|
|
6
|
+
const runtime_graph_levels_1 = require("./runtime-graph-levels");
|
|
7
|
+
/** Generator-owned layering. Renderers consume these numbers without deriving topology. */
|
|
8
|
+
class RuntimeNodeLevels {
|
|
9
|
+
apply(graph) {
|
|
10
|
+
this.collectContracts(graph);
|
|
11
|
+
const adjacency = this.adjacency(graph);
|
|
12
|
+
const order = (0, runtime_graph_levels_1.assignLevels)(adjacency); // The shared detector reports queue identities in cycles.
|
|
13
|
+
const levels = {};
|
|
14
|
+
for (const name of Object.keys(order).sort((a, b) => order[a] - order[b] || a.localeCompare(b))) {
|
|
15
|
+
const floor = graph.services[name] === undefined ? 0 : 1;
|
|
16
|
+
levels[name] = Math.max(floor, ...adjacency[name].map((target) => levels[target] + 1));
|
|
17
|
+
}
|
|
18
|
+
for (const [name, service] of Object.entries(graph.services)) {
|
|
19
|
+
service.level = levels[name];
|
|
20
|
+
const highest = [...adjacency[name]].sort((a, b) => levels[b] - levels[a] || a.localeCompare(b))[0];
|
|
21
|
+
service.levelBecause =
|
|
22
|
+
highest === undefined
|
|
23
|
+
? 'no callees (service starts at L1)'
|
|
24
|
+
: `${graph.services[highest] === undefined ? highest : `rpc:${highest}`} (L${levels[highest]})`;
|
|
25
|
+
}
|
|
26
|
+
for (const entry of this.queues(graph))
|
|
27
|
+
this.queueLevel(entry[0], entry[1], graph, levels);
|
|
28
|
+
for (const system of Object.values(graph.externalSystems ?? {}))
|
|
29
|
+
this.sink(system);
|
|
30
|
+
for (const remote of Object.values(graph.remoteServices ?? {}))
|
|
31
|
+
this.sink(remote);
|
|
32
|
+
for (const use of graph.unresolvedUses)
|
|
33
|
+
this.sink(use);
|
|
34
|
+
const top = 1 +
|
|
35
|
+
Math.max(0, ...Object.values(levels), ...this.queues(graph).map((entry) => entry[1].level));
|
|
36
|
+
for (const trigger of graph.triggers) {
|
|
37
|
+
trigger.level = top;
|
|
38
|
+
trigger.levelBecause = `above every non-trigger node (L${top - 1})`;
|
|
39
|
+
}
|
|
40
|
+
graph.maxLevel = graph.triggers.length > 0 ? top : top - 1;
|
|
41
|
+
}
|
|
42
|
+
/** All runtime dependencies except self-producers and explicitly admitted legacy RPC cuts. */
|
|
43
|
+
adjacency(graph) {
|
|
44
|
+
const adj = {};
|
|
45
|
+
for (const name of Object.keys(graph.services))
|
|
46
|
+
adj[name] = [];
|
|
47
|
+
for (const edge of graph.runtimeEdges) {
|
|
48
|
+
if (edge.type !== 'pubsub' && edge.cutLegacyCycle !== true)
|
|
49
|
+
adj[edge.from].push(edge.to);
|
|
50
|
+
}
|
|
51
|
+
for (const entry of this.queues(graph)) {
|
|
52
|
+
const key = entry[0];
|
|
53
|
+
const queue = entry[1];
|
|
54
|
+
const foreign = queue.producedBy.filter((producer) => queue.consumedBy.some((consumer) => consumer !== producer));
|
|
55
|
+
queue.selfQueue = foreign.length === 0;
|
|
56
|
+
if (queue.selfQueue)
|
|
57
|
+
continue;
|
|
58
|
+
adj[`queue:${key}`] = [...queue.consumedBy];
|
|
59
|
+
for (const producer of foreign)
|
|
60
|
+
adj[producer].push(`queue:${key}`);
|
|
61
|
+
}
|
|
62
|
+
for (const [name, system] of Object.entries(graph.externalSystems ?? {})) {
|
|
63
|
+
adj[`external:${name}`] = [];
|
|
64
|
+
for (const caller of system.usedBy)
|
|
65
|
+
adj[caller].push(`external:${name}`);
|
|
66
|
+
}
|
|
67
|
+
for (const [name, remote] of Object.entries(graph.remoteServices ?? {})) {
|
|
68
|
+
adj[`remote:${name}`] = [];
|
|
69
|
+
for (const call of remote.calledBy)
|
|
70
|
+
adj[call.from].push(`remote:${name}`);
|
|
71
|
+
}
|
|
72
|
+
for (const use of graph.unresolvedUses) {
|
|
73
|
+
const id = `external:${graph.apis[use.api]?.owner ?? use.api}`;
|
|
74
|
+
adj[id] = [];
|
|
75
|
+
adj[use.service].push(id);
|
|
76
|
+
}
|
|
77
|
+
for (const targets of Object.values(adj))
|
|
78
|
+
targets.sort();
|
|
79
|
+
return adj;
|
|
80
|
+
}
|
|
81
|
+
collectContracts(graph) {
|
|
82
|
+
graph.contractQueues = {};
|
|
83
|
+
for (const edge of graph.runtimeEdges) {
|
|
84
|
+
if (edge.type !== 'pubsub' || edge.queue !== undefined)
|
|
85
|
+
continue;
|
|
86
|
+
for (const api of edge.via) {
|
|
87
|
+
const queue = (graph.contractQueues[api] ??= new runtime_graph_model_1.RuntimeQueueFlow());
|
|
88
|
+
if (!queue.producedBy.includes(edge.from))
|
|
89
|
+
queue.producedBy.push(edge.from);
|
|
90
|
+
if (!queue.consumedBy.includes(edge.to))
|
|
91
|
+
queue.consumedBy.push(edge.to);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const queue of Object.values(graph.contractQueues)) {
|
|
95
|
+
queue.producedBy.sort();
|
|
96
|
+
queue.consumedBy.sort();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
queues(graph) {
|
|
100
|
+
return [...Object.entries(graph.queues), ...Object.entries(graph.contractQueues)];
|
|
101
|
+
}
|
|
102
|
+
queueLevel(key, queue, graph, levels) {
|
|
103
|
+
const consumerLevel = Math.max(0, ...queue.consumedBy.map((name) => graph.services[name].level));
|
|
104
|
+
queue.level = queue.selfQueue ? consumerLevel : levels[`queue:${key}`];
|
|
105
|
+
queue.levelBecause = `${queue.selfQueue ? 'self-queue beside' : 'above'} consumer ${queue.consumedBy.join(', ')} (L${consumerLevel})`;
|
|
106
|
+
}
|
|
107
|
+
sink(node) {
|
|
108
|
+
node.level = 0;
|
|
109
|
+
node.levelBecause = 'outbound sink';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.RuntimeNodeLevels = RuntimeNodeLevels;
|
|
113
|
+
/** Complete generated runtime dependencies, including foreign queues and outbound sinks. */
|
|
114
|
+
// webpieces-disable no-function-outside-class -- runtime graph entry point, sibling of deriveRuntimeGraph
|
|
115
|
+
function runtimeAdjacency(graph) {
|
|
116
|
+
return new RuntimeNodeLevels().adjacency(graph);
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=runtime-node-levels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-node-levels.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-node-levels.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RuntimeGraph, RuntimeLevel } from './runtime-graph-model';\nimport { RuntimeQueueFlow } from './runtime-graph-model';\nimport { assignLevels } from './runtime-graph-levels';\n\ntype QueueEntry = [string, RuntimeQueueFlow];\n\n/** Generator-owned layering. Renderers consume these numbers without deriving topology. */\nexport class RuntimeNodeLevels {\n apply(graph: RuntimeGraph): void {\n this.collectContracts(graph);\n const adjacency = this.adjacency(graph);\n const order = assignLevels(adjacency); // The shared detector reports queue identities in cycles.\n const levels: Record<string, number> = {};\n for (const name of Object.keys(order).sort(\n (a, b) => order[a] - order[b] || a.localeCompare(b),\n )) {\n const floor = graph.services[name] === undefined ? 0 : 1;\n levels[name] = Math.max(floor, ...adjacency[name].map((target) => levels[target] + 1));\n }\n for (const [name, service] of Object.entries(graph.services)) {\n service.level = levels[name];\n const highest = [...adjacency[name]].sort(\n (a, b) => levels[b] - levels[a] || a.localeCompare(b),\n )[0];\n service.levelBecause =\n highest === undefined\n ? 'no callees (service starts at L1)'\n : `${graph.services[highest] === undefined ? highest : `rpc:${highest}`} (L${levels[highest]})`;\n }\n for (const entry of this.queues(graph)) this.queueLevel(entry[0], entry[1], graph, levels);\n for (const system of Object.values(graph.externalSystems ?? {})) this.sink(system);\n for (const remote of Object.values(graph.remoteServices ?? {})) this.sink(remote);\n for (const use of graph.unresolvedUses) this.sink(use);\n const top =\n 1 +\n Math.max(\n 0,\n ...Object.values(levels),\n ...this.queues(graph).map((entry) => entry[1].level),\n );\n for (const trigger of graph.triggers) {\n trigger.level = top;\n trigger.levelBecause = `above every non-trigger node (L${top - 1})`;\n }\n graph.maxLevel = graph.triggers.length > 0 ? top : top - 1;\n }\n\n /** All runtime dependencies except self-producers and explicitly admitted legacy RPC cuts. */\n adjacency(graph: RuntimeGraph): Record<string, string[]> {\n const adj: Record<string, string[]> = {};\n for (const name of Object.keys(graph.services)) adj[name] = [];\n for (const edge of graph.runtimeEdges) {\n if (edge.type !== 'pubsub' && edge.cutLegacyCycle !== true)\n adj[edge.from].push(edge.to);\n }\n for (const entry of this.queues(graph)) {\n const key = entry[0];\n const queue = entry[1];\n const foreign = queue.producedBy.filter((producer) =>\n queue.consumedBy.some((consumer) => consumer !== producer),\n );\n queue.selfQueue = foreign.length === 0;\n if (queue.selfQueue) continue;\n adj[`queue:${key}`] = [...queue.consumedBy];\n for (const producer of foreign) adj[producer].push(`queue:${key}`);\n }\n for (const [name, system] of Object.entries(graph.externalSystems ?? {})) {\n adj[`external:${name}`] = [];\n for (const caller of system.usedBy) adj[caller].push(`external:${name}`);\n }\n for (const [name, remote] of Object.entries(graph.remoteServices ?? {})) {\n adj[`remote:${name}`] = [];\n for (const call of remote.calledBy) adj[call.from].push(`remote:${name}`);\n }\n for (const use of graph.unresolvedUses) {\n const id = `external:${graph.apis[use.api]?.owner ?? use.api}`;\n adj[id] = [];\n adj[use.service].push(id);\n }\n for (const targets of Object.values(adj)) targets.sort();\n return adj;\n }\n\n private collectContracts(graph: RuntimeGraph): void {\n graph.contractQueues = {};\n for (const edge of graph.runtimeEdges) {\n if (edge.type !== 'pubsub' || edge.queue !== undefined) continue;\n for (const api of edge.via) {\n const queue = (graph.contractQueues[api] ??= new RuntimeQueueFlow());\n if (!queue.producedBy.includes(edge.from)) queue.producedBy.push(edge.from);\n if (!queue.consumedBy.includes(edge.to)) queue.consumedBy.push(edge.to);\n }\n }\n for (const queue of Object.values(graph.contractQueues)) {\n queue.producedBy.sort();\n queue.consumedBy.sort();\n }\n }\n\n private queues(graph: RuntimeGraph): QueueEntry[] {\n return [...Object.entries(graph.queues), ...Object.entries(graph.contractQueues)];\n }\n\n private queueLevel(\n key: string,\n queue: RuntimeQueueFlow,\n graph: RuntimeGraph,\n levels: Record<string, number>,\n ): void {\n const consumerLevel = Math.max(\n 0,\n ...queue.consumedBy.map((name) => graph.services[name].level),\n );\n queue.level = queue.selfQueue ? consumerLevel : levels[`queue:${key}`];\n queue.levelBecause = `${queue.selfQueue ? 'self-queue beside' : 'above'} consumer ${queue.consumedBy.join(', ')} (L${consumerLevel})`;\n }\n\n private sink(node: RuntimeLevel): void {\n node.level = 0;\n node.levelBecause = 'outbound sink';\n }\n}\n\n/** Complete generated runtime dependencies, including foreign queues and outbound sinks. */\n// webpieces-disable no-function-outside-class -- runtime graph entry point, sibling of deriveRuntimeGraph\nexport function runtimeAdjacency(graph: RuntimeGraph): Record<string, string[]> {\n return new RuntimeNodeLevels().adjacency(graph);\n}\n"]}
|
|
@@ -64,6 +64,8 @@ class RemoteCallCollector {
|
|
|
64
64
|
for (const name of names) {
|
|
65
65
|
const decl = this.remotes[name];
|
|
66
66
|
remoteServices[name] = {
|
|
67
|
+
level: 0,
|
|
68
|
+
levelBecause: 'outbound sink',
|
|
67
69
|
label: decl.label,
|
|
68
70
|
implements: decl.implements.map((entry) => `${entry.api} (${entry.type})`),
|
|
69
71
|
calledBy: this.calledBy(name),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-remote-calls.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-remote-calls.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"runtime-remote-calls.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-remote-calls.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Remote service calls in the runtime graph (#1203)\n *\n * A `uses` whose named target (the call-site `targetService`, else the caller's `callsService`) is a\n * REVIEWED remote service — declared in dependencies.json `remoteServices`, outside this workspace —\n * is claimed here instead of being resolved against the workspace's own nodes. Without this the call\n * either decayed into an \"unresolved\" vendor box (nothing in-repo implements the contract) or failed as\n * a name no module answers to (something in-repo does).\n *\n * Workspace names always win: a remote name that collides with a node is reported as a problem and\n * never claims the call, so a stray declaration cannot redirect a call away from a real module.\n */\n\nimport type { ApiRef, ApiTransport } from './api-usage/api-relations';\nimport type { RemoteServiceApi, RemoteServices } from './remote-services';\nimport type { RuntimeGraph, RuntimeRemoteCall, RuntimeRemoteService } from './runtime-graph-model';\n\nexport class RemoteCallCollector {\n /** remote name -> \"caller transport\" -> contracts called there. */\n private readonly calls = new Map<string, Map<string, Set<string>>>();\n\n constructor(\n private readonly remotes: RemoteServices,\n /** True when a workspace runtime node answers to the name. */\n private readonly isWorkspaceName: (name: string) => boolean,\n private readonly problems: string[],\n ) {\n for (const name of Object.keys(remotes).sort())\n if (isWorkspaceName(name))\n problems.push(\n `remoteServices declares '${name}', but a workspace module already answers to that name — a remote ` +\n `service must live OUTSIDE this workspace. Remove the remoteServices entry or rename one of them.`,\n );\n }\n\n /**\n * True when `wanted` names a declared remote service, which then owns this use. A remote that does\n * not declare the contract + transport is a problem; the call is still drawn to it, so the picture\n * shows exactly what the declaration is missing.\n */\n claim(user: string, ref: ApiRef, wanted: string | undefined): boolean {\n if (wanted === undefined) return false;\n const remote = this.remotes[wanted];\n if (remote === undefined || this.isWorkspaceName(wanted)) return false;\n if (!remote.implementsApi(ref.api, ref.type))\n this.problems.push(\n `${user} calls \"${ref.api}\" (${ref.type}) at remote service '${wanted}', but remoteServices.${wanted}.implements ` +\n `does not declare it. Add { \"api\": \"${ref.api}\", \"type\": \"${ref.type}\" } after review, or fix the target.`,\n );\n let byCaller = this.calls.get(wanted);\n if (byCaller === undefined) {\n byCaller = new Map<string, Set<string>>();\n this.calls.set(wanted, byCaller);\n }\n const key = `${user} ${ref.type}`;\n if (!byCaller.has(key)) byCaller.set(key, new Set<string>());\n byCaller.get(key)!.add(ref.api);\n return true;\n }\n\n /** Every DECLARED remote service, called or not, onto the graph. Absent when none are declared. */\n attach(graph: RuntimeGraph): void {\n const names = Object.keys(this.remotes).sort();\n if (names.length === 0) return;\n const remoteServices: Record<string, RuntimeRemoteService> = {};\n for (const name of names) {\n const decl = this.remotes[name];\n remoteServices[name] = {\n level: 0,\n levelBecause: 'outbound sink',\n label: decl.label,\n implements: decl.implements.map(\n (entry: RemoteServiceApi) => `${entry.api} (${entry.type})`,\n ),\n calledBy: this.calledBy(name),\n };\n }\n graph.remoteServices = remoteServices;\n }\n\n private calledBy(name: string): RuntimeRemoteCall[] {\n const byCaller = this.calls.get(name) ?? new Map<string, Set<string>>();\n return [...byCaller.keys()].sort().map((key: string): RuntimeRemoteCall => {\n const parts = key.split(' ');\n return {\n from: parts[0],\n via: [...byCaller.get(key)!].sort(),\n type: parts[1] as ApiTransport,\n };\n });\n }\n}\n"]}
|
|
@@ -5,6 +5,8 @@ const graph_render_model_1 = require("./graph-render-model");
|
|
|
5
5
|
const graph_color_modes_1 = require("./graph-color-modes");
|
|
6
6
|
const graph_products_1 = require("./graph-products");
|
|
7
7
|
const graph_level_bands_1 = require("./graph-level-bands");
|
|
8
|
+
const remote_service_node_id_1 = require("./remote-service-node-id");
|
|
9
|
+
const runtime_external_triggers_1 = require("./runtime-external-triggers");
|
|
8
10
|
/** Saved project facts enrich presentation without changing runtime derivation or topology. */
|
|
9
11
|
class RuntimeViewerAdapter {
|
|
10
12
|
graph;
|
|
@@ -28,11 +30,44 @@ class RuntimeViewerAdapter {
|
|
|
28
30
|
return model.styledNode(facts, this.styler.dots(facts, this.palette));
|
|
29
31
|
}
|
|
30
32
|
levelBands(model) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
.
|
|
35
|
-
|
|
33
|
+
const levels = new Map();
|
|
34
|
+
const safe = (name) => name.replace(/[^A-Za-z0-9_]/g, '_');
|
|
35
|
+
for (const [name, service] of Object.entries(this.graph.services))
|
|
36
|
+
levels.set(name, service.level);
|
|
37
|
+
for (const node of model.nodes) {
|
|
38
|
+
if (node.queueKeys.length === 0)
|
|
39
|
+
continue;
|
|
40
|
+
const key = node.queueKeys[0];
|
|
41
|
+
const queue = key.startsWith('queue:')
|
|
42
|
+
? this.graph.contractQueues[key.slice(6)]
|
|
43
|
+
: this.graph.queues[key];
|
|
44
|
+
levels.set(node.id, queue.level);
|
|
45
|
+
}
|
|
46
|
+
for (const [name, system] of Object.entries(this.graph.externalSystems ?? {}))
|
|
47
|
+
levels.set(`system__${safe(name)}`, system.level);
|
|
48
|
+
for (const [name, remote] of Object.entries(this.graph.remoteServices ?? {}))
|
|
49
|
+
levels.set(new remote_service_node_id_1.RemoteServiceNodeId().of(name), remote.level);
|
|
50
|
+
for (const trigger of this.graph.triggers) {
|
|
51
|
+
if (trigger.kind === 'cron')
|
|
52
|
+
levels.set(`cron__${safe(`${trigger.api}_${trigger.method}`)}`, trigger.level);
|
|
53
|
+
}
|
|
54
|
+
for (const group of new runtime_external_triggers_1.RuntimeExternalTriggers(this.graph.triggers).groups)
|
|
55
|
+
levels.set(group.from, group.triggers[0].level);
|
|
56
|
+
// Undeclared vendor seams are terminal sinks too, independent of their grouping for display.
|
|
57
|
+
for (const use of this.graph.unresolvedUses) {
|
|
58
|
+
const owner = this.graph.apis[use.api]?.owner ?? use.api;
|
|
59
|
+
levels.set(`external__${owner}`, use.level);
|
|
60
|
+
}
|
|
61
|
+
const bands = new Map();
|
|
62
|
+
for (const node of model.nodes) {
|
|
63
|
+
const level = levels.get(node.id);
|
|
64
|
+
if (!bands.has(level))
|
|
65
|
+
bands.set(level, []);
|
|
66
|
+
bands.get(level).push(node.id);
|
|
67
|
+
}
|
|
68
|
+
return [...bands.entries()]
|
|
69
|
+
.sort((a, b) => b[0] - a[0])
|
|
70
|
+
.map((entry) => new graph_level_bands_1.LevelBand(entry[0], entry[1]));
|
|
36
71
|
}
|
|
37
72
|
/** Context keeps its shape/kind palette; Impact never paints a queue as a source owner. */
|
|
38
73
|
contextModes(model) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-viewer-adapter.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-viewer-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EnhancedGraph } from './graph-sorter';\nimport type { RuntimeGraph, RuntimeService } from './runtime-graph-model';\nimport { GraphRenderModel, RenderNode } from './graph-render-model';\nimport { NodeFacts, NodeModeDots, NodeModeStyler } from './graph-color-modes';\nimport { ProductPalette, PRODUCT_SHARED_FILL, PRODUCT_NONE_FILL } from './graph-products';\nimport { LevelBand } from './graph-level-bands';\n\n/** Saved project facts enrich presentation without changing runtime derivation or topology. */\nexport class RuntimeViewerAdapter {\n private readonly styler = new NodeModeStyler();\n private readonly palette: ProductPalette;\n\n constructor(\n private readonly graph: RuntimeGraph,\n private readonly projects: EnhancedGraph | null,\n ) {\n this.palette = new ProductPalette(\n [\n ...new Set(\n Object.values(graph.services)\n .filter((service) => service.drawOnGraph !== false)\n .flatMap((service) => service.products ?? []),\n ),\n ].sort(),\n );\n }\n\n service(model: GraphRenderModel, name: string, service: RuntimeService): string {\n const project = this.projects?.[name];\n const role = service.role ?? project?.role ?? 'unknown';\n const facts = new NodeFacts(\n name,\n name.split('/').pop() ?? name,\n service.level,\n role,\n project?.framework ?? [],\n service.products ?? [],\n `Implements (${new Set(service.implements).size})${service.serviceName === undefined ? '' : ` · \"${service.serviceName}\"`}`,\n );\n this.styler.record(model.legend, facts, this.palette);\n return model.styledNode(facts, this.styler.dots(facts, this.palette));\n }\n\n levelBands(model: GraphRenderModel): LevelBand[] {\n return [...new Set(model.nodes.map((node: RenderNode) => node.tags!.level))]\n .sort((a: number, b: number) => b - a)\n .map(\n (level: number) =>\n new LevelBand(\n level,\n model.nodes\n .filter((node: RenderNode) => node.tags?.level === level)\n .map((node: RenderNode) => node.id),\n ),\n );\n }\n\n /** Context keeps its shape/kind palette; Impact never paints a queue as a source owner. */\n contextModes(model: GraphRenderModel): void {\n for (let index = 0; index < model.nodes.length; index++) {\n const node = model.nodes[index];\n if (node.tags !== null) continue;\n const colors = node.products.map((product) => this.palette.colorOf(product).color);\n const fill =\n colors.length === 0\n ? PRODUCT_NONE_FILL\n : this.palette.sharedByAll(node.products)\n ? PRODUCT_SHARED_FILL\n : colors.join(':');\n // Graphviz cylinders cannot stripe; a gradient preserves recognizable kind geometry.\n const recolored = node.dot.replace(/fillcolor=\"[^\"]*\"/, `fillcolor=\"${fill}\"`);\n const product =\n colors.length === 0\n ? recolored.replace(/style=\"[^\"]*\"/, 'style=\"filled,dashed\"')\n : recolored;\n const modes = new NodeModeDots(\n node.dot,\n node.dot,\n node.dot,\n node.dot,\n node.dot,\n node.dot,\n product,\n );\n model.nodes[index] = new RenderNode(\n node.id,\n node.dot,\n modes,\n null,\n node.products,\n node.queueKeys,\n );\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"runtime-viewer-adapter.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-viewer-adapter.ts"],"names":[],"mappings":";;;AAEA,6DAAoE;AACpE,2DAA8E;AAC9E,qDAA0F;AAC1F,2DAAgD;AAChD,qEAA+D;AAC/D,2EAAsE;AAEtE,+FAA+F;AAC/F,MAAa,oBAAoB;IAKR;IACA;IALJ,MAAM,GAAG,IAAI,kCAAc,EAAE,CAAC;IAC9B,OAAO,CAAiB;IAEzC,YACqB,KAAmB,EACnB,QAA8B;QAD9B,UAAK,GAAL,KAAK,CAAc;QACnB,aAAQ,GAAR,QAAQ,CAAsB;QAE/C,IAAI,CAAC,OAAO,GAAG,IAAI,+BAAc,CAC7B;YACI,GAAG,IAAI,GAAG,CACN,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC;iBACxB,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,KAAK,KAAK,CAAC;iBAClD,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CACpD;SACJ,CAAC,IAAI,EAAE,CACX,CAAC;IACN,CAAC;IAED,OAAO,CAAC,KAAuB,EAAE,IAAY,EAAE,OAAuB;QAClE,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,EAAE,IAAI,IAAI,SAAS,CAAC;QACxD,MAAM,KAAK,GAAG,IAAI,6BAAS,CACvB,IAAI,EACJ,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,EAC7B,OAAO,CAAC,KAAK,EACb,IAAI,EACJ,OAAO,EAAE,SAAS,IAAI,EAAE,EACxB,OAAO,CAAC,QAAQ,IAAI,EAAE,EACtB,eAAe,IAAI,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,WAAW,GAAG,EAAE,CAC9H,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACtD,OAAO,KAAK,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1E,CAAC;IAED,UAAU,CAAC,KAAuB;QAC9B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QACzC,MAAM,IAAI,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;QAC3E,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;YAC7D,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QACpC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAC7B,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAC9B,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC;gBAClC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACzC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7B,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,IAAI,EAAE,CAAC;YACzE,MAAM,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACtD,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,IAAI,EAAE,CAAC;YACxE,MAAM,CAAC,GAAG,CAAC,IAAI,4CAAmB,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACjE,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;YACxC,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;gBACvB,MAAM,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QACvF,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,IAAI,mDAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM;YACvE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACpD,6FAA6F;QAC7F,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC;YACzD,MAAM,CAAC,GAAG,CAAC,aAAa,KAAK,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;QAChD,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAE,CAAC;YACnC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAC5C,KAAK,CAAC,GAAG,CAAC,KAAK,CAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpC,CAAC;QACD,OAAO,CAAC,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;aACtB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;aAC3B,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,6BAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,CAAC;IAED,2FAA2F;IAC3F,YAAY,CAAC,KAAuB;QAChC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;YACtD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAChC,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;gBAAE,SAAS;YACjC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC;YACnF,MAAM,IAAI,GACN,MAAM,CAAC,MAAM,KAAK,CAAC;gBACf,CAAC,CAAC,kCAAiB;gBACnB,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC;oBACvC,CAAC,CAAC,oCAAmB;oBACrB,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7B,qFAAqF;YACrF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,mBAAmB,EAAE,cAAc,IAAI,GAAG,CAAC,CAAC;YAC/E,MAAM,OAAO,GACT,MAAM,CAAC,MAAM,KAAK,CAAC;gBACf,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,eAAe,EAAE,uBAAuB,CAAC;gBAC7D,CAAC,CAAC,SAAS,CAAC;YACpB,MAAM,KAAK,GAAG,IAAI,gCAAY,CAC1B,IAAI,CAAC,GAAG,EACR,IAAI,CAAC,GAAG,EACR,IAAI,CAAC,GAAG,EACR,IAAI,CAAC,GAAG,EACR,IAAI,CAAC,GAAG,EACR,IAAI,CAAC,GAAG,EACR,OAAO,CACV,CAAC;YACF,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,+BAAU,CAC/B,IAAI,CAAC,EAAE,EACP,IAAI,CAAC,GAAG,EACR,KAAK,EACL,IAAI,EACJ,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,SAAS,CACjB,CAAC;QACN,CAAC;IACL,CAAC;CACJ;AA/GD,oDA+GC","sourcesContent":["import type { EnhancedGraph } from './graph-sorter';\nimport type { RuntimeGraph, RuntimeService } from './runtime-graph-model';\nimport { GraphRenderModel, RenderNode } from './graph-render-model';\nimport { NodeFacts, NodeModeDots, NodeModeStyler } from './graph-color-modes';\nimport { ProductPalette, PRODUCT_SHARED_FILL, PRODUCT_NONE_FILL } from './graph-products';\nimport { LevelBand } from './graph-level-bands';\nimport { RemoteServiceNodeId } from './remote-service-node-id';\nimport { RuntimeExternalTriggers } from './runtime-external-triggers';\n\n/** Saved project facts enrich presentation without changing runtime derivation or topology. */\nexport class RuntimeViewerAdapter {\n private readonly styler = new NodeModeStyler();\n private readonly palette: ProductPalette;\n\n constructor(\n private readonly graph: RuntimeGraph,\n private readonly projects: EnhancedGraph | null,\n ) {\n this.palette = new ProductPalette(\n [\n ...new Set(\n Object.values(graph.services)\n .filter((service) => service.drawOnGraph !== false)\n .flatMap((service) => service.products ?? []),\n ),\n ].sort(),\n );\n }\n\n service(model: GraphRenderModel, name: string, service: RuntimeService): string {\n const project = this.projects?.[name];\n const role = service.role ?? project?.role ?? 'unknown';\n const facts = new NodeFacts(\n name,\n name.split('/').pop() ?? name,\n service.level,\n role,\n project?.framework ?? [],\n service.products ?? [],\n `Implements (${new Set(service.implements).size})${service.serviceName === undefined ? '' : ` · \"${service.serviceName}\"`}`,\n );\n this.styler.record(model.legend, facts, this.palette);\n return model.styledNode(facts, this.styler.dots(facts, this.palette));\n }\n\n levelBands(model: GraphRenderModel): LevelBand[] {\n const levels = new Map<string, number>();\n const safe = (name: string): string => name.replace(/[^A-Za-z0-9_]/g, '_');\n for (const [name, service] of Object.entries(this.graph.services))\n levels.set(name, service.level);\n for (const node of model.nodes) {\n if (node.queueKeys.length === 0) continue;\n const key = node.queueKeys[0];\n const queue = key.startsWith('queue:')\n ? this.graph.contractQueues[key.slice(6)]\n : this.graph.queues[key];\n levels.set(node.id, queue.level);\n }\n for (const [name, system] of Object.entries(this.graph.externalSystems ?? {}))\n levels.set(`system__${safe(name)}`, system.level);\n for (const [name, remote] of Object.entries(this.graph.remoteServices ?? {}))\n levels.set(new RemoteServiceNodeId().of(name), remote.level);\n for (const trigger of this.graph.triggers) {\n if (trigger.kind === 'cron')\n levels.set(`cron__${safe(`${trigger.api}_${trigger.method}`)}`, trigger.level);\n }\n for (const group of new RuntimeExternalTriggers(this.graph.triggers).groups)\n levels.set(group.from, group.triggers[0].level);\n // Undeclared vendor seams are terminal sinks too, independent of their grouping for display.\n for (const use of this.graph.unresolvedUses) {\n const owner = this.graph.apis[use.api]?.owner ?? use.api;\n levels.set(`external__${owner}`, use.level);\n }\n const bands = new Map<number, string[]>();\n for (const node of model.nodes) {\n const level = levels.get(node.id)!;\n if (!bands.has(level)) bands.set(level, []);\n bands.get(level)!.push(node.id);\n }\n return [...bands.entries()]\n .sort((a, b) => b[0] - a[0])\n .map((entry) => new LevelBand(entry[0], entry[1]));\n }\n\n /** Context keeps its shape/kind palette; Impact never paints a queue as a source owner. */\n contextModes(model: GraphRenderModel): void {\n for (let index = 0; index < model.nodes.length; index++) {\n const node = model.nodes[index];\n if (node.tags !== null) continue;\n const colors = node.products.map((product) => this.palette.colorOf(product).color);\n const fill =\n colors.length === 0\n ? PRODUCT_NONE_FILL\n : this.palette.sharedByAll(node.products)\n ? PRODUCT_SHARED_FILL\n : colors.join(':');\n // Graphviz cylinders cannot stripe; a gradient preserves recognizable kind geometry.\n const recolored = node.dot.replace(/fillcolor=\"[^\"]*\"/, `fillcolor=\"${fill}\"`);\n const product =\n colors.length === 0\n ? recolored.replace(/style=\"[^\"]*\"/, 'style=\"filled,dashed\"')\n : recolored;\n const modes = new NodeModeDots(\n node.dot,\n node.dot,\n node.dot,\n node.dot,\n node.dot,\n node.dot,\n product,\n );\n model.nodes[index] = new RenderNode(\n node.id,\n node.dot,\n modes,\n null,\n node.products,\n node.queueKeys,\n );\n }\n }\n}\n"]}
|
|
@@ -38,8 +38,8 @@ const graph_level_bands_1 = require("./graph-level-bands");
|
|
|
38
38
|
*
|
|
39
39
|
* The same is true in the other direction for endpoints nothing in-repo CALLS: a
|
|
40
40
|
* `cron` method hangs off a clock and an `external` method off a box naming the
|
|
41
|
-
* CALLER that posts to it (`twilio`),
|
|
42
|
-
*
|
|
41
|
+
* CALLER that posts to it (`twilio`), separately from an outbound sink for the same
|
|
42
|
+
* vendor, so callers can sit on the top row and sinks on L0. Those are the
|
|
43
43
|
* entry points that wake a service up at 3am, and a graph built only from in-repo
|
|
44
44
|
* callers cannot show them at all.
|
|
45
45
|
*
|
|
@@ -57,6 +57,7 @@ const dot_syntax_1 = require("./dot-syntax");
|
|
|
57
57
|
const graph_render_model_1 = require("./graph-render-model");
|
|
58
58
|
const runtime_viewer_adapter_1 = require("./runtime-viewer-adapter");
|
|
59
59
|
const runtime_html_page_1 = require("./runtime-html-page");
|
|
60
|
+
const runtime_external_triggers_1 = require("./runtime-external-triggers");
|
|
60
61
|
const remote_service_node_id_1 = require("./remote-service-node-id");
|
|
61
62
|
const runtime_viz_theme_1 = require("./runtime-viz-theme");
|
|
62
63
|
/** Apis per line inside a node label — beyond this the box grows wider than it is readable. */
|
|
@@ -105,7 +106,7 @@ function labelList(entries) {
|
|
|
105
106
|
function edgeDot(edge) {
|
|
106
107
|
const from = serviceNodeId(edge.from);
|
|
107
108
|
const to = serviceNodeId(edge.to);
|
|
108
|
-
const viaRaw = edge.via.length >
|
|
109
|
+
const viaRaw = edge.via.length > 1
|
|
109
110
|
? `Uses (${edge.via.length}) ▾`
|
|
110
111
|
: edge.via.map((v) => getShortName(v)).join(', ');
|
|
111
112
|
const cut = edge.cutLegacyCycle === true;
|
|
@@ -119,8 +120,8 @@ class QueueEndpoints {
|
|
|
119
120
|
}
|
|
120
121
|
/**
|
|
121
122
|
* One drawn queue BOX: the queue keys (`Api.method`) it shows, and the endpoints they all share.
|
|
122
|
-
* Several keys land in one box only when they agree on
|
|
123
|
-
*
|
|
123
|
+
* Several keys land in one box only when they agree on contract, producers, consumers and
|
|
124
|
+
* their persisted level — see {@link queuesDot}.
|
|
124
125
|
*/
|
|
125
126
|
class QueueGroup {
|
|
126
127
|
members;
|
|
@@ -152,21 +153,22 @@ function queueLine(key, queue) {
|
|
|
152
153
|
}
|
|
153
154
|
/** The node statement + enqueue/deliver arrows for one queue box. Service identities remain raw; only emitted DOT is escaped. */
|
|
154
155
|
// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file
|
|
155
|
-
function queueBoxDot(id, body, producers, consumers, model,
|
|
156
|
+
function queueBoxDot(id, body, producers, consumers, model, queueKeys) {
|
|
156
157
|
// Record-mode label: the text must clear recordValue(), and QUEUE_LABEL_PREFIX supplies the
|
|
157
158
|
// empty leading field that draws the cylinder's end cap. Drop it and the node silently
|
|
158
159
|
// degrades to a plain box.
|
|
159
160
|
let dot = model.node(id, ` "${id}" [shape=${runtime_viz_theme_1.QUEUE_SHAPE}, style="filled", fillcolor="${runtime_viz_theme_1.QUEUE_FILL}", ` +
|
|
160
161
|
`class="${runtime_viz_theme_1.QUEUE_CLASS}", label="${runtime_viz_theme_1.QUEUE_LABEL_PREFIX}${body}"];\n`, [], queueKeys);
|
|
161
162
|
for (const producer of producers)
|
|
162
|
-
dot += model.edge(producer, id, ` "${serviceNodeId(producer)}" -> "${id}" [label="
|
|
163
|
+
dot += model.edge(producer, id, ` "${serviceNodeId(producer)}" -> "${id}" [label="enqueue", style=dashed];\n`);
|
|
163
164
|
for (const consumer of consumers)
|
|
164
165
|
dot += model.edge(id, consumer, ` "${id}" -> "${serviceNodeId(consumer)}" [style=dashed];\n`);
|
|
165
166
|
return dot;
|
|
166
167
|
}
|
|
167
168
|
/**
|
|
168
169
|
* Group the queued hops into boxes. The key is (contract, producer SET, consumer SET) — NOT the
|
|
169
|
-
* (from,to) pair of some edge.
|
|
170
|
+
* (from,to) pair of some edge. Persisted levels must also match: hiding a foreign producer
|
|
171
|
+
* can make a foreign queue look self-only without changing its generated level.
|
|
170
172
|
*
|
|
171
173
|
* A queue node converges EVERY producer and consumer of its method onto one box, so keying by a
|
|
172
174
|
* service PAIR would re-split a two-producer queue into two boxes and invent topology nobody wrote.
|
|
@@ -182,7 +184,12 @@ function groupQueues(byQueue, graph) {
|
|
|
182
184
|
const producers = [...ends.producers].sort();
|
|
183
185
|
const consumers = [...ends.consumers].sort();
|
|
184
186
|
const api = graph.queues[key]?.api ?? key.split('.')[0];
|
|
185
|
-
const mergeKey = [
|
|
187
|
+
const mergeKey = [
|
|
188
|
+
api,
|
|
189
|
+
graph.queues[key].level,
|
|
190
|
+
producers.join(','),
|
|
191
|
+
consumers.join(','),
|
|
192
|
+
].join(PAIR_SEP);
|
|
186
193
|
const existing = groups.get(mergeKey);
|
|
187
194
|
if (existing === undefined)
|
|
188
195
|
groups.set(mergeKey, new QueueGroup([key], producers, consumers));
|
|
@@ -225,13 +232,13 @@ function queuesDot(graph, hidden, model) {
|
|
|
225
232
|
}
|
|
226
233
|
for (const api of [...byContract.keys()].sort()) {
|
|
227
234
|
const ends = byContract.get(api);
|
|
228
|
-
dot += queueBoxDot(`queue__${dotId(api)}`, `${(0, dot_syntax_1.recordValue)(getShortName(api))} queue`, [...ends.producers].sort(), [...ends.consumers].sort(), model,
|
|
235
|
+
dot += queueBoxDot(`queue__${dotId(api)}`, `${(0, dot_syntax_1.recordValue)(getShortName(api))} queue`, [...ends.producers].sort(), [...ends.consumers].sort(), model, [`queue:${api}`]);
|
|
229
236
|
}
|
|
230
237
|
for (const group of groupQueues(byQueue, graph)) {
|
|
231
238
|
const body = group.members
|
|
232
239
|
.map((key) => queueLine(key, graph.queues[key]))
|
|
233
240
|
.join('\\n');
|
|
234
|
-
dot += queueBoxDot(`queue__${dotId(group.members[0])}`, body, group.producers, group.consumers, model,
|
|
241
|
+
dot += queueBoxDot(`queue__${dotId(group.members[0])}`, body, group.producers, group.consumers, model, group.members);
|
|
235
242
|
}
|
|
236
243
|
return dot;
|
|
237
244
|
}
|
|
@@ -245,9 +252,8 @@ function dotId(raw) {
|
|
|
245
252
|
* parenthesised subtitle saying which way it faces.
|
|
246
253
|
*
|
|
247
254
|
* Shared by the OUTBOUND systems ({@link externalSystemsDot}) and the INBOUND callers
|
|
248
|
-
* ({@link triggerDot}) on purpose.
|
|
249
|
-
*
|
|
250
|
-
* copy-pasted attribute string would have let the two drift until they stopped being one box.
|
|
255
|
+
* ({@link triggerDot}) on purpose. Callers use a separate identity from outbound sinks
|
|
256
|
+
* because their generated levels differ, while sharing the same shape and color palette.
|
|
251
257
|
*/
|
|
252
258
|
// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file
|
|
253
259
|
function externalSystemNodeDot(identity, kind, label, subtitle, model) {
|
|
@@ -278,47 +284,42 @@ function externalSystemNodeDot(identity, kind, label, subtitle, model) {
|
|
|
278
284
|
* exists to convey — which vendor is posting to us — appeared nowhere. The contract keeps its place
|
|
279
285
|
* on the EDGE label, where `WhatsAppApi.inbound` reads as "…posts to this method".
|
|
280
286
|
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
* methods is one box with three arrows, and a vendor this repo also CALLS is that same single box.
|
|
284
|
-
* `options` is passed in for the last of those — when that outbound half will be drawn, this half
|
|
285
|
-
* must not restate the node statement.
|
|
287
|
+
* Inbound identities are separate from outbound sinks so each has its own generated level.
|
|
288
|
+
* Several methods called by the same vendor still share one caller box.
|
|
286
289
|
*/
|
|
287
290
|
// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file
|
|
288
|
-
function triggerDot(graph, hidden,
|
|
291
|
+
function triggerDot(graph, hidden, model) {
|
|
289
292
|
const triggers = graph.triggers.filter((t) => !hidden.has(t.service));
|
|
290
293
|
if (triggers.length === 0)
|
|
291
294
|
return '';
|
|
292
|
-
// Identities externalSystemsDot is about to draw; skipped here so the node is stated ONCE.
|
|
293
|
-
const drawnOutbound = new Set(options.showExternalNodes ? Object.keys(graph.externalSystems ?? {}) : []);
|
|
294
295
|
const emitted = new Set();
|
|
295
296
|
let dot = '\n // Entry points nothing in this repo calls: a clock, or a system outside the repo.\n';
|
|
296
|
-
for (const trigger of triggers) {
|
|
297
|
+
for (const trigger of triggers.filter((trigger) => trigger.kind === 'cron')) {
|
|
297
298
|
const service = serviceNodeId(trigger.service);
|
|
298
299
|
const label = (0, dot_syntax_1.dotValue)(`${trigger.api}.${trigger.method}`);
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
300
|
+
const id = `cron__${dotId(`${trigger.api}_${trigger.method}`)}`;
|
|
301
|
+
const schedule = (0, dot_syntax_1.dotValue)(trigger.queueName ?? `${trigger.api}-${trigger.method}`);
|
|
302
|
+
dot +=
|
|
303
|
+
model.node(id, ` "${id}" [shape=circle, style="filled", fillcolor="${runtime_viz_theme_1.CRON_FILL}", ` +
|
|
304
|
+
`color="${runtime_viz_theme_1.CRON_BORDER}", label="⏰\\ncron"];\n`) +
|
|
305
|
+
model.edge(id, trigger.service, ` "${id}" -> "${service}" [label="${label}\\n${schedule}", color="${runtime_viz_theme_1.CRON_BORDER}"];\n`);
|
|
306
|
+
}
|
|
307
|
+
for (const group of new runtime_external_triggers_1.RuntimeExternalTriggers(triggers).groups) {
|
|
308
|
+
const trigger = group.triggers[0];
|
|
309
|
+
const service = serviceNodeId(group.to);
|
|
310
|
+
const label = (0, dot_syntax_1.dotValue)(group.label());
|
|
308
311
|
const caller = trigger.caller;
|
|
309
312
|
// Undeclared: only possible for a graph generated BEFORE the caller was required (generation
|
|
310
313
|
// now fails instead). DOTTED and question-marked, so "we were never told who this is" cannot
|
|
311
314
|
// be mistaken for a named vendor.
|
|
312
|
-
const id =
|
|
313
|
-
|
|
314
|
-
: `system__${dotId(caller.label)}`;
|
|
315
|
-
if (!emitted.has(id) && !(caller !== undefined && drawnOutbound.has(caller.label))) {
|
|
315
|
+
const id = group.from;
|
|
316
|
+
if (!emitted.has(id)) {
|
|
316
317
|
emitted.add(id);
|
|
317
318
|
dot +=
|
|
318
319
|
caller === undefined
|
|
319
320
|
? model.node(id, ` "${id}" [shape=box, style="dotted,filled", fillcolor="${runtime_viz_theme_1.EXTERNAL_FILL}", ` +
|
|
320
321
|
`color="${runtime_viz_theme_1.EXTERNAL_BORDER}", label="${(0, dot_syntax_1.dotValue)(trigger.api)}\\n? unknown caller"];\n`)
|
|
321
|
-
: externalSystemNodeDot(caller.label
|
|
322
|
+
: externalSystemNodeDot(`caller__${caller.label}`, caller.kind, caller.label, 'external caller', model);
|
|
322
323
|
}
|
|
323
324
|
dot += model.edge(id, trigger.service, ` "${id}" -> "${service}" [label="${label}", style=dashed, color="${runtime_viz_theme_1.EXTERNAL_BORDER}"];\n`);
|
|
324
325
|
}
|
|
@@ -349,7 +350,7 @@ function externalSystemsDot(graph, hidden, model) {
|
|
|
349
350
|
const system = systems[id];
|
|
350
351
|
const via = system.apis.length === 0
|
|
351
352
|
? ''
|
|
352
|
-
: ` [label="${system.apis.length >
|
|
353
|
+
: ` [label="${system.apis.length > 1 ? `Uses (${system.apis.length}) ▾` : labelList([...system.apis].sort())}"]`;
|
|
353
354
|
for (const service of [...system.usedBy].sort()) {
|
|
354
355
|
if (hidden.has(service))
|
|
355
356
|
continue;
|
|
@@ -364,8 +365,8 @@ function externalSystemsDot(graph, hidden, model) {
|
|
|
364
365
|
* computes and which was, until now, only ever printed as a warning.
|
|
365
366
|
*
|
|
366
367
|
* Grouped by the api-lib that owns the contracts, so a service reaching three firestore contracts
|
|
367
|
-
* draws ONE `lib-firestore (external)` box rather than three.
|
|
368
|
-
*
|
|
368
|
+
* draws ONE `lib-firestore (external)` box rather than three. The unresolved uses carry their
|
|
369
|
+
* terminal level in the generated graph; grouping changes presentation only.
|
|
369
370
|
*
|
|
370
371
|
* A contract carrying an `@externalSystem` declaration is skipped here — {@link externalSystemsDot}
|
|
371
372
|
* has already drawn it with a real shape, and rendering it in both places would double the node.
|
|
@@ -403,7 +404,7 @@ function externalDot(graph, hidden, model) {
|
|
|
403
404
|
const service = parts[0];
|
|
404
405
|
const external = parts[1];
|
|
405
406
|
const apis = apisByPair.get(key).sort();
|
|
406
|
-
const via = apis.length >
|
|
407
|
+
const via = apis.length > 1 ? `Uses (${apis.length}) ▾` : labelList(apis);
|
|
407
408
|
// SOLID: this is a synchronous call that returns a value. Dashed is reserved for events, and
|
|
408
409
|
// "outside the repo" is already said by the node's dashed border.
|
|
409
410
|
dot += model.edge(service, `external__${external}`, ` "${serviceNodeId(service)}" -> "external__${(0, dot_syntax_1.dotValue)(external)}" ` +
|
|
@@ -442,7 +443,7 @@ function remoteServicesDot(graph, hidden, model) {
|
|
|
442
443
|
for (const call of remotes[name].calledBy) {
|
|
443
444
|
if (hidden.has(call.from))
|
|
444
445
|
continue;
|
|
445
|
-
const via = call.via.length >
|
|
446
|
+
const via = call.via.length > 1 ? `Uses (${call.via.length}) ▾` : labelList(call.via);
|
|
446
447
|
const label = call.type === 'pubsub' ? `${via}\\nenqueue` : via;
|
|
447
448
|
const style = call.type === 'pubsub' ? ', style=dashed' : '';
|
|
448
449
|
dot += model.edge(call.from, id, ` "${serviceNodeId(call.from)}" -> "${id}" [label="${label}", color="${runtime_viz_theme_1.REMOTE_SERVICE_BORDER}"${style}];\n`);
|
|
@@ -475,8 +476,6 @@ function generateRuntimeRenderModel(graph, title = 'WebPieces Runtime Architectu
|
|
|
475
476
|
const svc = graph.services[name];
|
|
476
477
|
dot += adapter.service(model, name, svc);
|
|
477
478
|
}
|
|
478
|
-
model.bands = adapter.levelBands(model);
|
|
479
|
-
dot += new graph_level_bands_1.LevelBandLayout().dot(model.bands);
|
|
480
479
|
dot += '\n';
|
|
481
480
|
for (const edge of graph.runtimeEdges) {
|
|
482
481
|
if (hidden.has(edge.from) || hidden.has(edge.to))
|
|
@@ -488,11 +487,13 @@ function generateRuntimeRenderModel(graph, title = 'WebPieces Runtime Architectu
|
|
|
488
487
|
}
|
|
489
488
|
dot += queuesDot(graph, hidden, model);
|
|
490
489
|
dot += remoteServicesDot(graph, hidden, model);
|
|
491
|
-
dot += triggerDot(graph, hidden,
|
|
490
|
+
dot += triggerDot(graph, hidden, model);
|
|
492
491
|
if (options.showExternalNodes) {
|
|
493
492
|
dot += externalSystemsDot(graph, hidden, model);
|
|
494
493
|
dot += externalDot(graph, hidden, model);
|
|
495
494
|
}
|
|
495
|
+
model.bands = adapter.levelBands(model);
|
|
496
|
+
dot += new graph_level_bands_1.LevelBandLayout().dot(model.bands);
|
|
496
497
|
const footerStart = dot.length;
|
|
497
498
|
dot += '\n labelloc="t";\n';
|
|
498
499
|
dot += ` label="${(0, dot_syntax_1.dotValue)(title)}\\n(from architecture/runtime-dependencies.json)";\n`;
|