@webpieces/nx-webpieces-rules 0.4.911 → 0.4.913
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 +11 -0
- package/executors.json +5 -0
- package/package.json +7 -7
- package/src/executors/help/executor.js +2 -0
- package/src/executors/help/executor.js.map +1 -1
- package/src/executors/visualize-runtime/executor.d.ts +3 -1
- package/src/executors/visualize-runtime/executor.js +9 -3
- package/src/executors/visualize-runtime/executor.js.map +1 -1
- package/src/executors/visualize-runtime-goal/executor.d.ts +27 -0
- package/src/executors/visualize-runtime-goal/executor.js +98 -0
- package/src/executors/visualize-runtime-goal/executor.js.map +1 -0
- package/src/executors/visualize-runtime-goal/schema.json +13 -0
- package/src/index.d.ts +1 -1
- package/src/index.js +3 -1
- package/src/index.js.map +1 -1
- package/src/lib/graph-color-modes.d.ts +15 -1
- package/src/lib/graph-color-modes.js +19 -1
- package/src/lib/graph-color-modes.js.map +1 -1
- package/src/lib/graph-filter.client.d.ts +9 -2
- package/src/lib/graph-filter.client.js +15 -0
- package/src/lib/graph-filter.client.js.map +1 -1
- package/src/lib/graph-legend.js +1 -1
- package/src/lib/graph-legend.js.map +1 -1
- package/src/lib/graph-mode-state.client.d.ts +9 -2
- package/src/lib/graph-mode-state.client.js +17 -4
- package/src/lib/graph-mode-state.client.js.map +1 -1
- package/src/lib/graph-page-shell.d.ts +11 -4
- package/src/lib/graph-page-shell.js +11 -9
- package/src/lib/graph-page-shell.js.map +1 -1
- package/src/lib/graph-products.d.ts +22 -3
- package/src/lib/graph-products.js +63 -13
- package/src/lib/graph-products.js.map +1 -1
- package/src/lib/graph-render-model.d.ts +41 -0
- package/src/lib/graph-render-model.js +45 -1
- package/src/lib/graph-render-model.js.map +1 -1
- package/src/lib/graph-visualizer.client.d.ts +7 -1
- package/src/lib/graph-visualizer.client.js +37 -4
- package/src/lib/graph-visualizer.client.js.map +1 -1
- package/src/lib/graph-visualizer.js +1 -1
- package/src/lib/graph-visualizer.js.map +1 -1
- package/src/lib/runtime-goal-comparison.d.ts +55 -0
- package/src/lib/runtime-goal-comparison.js +209 -0
- package/src/lib/runtime-goal-comparison.js.map +1 -0
- package/src/lib/runtime-goal-details.d.ts +17 -0
- package/src/lib/runtime-goal-details.js +118 -0
- package/src/lib/runtime-goal-details.js.map +1 -0
- package/src/lib/runtime-goal-file.d.ts +72 -0
- package/src/lib/runtime-goal-file.js +201 -0
- package/src/lib/runtime-goal-file.js.map +1 -0
- package/src/lib/runtime-goal-styles.d.ts +10 -0
- package/src/lib/runtime-goal-styles.js +25 -0
- package/src/lib/runtime-goal-styles.js.map +1 -0
- package/src/lib/runtime-goal-validator.d.ts +39 -0
- package/src/lib/runtime-goal-validator.js +236 -0
- package/src/lib/runtime-goal-validator.js.map +1 -0
- package/src/lib/runtime-goal-view.d.ts +22 -0
- package/src/lib/runtime-goal-view.js +64 -0
- package/src/lib/runtime-goal-view.js.map +1 -0
- package/src/lib/runtime-graph-diff.d.ts +107 -0
- package/src/lib/runtime-graph-diff.js +346 -0
- package/src/lib/runtime-graph-diff.js.map +1 -0
- package/src/lib/runtime-html-page.d.ts +8 -0
- package/src/lib/runtime-html-page.js +39 -6
- package/src/lib/runtime-html-page.js.map +1 -1
- package/src/lib/runtime-layout.d.ts +2 -0
- package/src/lib/runtime-layout.js +4 -0
- package/src/lib/runtime-layout.js.map +1 -1
- package/src/lib/runtime-union-graph.d.ts +20 -0
- package/src/lib/runtime-union-graph.js +94 -0
- package/src/lib/runtime-union-graph.js.map +1 -0
- package/src/lib/runtime-viewer-adapter.js +7 -5
- package/src/lib/runtime-viewer-adapter.js.map +1 -1
- package/src/lib/runtime-visualizer.client.js +7 -3
- package/src/lib/runtime-visualizer.client.js.map +1 -1
- package/src/lib/runtime-visualizer.d.ts +8 -3
- package/src/lib/runtime-visualizer.js +18 -10
- package/src/lib/runtime-visualizer.js.map +1 -1
- package/src/lib/runtime-viz-options.d.ts +28 -1
- package/src/lib/runtime-viz-options.js +36 -2
- package/src/lib/runtime-viz-options.js.map +1 -1
- package/src/plugin.js +2 -2
- package/src/plugin.js.map +1 -1
- package/src/runtime-targets.d.ts +6 -0
- package/src/runtime-targets.js +17 -0
- package/src/runtime-targets.js.map +1 -1
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RuntimeGoalValidator = void 0;
|
|
4
|
+
/** One queue flow and the goal path segment that names it. */
|
|
5
|
+
class NamedFlow {
|
|
6
|
+
where;
|
|
7
|
+
flow;
|
|
8
|
+
constructor(where, flow) {
|
|
9
|
+
this.where = where;
|
|
10
|
+
this.flow = flow;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Shape and internal-reference checks for a HAND-AUTHORED runtime goal (#1239).
|
|
15
|
+
*
|
|
16
|
+
* The generated current graph is validated by regenerating it from source; a goal has no source to
|
|
17
|
+
* regenerate from, so every problem is named against the goal file itself, with the element that is
|
|
18
|
+
* wrong and the edit that fixes it. Problems are collected, never thrown one at a time, so one run
|
|
19
|
+
* names every broken reference.
|
|
20
|
+
*
|
|
21
|
+
* Two kinds of problem, because they have different cures:
|
|
22
|
+
* - STRUCTURAL (shape, dangling references): only an edit of the goal file fixes them.
|
|
23
|
+
* - DERIVED (a missing or invalid level, a missing contract queue, maxLevel): the explicit
|
|
24
|
+
* `--prepare` step computes them from the relationships, so it must accept a goal that has them.
|
|
25
|
+
* Facts that are present but merely disagree with the relationships are drift, reported by
|
|
26
|
+
* RuntimeGoalFile.drift().
|
|
27
|
+
*/
|
|
28
|
+
class RuntimeGoalValidator {
|
|
29
|
+
goalPath;
|
|
30
|
+
constructor(goalPath) {
|
|
31
|
+
this.goalPath = goalPath;
|
|
32
|
+
}
|
|
33
|
+
/** Problems only an edit of the goal file can fix; empty when `--prepare` can run. */
|
|
34
|
+
// webpieces-disable no-any-unknown -- hand-authored JSON is untrusted until its shape has been checked
|
|
35
|
+
structural(graph) {
|
|
36
|
+
if (!this.isMap(graph))
|
|
37
|
+
return [`${this.goalPath} must contain a JSON object (a RuntimeGraph).`];
|
|
38
|
+
const typed = graph;
|
|
39
|
+
const shape = this.shape(typed);
|
|
40
|
+
if (shape.length > 0)
|
|
41
|
+
return shape;
|
|
42
|
+
return this.references(typed);
|
|
43
|
+
}
|
|
44
|
+
/** Derived facts `--prepare` computes; call only on a structurally valid goal. */
|
|
45
|
+
derived(graph) {
|
|
46
|
+
const problems = [];
|
|
47
|
+
if (!Number.isInteger(graph.maxLevel))
|
|
48
|
+
problems.push(`${this.goalPath}: "maxLevel" must be an integer.`);
|
|
49
|
+
if (graph.contractQueues === undefined)
|
|
50
|
+
problems.push(`${this.goalPath}: "contractQueues" is missing.`);
|
|
51
|
+
return [...problems, ...this.levels(graph), ...this.contractQueues(graph)];
|
|
52
|
+
}
|
|
53
|
+
shape(graph) {
|
|
54
|
+
const problems = [];
|
|
55
|
+
for (const key of ['services', 'apis', 'queues']) {
|
|
56
|
+
if (!this.isMap(graph[key]))
|
|
57
|
+
problems.push(`${this.goalPath}: "${key}" must be an object keyed by identity.`);
|
|
58
|
+
}
|
|
59
|
+
for (const key of ['runtimeEdges', 'unresolvedUses', 'triggers']) {
|
|
60
|
+
if (!Array.isArray(graph[key]))
|
|
61
|
+
problems.push(`${this.goalPath}: "${key}" must be an array.`);
|
|
62
|
+
}
|
|
63
|
+
for (const key of ['externalSystems', 'remoteServices', 'contractQueues']) {
|
|
64
|
+
if (graph[key] !== undefined && !this.isMap(graph[key]))
|
|
65
|
+
problems.push(`${this.goalPath}: "${key}" must be an object when present.`);
|
|
66
|
+
}
|
|
67
|
+
return problems;
|
|
68
|
+
}
|
|
69
|
+
/** The renderer places every node by its saved level, so each needs one. */
|
|
70
|
+
levels(graph) {
|
|
71
|
+
const problems = [];
|
|
72
|
+
const check = (where, node) => {
|
|
73
|
+
if (!this.isMap(node) || !Number.isInteger(node.level) || node.level < 0 || typeof node.levelBecause !== 'string')
|
|
74
|
+
problems.push(`${this.goalPath}: ${where} needs an integer "level" >= 0 and a string "levelBecause".`);
|
|
75
|
+
};
|
|
76
|
+
for (const [name, service] of Object.entries(graph.services))
|
|
77
|
+
check(`services["${name}"]`, service);
|
|
78
|
+
for (const [key, queue] of Object.entries(graph.queues))
|
|
79
|
+
check(`queues["${key}"]`, queue);
|
|
80
|
+
for (const [api, queue] of Object.entries(graph.contractQueues ?? {}))
|
|
81
|
+
check(`contractQueues["${api}"]`, queue);
|
|
82
|
+
graph.triggers.forEach((trigger, index) => check(`triggers[${index}]`, trigger));
|
|
83
|
+
graph.unresolvedUses.forEach((use, index) => check(`unresolvedUses[${index}]`, use));
|
|
84
|
+
for (const [id, system] of Object.entries(graph.externalSystems ?? {}))
|
|
85
|
+
check(`externalSystems["${id}"]`, system);
|
|
86
|
+
for (const [name, remote] of Object.entries(graph.remoteServices ?? {}))
|
|
87
|
+
check(`remoteServices["${name}"]`, remote);
|
|
88
|
+
return problems;
|
|
89
|
+
}
|
|
90
|
+
references(graph) {
|
|
91
|
+
return [
|
|
92
|
+
...this.services(graph),
|
|
93
|
+
...this.edges(graph),
|
|
94
|
+
...this.queues(graph),
|
|
95
|
+
...this.triggers(graph),
|
|
96
|
+
...this.externals(graph),
|
|
97
|
+
...this.remotes(graph),
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
services(graph) {
|
|
101
|
+
const problems = [];
|
|
102
|
+
for (const [name, service] of Object.entries(graph.services)) {
|
|
103
|
+
for (const key of ['implements', 'uses', 'dependsOn']) {
|
|
104
|
+
if (!this.strings(service[key]))
|
|
105
|
+
problems.push(`${this.goalPath}: services["${name}"].${key} must be an array of strings (use [] for none).`);
|
|
106
|
+
}
|
|
107
|
+
if (service.products !== undefined && !this.strings(service.products))
|
|
108
|
+
problems.push(`${this.goalPath}: services["${name}"].products must be an array of strings when present.`);
|
|
109
|
+
}
|
|
110
|
+
return problems;
|
|
111
|
+
}
|
|
112
|
+
edges(graph) {
|
|
113
|
+
const problems = [];
|
|
114
|
+
graph.runtimeEdges.forEach((edge, index) => problems.push(...this.edge(graph, edge, index)));
|
|
115
|
+
return problems;
|
|
116
|
+
}
|
|
117
|
+
edge(graph, edge, index) {
|
|
118
|
+
const where = `${this.goalPath}: runtimeEdges[${index}] (${edge?.from} -> ${edge?.to})`;
|
|
119
|
+
if (!this.isMap(edge) || !this.strings(edge.via) || edge.via.length === 0)
|
|
120
|
+
return [`${where} needs "from", "to" and a non-empty "via" contract list.`];
|
|
121
|
+
const problems = [];
|
|
122
|
+
for (const end of [edge.from, edge.to]) {
|
|
123
|
+
if (graph.services[end] === undefined)
|
|
124
|
+
problems.push(`${where} names '${end}', which is not in "services". Add services["${end}"] or remove the edge.`);
|
|
125
|
+
}
|
|
126
|
+
if (edge.type !== undefined && edge.type !== 'rpc' && edge.type !== 'pubsub')
|
|
127
|
+
problems.push(`${where} has type '${edge.type}'; use "rpc" or "pubsub".`);
|
|
128
|
+
if (edge.type === 'pubsub' && edge.queue !== undefined && graph.queues[edge.queue] === undefined)
|
|
129
|
+
problems.push(`${where} flows through queue '${edge.queue}', which is not in "queues". Add queues["${edge.queue}"] or drop "queue" for a contract-level edge.`);
|
|
130
|
+
return problems;
|
|
131
|
+
}
|
|
132
|
+
/** Every contract-level pubsub edge needs its contract queue, which `--prepare` derives. */
|
|
133
|
+
contractQueues(graph) {
|
|
134
|
+
const problems = [];
|
|
135
|
+
graph.runtimeEdges.forEach((edge, index) => {
|
|
136
|
+
if (edge.type !== 'pubsub' || edge.queue !== undefined)
|
|
137
|
+
return;
|
|
138
|
+
for (const api of edge.via) {
|
|
139
|
+
if (graph.contractQueues?.[api] === undefined)
|
|
140
|
+
problems.push(`${this.goalPath}: runtimeEdges[${index}] (${edge.from} -> ${edge.to}) is a contract-level pubsub edge over ${api}, but contractQueues["${api}"] is missing.`);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return problems;
|
|
144
|
+
}
|
|
145
|
+
queues(graph) {
|
|
146
|
+
const problems = [];
|
|
147
|
+
const flows = [];
|
|
148
|
+
for (const key of Object.keys(graph.queues))
|
|
149
|
+
flows.push(new NamedFlow(`queues["${key}"]`, graph.queues[key]));
|
|
150
|
+
const contracts = graph.contractQueues ?? {};
|
|
151
|
+
for (const api of Object.keys(contracts))
|
|
152
|
+
flows.push(new NamedFlow(`contractQueues["${api}"]`, contracts[api]));
|
|
153
|
+
for (const named of flows) {
|
|
154
|
+
const where = named.where;
|
|
155
|
+
const flow = named.flow;
|
|
156
|
+
for (const key of ['producedBy', 'consumedBy']) {
|
|
157
|
+
if (!this.strings(flow[key])) {
|
|
158
|
+
problems.push(`${this.goalPath}: ${where}.${key} must be an array of service names.`);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
for (const name of flow[key]) {
|
|
162
|
+
if (graph.services[name] === undefined)
|
|
163
|
+
problems.push(`${this.goalPath}: ${where}.${key} names '${name}', which is not in "services".`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const [key, queue] of Object.entries(graph.queues)) {
|
|
168
|
+
if (typeof queue.api !== 'string' || typeof queue.method !== 'string' || typeof queue.queueName !== 'string')
|
|
169
|
+
problems.push(`${this.goalPath}: queues["${key}"] needs string "api", "method" and "queueName".`);
|
|
170
|
+
}
|
|
171
|
+
return problems;
|
|
172
|
+
}
|
|
173
|
+
triggers(graph) {
|
|
174
|
+
const problems = [];
|
|
175
|
+
graph.triggers.forEach((trigger, index) => {
|
|
176
|
+
const where = `${this.goalPath}: triggers[${index}]`;
|
|
177
|
+
if (trigger.kind !== 'cron' && trigger.kind !== 'external')
|
|
178
|
+
problems.push(`${where} has kind '${trigger.kind}'; use "cron" or "external".`);
|
|
179
|
+
if (typeof trigger.api !== 'string' || typeof trigger.method !== 'string')
|
|
180
|
+
problems.push(`${where} needs string "api" and "method".`);
|
|
181
|
+
if (graph.services[trigger.service] === undefined)
|
|
182
|
+
problems.push(`${where} targets '${trigger.service}', which is not in "services".`);
|
|
183
|
+
});
|
|
184
|
+
graph.unresolvedUses.forEach((use, index) => {
|
|
185
|
+
if (graph.services[use.service] === undefined)
|
|
186
|
+
problems.push(`${this.goalPath}: unresolvedUses[${index}] names '${use.service}', which is not in "services".`);
|
|
187
|
+
if (typeof use.api !== 'string')
|
|
188
|
+
problems.push(`${this.goalPath}: unresolvedUses[${index}] needs a string "api".`);
|
|
189
|
+
});
|
|
190
|
+
return problems;
|
|
191
|
+
}
|
|
192
|
+
externals(graph) {
|
|
193
|
+
const problems = [];
|
|
194
|
+
for (const [id, system] of Object.entries(graph.externalSystems ?? {})) {
|
|
195
|
+
const where = `${this.goalPath}: externalSystems["${id}"]`;
|
|
196
|
+
if (typeof system.kind !== 'string' || typeof system.label !== 'string' || !this.strings(system.apis))
|
|
197
|
+
problems.push(`${where} needs string "kind" and "label" and an "apis" string array.`);
|
|
198
|
+
if (!this.strings(system.usedBy)) {
|
|
199
|
+
problems.push(`${where}.usedBy must be an array of service names.`);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
for (const name of system.usedBy) {
|
|
203
|
+
if (graph.services[name] === undefined)
|
|
204
|
+
problems.push(`${where}.usedBy names '${name}', which is not in "services".`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return problems;
|
|
208
|
+
}
|
|
209
|
+
remotes(graph) {
|
|
210
|
+
const problems = [];
|
|
211
|
+
for (const [name, remote] of Object.entries(graph.remoteServices ?? {})) {
|
|
212
|
+
const where = `${this.goalPath}: remoteServices["${name}"]`;
|
|
213
|
+
if (!this.strings(remote.implements) || !Array.isArray(remote.calledBy)) {
|
|
214
|
+
problems.push(`${where} needs an "implements" string array and a "calledBy" array.`);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
remote.calledBy.forEach((call, index) => {
|
|
218
|
+
if (!this.isMap(call) || !this.strings(call.via) || (call.type !== 'rpc' && call.type !== 'pubsub'))
|
|
219
|
+
problems.push(`${where}.calledBy[${index}] needs "from", a "via" string array and type "rpc" or "pubsub".`);
|
|
220
|
+
else if (graph.services[call.from] === undefined)
|
|
221
|
+
problems.push(`${where}.calledBy[${index}] names '${call.from}', which is not in "services".`);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return problems;
|
|
225
|
+
}
|
|
226
|
+
// webpieces-disable no-any-unknown -- hand-authored JSON is untrusted until its shape has been checked
|
|
227
|
+
strings(value) {
|
|
228
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
|
|
229
|
+
}
|
|
230
|
+
// webpieces-disable no-any-unknown -- hand-authored JSON is untrusted until its shape has been checked
|
|
231
|
+
isMap(value) {
|
|
232
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
exports.RuntimeGoalValidator = RuntimeGoalValidator;
|
|
236
|
+
//# sourceMappingURL=runtime-goal-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-goal-validator.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-goal-validator.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n RuntimeEdge,\n RuntimeGraph,\n RuntimeLevel,\n RuntimeQueueFlow,\n RuntimeRemoteCall,\n RuntimeTrigger,\n RuntimeUnresolved,\n} from './runtime-graph-model';\n\n/** One queue flow and the goal path segment that names it. */\nclass NamedFlow {\n constructor(\n public readonly where: string,\n public readonly flow: RuntimeQueueFlow,\n ) {}\n}\n\n/**\n * Shape and internal-reference checks for a HAND-AUTHORED runtime goal (#1239).\n *\n * The generated current graph is validated by regenerating it from source; a goal has no source to\n * regenerate from, so every problem is named against the goal file itself, with the element that is\n * wrong and the edit that fixes it. Problems are collected, never thrown one at a time, so one run\n * names every broken reference.\n *\n * Two kinds of problem, because they have different cures:\n * - STRUCTURAL (shape, dangling references): only an edit of the goal file fixes them.\n * - DERIVED (a missing or invalid level, a missing contract queue, maxLevel): the explicit\n * `--prepare` step computes them from the relationships, so it must accept a goal that has them.\n * Facts that are present but merely disagree with the relationships are drift, reported by\n * RuntimeGoalFile.drift().\n */\nexport class RuntimeGoalValidator {\n constructor(private readonly goalPath: string) {}\n\n /** Problems only an edit of the goal file can fix; empty when `--prepare` can run. */\n // webpieces-disable no-any-unknown -- hand-authored JSON is untrusted until its shape has been checked\n structural(graph: unknown): string[] {\n if (!this.isMap(graph)) return [`${this.goalPath} must contain a JSON object (a RuntimeGraph).`];\n const typed = graph as RuntimeGraph;\n const shape = this.shape(typed);\n if (shape.length > 0) return shape;\n return this.references(typed);\n }\n\n /** Derived facts `--prepare` computes; call only on a structurally valid goal. */\n derived(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n if (!Number.isInteger(graph.maxLevel)) problems.push(`${this.goalPath}: \"maxLevel\" must be an integer.`);\n if (graph.contractQueues === undefined) problems.push(`${this.goalPath}: \"contractQueues\" is missing.`);\n return [...problems, ...this.levels(graph), ...this.contractQueues(graph)];\n }\n\n private shape(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n for (const key of ['services', 'apis', 'queues'] as const) {\n if (!this.isMap(graph[key])) problems.push(`${this.goalPath}: \"${key}\" must be an object keyed by identity.`);\n }\n for (const key of ['runtimeEdges', 'unresolvedUses', 'triggers'] as const) {\n if (!Array.isArray(graph[key])) problems.push(`${this.goalPath}: \"${key}\" must be an array.`);\n }\n for (const key of ['externalSystems', 'remoteServices', 'contractQueues'] as const) {\n if (graph[key] !== undefined && !this.isMap(graph[key]))\n problems.push(`${this.goalPath}: \"${key}\" must be an object when present.`);\n }\n return problems;\n }\n\n /** The renderer places every node by its saved level, so each needs one. */\n private levels(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n const check = (where: string, node: RuntimeLevel): void => {\n if (!this.isMap(node) || !Number.isInteger(node.level) || node.level < 0 || typeof node.levelBecause !== 'string')\n problems.push(`${this.goalPath}: ${where} needs an integer \"level\" >= 0 and a string \"levelBecause\".`);\n };\n for (const [name, service] of Object.entries(graph.services)) check(`services[\"${name}\"]`, service);\n for (const [key, queue] of Object.entries(graph.queues)) check(`queues[\"${key}\"]`, queue);\n for (const [api, queue] of Object.entries(graph.contractQueues ?? {})) check(`contractQueues[\"${api}\"]`, queue);\n graph.triggers.forEach((trigger: RuntimeTrigger, index: number) => check(`triggers[${index}]`, trigger));\n graph.unresolvedUses.forEach((use: RuntimeUnresolved, index: number) => check(`unresolvedUses[${index}]`, use));\n for (const [id, system] of Object.entries(graph.externalSystems ?? {})) check(`externalSystems[\"${id}\"]`, system);\n for (const [name, remote] of Object.entries(graph.remoteServices ?? {})) check(`remoteServices[\"${name}\"]`, remote);\n return problems;\n }\n\n private references(graph: RuntimeGraph): string[] {\n return [\n ...this.services(graph),\n ...this.edges(graph),\n ...this.queues(graph),\n ...this.triggers(graph),\n ...this.externals(graph),\n ...this.remotes(graph),\n ];\n }\n\n private services(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n for (const [name, service] of Object.entries(graph.services)) {\n for (const key of ['implements', 'uses', 'dependsOn'] as const) {\n if (!this.strings(service[key]))\n problems.push(`${this.goalPath}: services[\"${name}\"].${key} must be an array of strings (use [] for none).`);\n }\n if (service.products !== undefined && !this.strings(service.products))\n problems.push(`${this.goalPath}: services[\"${name}\"].products must be an array of strings when present.`);\n }\n return problems;\n }\n\n private edges(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n graph.runtimeEdges.forEach((edge: RuntimeEdge, index: number) => problems.push(...this.edge(graph, edge, index)));\n return problems;\n }\n\n private edge(graph: RuntimeGraph, edge: RuntimeEdge, index: number): string[] {\n const where = `${this.goalPath}: runtimeEdges[${index}] (${edge?.from} -> ${edge?.to})`;\n if (!this.isMap(edge) || !this.strings(edge.via) || edge.via.length === 0)\n return [`${where} needs \"from\", \"to\" and a non-empty \"via\" contract list.`];\n const problems: string[] = [];\n for (const end of [edge.from, edge.to]) {\n if (graph.services[end] === undefined)\n problems.push(`${where} names '${end}', which is not in \"services\". Add services[\"${end}\"] or remove the edge.`);\n }\n if (edge.type !== undefined && edge.type !== 'rpc' && edge.type !== 'pubsub')\n problems.push(`${where} has type '${edge.type}'; use \"rpc\" or \"pubsub\".`);\n if (edge.type === 'pubsub' && edge.queue !== undefined && graph.queues[edge.queue] === undefined)\n problems.push(`${where} flows through queue '${edge.queue}', which is not in \"queues\". Add queues[\"${edge.queue}\"] or drop \"queue\" for a contract-level edge.`);\n return problems;\n }\n\n /** Every contract-level pubsub edge needs its contract queue, which `--prepare` derives. */\n private contractQueues(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n graph.runtimeEdges.forEach((edge: RuntimeEdge, index: number) => {\n if (edge.type !== 'pubsub' || edge.queue !== undefined) return;\n for (const api of edge.via) {\n if (graph.contractQueues?.[api] === undefined)\n problems.push(`${this.goalPath}: runtimeEdges[${index}] (${edge.from} -> ${edge.to}) is a contract-level pubsub edge over ${api}, but contractQueues[\"${api}\"] is missing.`);\n }\n });\n return problems;\n }\n\n private queues(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n const flows: NamedFlow[] = [];\n for (const key of Object.keys(graph.queues)) flows.push(new NamedFlow(`queues[\"${key}\"]`, graph.queues[key]));\n const contracts = graph.contractQueues ?? {};\n for (const api of Object.keys(contracts)) flows.push(new NamedFlow(`contractQueues[\"${api}\"]`, contracts[api]));\n for (const named of flows) {\n const where = named.where;\n const flow = named.flow;\n for (const key of ['producedBy', 'consumedBy'] as const) {\n if (!this.strings(flow[key])) {\n problems.push(`${this.goalPath}: ${where}.${key} must be an array of service names.`);\n continue;\n }\n for (const name of flow[key]) {\n if (graph.services[name] === undefined)\n problems.push(`${this.goalPath}: ${where}.${key} names '${name}', which is not in \"services\".`);\n }\n }\n }\n for (const [key, queue] of Object.entries(graph.queues)) {\n if (typeof queue.api !== 'string' || typeof queue.method !== 'string' || typeof queue.queueName !== 'string')\n problems.push(`${this.goalPath}: queues[\"${key}\"] needs string \"api\", \"method\" and \"queueName\".`);\n }\n return problems;\n }\n\n private triggers(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n graph.triggers.forEach((trigger: RuntimeTrigger, index: number) => {\n const where = `${this.goalPath}: triggers[${index}]`;\n if (trigger.kind !== 'cron' && trigger.kind !== 'external')\n problems.push(`${where} has kind '${trigger.kind}'; use \"cron\" or \"external\".`);\n if (typeof trigger.api !== 'string' || typeof trigger.method !== 'string')\n problems.push(`${where} needs string \"api\" and \"method\".`);\n if (graph.services[trigger.service] === undefined)\n problems.push(`${where} targets '${trigger.service}', which is not in \"services\".`);\n });\n graph.unresolvedUses.forEach((use: RuntimeUnresolved, index: number) => {\n if (graph.services[use.service] === undefined)\n problems.push(`${this.goalPath}: unresolvedUses[${index}] names '${use.service}', which is not in \"services\".`);\n if (typeof use.api !== 'string') problems.push(`${this.goalPath}: unresolvedUses[${index}] needs a string \"api\".`);\n });\n return problems;\n }\n\n private externals(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n for (const [id, system] of Object.entries(graph.externalSystems ?? {})) {\n const where = `${this.goalPath}: externalSystems[\"${id}\"]`;\n if (typeof system.kind !== 'string' || typeof system.label !== 'string' || !this.strings(system.apis))\n problems.push(`${where} needs string \"kind\" and \"label\" and an \"apis\" string array.`);\n if (!this.strings(system.usedBy)) {\n problems.push(`${where}.usedBy must be an array of service names.`);\n continue;\n }\n for (const name of system.usedBy) {\n if (graph.services[name] === undefined) problems.push(`${where}.usedBy names '${name}', which is not in \"services\".`);\n }\n }\n return problems;\n }\n\n private remotes(graph: RuntimeGraph): string[] {\n const problems: string[] = [];\n for (const [name, remote] of Object.entries(graph.remoteServices ?? {})) {\n const where = `${this.goalPath}: remoteServices[\"${name}\"]`;\n if (!this.strings(remote.implements) || !Array.isArray(remote.calledBy)) {\n problems.push(`${where} needs an \"implements\" string array and a \"calledBy\" array.`);\n continue;\n }\n remote.calledBy.forEach((call: RuntimeRemoteCall, index: number) => {\n if (!this.isMap(call) || !this.strings(call.via) || (call.type !== 'rpc' && call.type !== 'pubsub'))\n problems.push(`${where}.calledBy[${index}] needs \"from\", a \"via\" string array and type \"rpc\" or \"pubsub\".`);\n else if (graph.services[call.from] === undefined)\n problems.push(`${where}.calledBy[${index}] names '${call.from}', which is not in \"services\".`);\n });\n }\n return problems;\n }\n\n // webpieces-disable no-any-unknown -- hand-authored JSON is untrusted until its shape has been checked\n private strings(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((entry: string | number | boolean | object | null) => typeof entry === 'string');\n }\n\n // webpieces-disable no-any-unknown -- hand-authored JSON is untrusted until its shape has been checked\n private isMap(value: unknown): boolean {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n }\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RuntimeGraph } from './runtime-graph-model';
|
|
2
|
+
import { SavedSnapshot } from './saved-snapshot';
|
|
3
|
+
import { RuntimeGoalFile } from './runtime-goal-file';
|
|
4
|
+
import { RuntimeComparisonView } from './graph-render-model';
|
|
5
|
+
import type { RuntimeVizOptions } from './runtime-viz-options';
|
|
6
|
+
/**
|
|
7
|
+
* Loads either saved runtime file for the OTHER page's comparison, turning a missing or unusable file
|
|
8
|
+
* into the reason Goal vs Current is unavailable — never into "no differences".
|
|
9
|
+
*/
|
|
10
|
+
export declare class RuntimeComparisonInputs {
|
|
11
|
+
private readonly snapshot;
|
|
12
|
+
private readonly goalFile;
|
|
13
|
+
constructor(snapshot?: SavedSnapshot, goalFile?: RuntimeGoalFile);
|
|
14
|
+
/** The saved current graph, or why it cannot be compared. */
|
|
15
|
+
current(workspaceRoot: string): RuntimeGraph | string;
|
|
16
|
+
/** The saved goal, or why it cannot be compared (missing: how to start one). */
|
|
17
|
+
goal(workspaceRoot: string): RuntimeGraph | string;
|
|
18
|
+
view(current: RuntimeGraph | string, goal: RuntimeGraph | string, options: RuntimeVizOptions): RuntimeComparisonView;
|
|
19
|
+
/** Goal vs Current with no overlay: the mode is disabled and says `reason` instead. */
|
|
20
|
+
unavailable(reason: string): RuntimeComparisonView;
|
|
21
|
+
private text;
|
|
22
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RuntimeComparisonInputs = void 0;
|
|
4
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
5
|
+
const runtime_graph_io_1 = require("./runtime-graph-io");
|
|
6
|
+
const saved_snapshot_1 = require("./saved-snapshot");
|
|
7
|
+
const runtime_goal_file_1 = require("./runtime-goal-file");
|
|
8
|
+
const graph_render_model_1 = require("./graph-render-model");
|
|
9
|
+
const runtime_goal_comparison_1 = require("./runtime-goal-comparison");
|
|
10
|
+
const toError_1 = require("../toError");
|
|
11
|
+
/**
|
|
12
|
+
* Loads either saved runtime file for the OTHER page's comparison, turning a missing or unusable file
|
|
13
|
+
* into the reason Goal vs Current is unavailable — never into "no differences".
|
|
14
|
+
*/
|
|
15
|
+
class RuntimeComparisonInputs {
|
|
16
|
+
snapshot;
|
|
17
|
+
goalFile;
|
|
18
|
+
constructor(snapshot = new saved_snapshot_1.SavedSnapshot(), goalFile = new runtime_goal_file_1.RuntimeGoalFile()) {
|
|
19
|
+
this.snapshot = snapshot;
|
|
20
|
+
this.goalFile = goalFile;
|
|
21
|
+
}
|
|
22
|
+
/** The saved current graph, or why it cannot be compared. */
|
|
23
|
+
current(workspaceRoot) {
|
|
24
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- an unusable comparison input becomes the mode's stated reason; the page itself still renders
|
|
25
|
+
try {
|
|
26
|
+
return this.snapshot.loadRuntime(workspaceRoot);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
const error = (0, toError_1.toError)(err);
|
|
30
|
+
return `No usable saved current runtime graph at ${runtime_graph_io_1.DEFAULT_RUNTIME_GRAPH_PATH}: ${this.text(error)}`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** The saved goal, or why it cannot be compared (missing: how to start one). */
|
|
34
|
+
goal(workspaceRoot) {
|
|
35
|
+
if (!this.goalFile.exists(workspaceRoot))
|
|
36
|
+
return `No runtime goal at ${runtime_goal_file_1.DEFAULT_RUNTIME_GOAL_PATH}. Start a proposal with: ${runtime_goal_file_1.RUNTIME_GOAL_INIT_COMMAND}`;
|
|
37
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- an unusable comparison input becomes the mode's stated reason; the page itself still renders
|
|
38
|
+
try {
|
|
39
|
+
return this.goalFile.load(workspaceRoot);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
const error = (0, toError_1.toError)(err);
|
|
43
|
+
return this.text(error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
view(current, goal, options) {
|
|
47
|
+
if (typeof current === 'string')
|
|
48
|
+
return this.unavailable(current);
|
|
49
|
+
if (typeof goal === 'string')
|
|
50
|
+
return this.unavailable(goal);
|
|
51
|
+
return new runtime_goal_comparison_1.RuntimeGoalComparison().build(current, goal, options);
|
|
52
|
+
}
|
|
53
|
+
/** Goal vs Current with no overlay: the mode is disabled and says `reason` instead. */
|
|
54
|
+
unavailable(reason) {
|
|
55
|
+
return new graph_render_model_1.RuntimeComparisonView(false, reason, 'Current → Goal', '', null, {}, {}, []);
|
|
56
|
+
}
|
|
57
|
+
text(error) {
|
|
58
|
+
if (error instanceof rules_config_1.RuleFailError)
|
|
59
|
+
return (0, rules_config_1.renderRuleFailForHuman)(error);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.RuntimeComparisonInputs = RuntimeComparisonInputs;
|
|
64
|
+
//# sourceMappingURL=runtime-goal-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-goal-view.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-goal-view.ts"],"names":[],"mappings":";;;AAAA,0DAAgF;AAEhF,yDAAgE;AAChE,qDAAiD;AACjD,2DAA4G;AAC5G,6DAA6D;AAC7D,uEAAkE;AAElE,wCAAqC;AAErC;;;GAGG;AACH,MAAa,uBAAuB;IAEX;IACA;IAFrB,YACqB,WAA0B,IAAI,8BAAa,EAAE,EAC7C,WAA4B,IAAI,mCAAe,EAAE;QADjD,aAAQ,GAAR,QAAQ,CAAqC;QAC7C,aAAQ,GAAR,QAAQ,CAAyC;IACnE,CAAC;IAEJ,6DAA6D;IAC7D,OAAO,CAAC,aAAqB;QACzB,8JAA8J;QAC9J,IAAI,CAAC;YACD,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;QACpD,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,OAAO,4CAA4C,6CAA0B,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACzG,CAAC;IACL,CAAC;IAED,gFAAgF;IAChF,IAAI,CAAC,aAAqB;QACtB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC;YACpC,OAAO,sBAAsB,6CAAyB,4BAA4B,6CAAyB,EAAE,CAAC;QAClH,8JAA8J;QAC9J,IAAI,CAAC;YACD,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACL,CAAC;IAED,IAAI,CAAC,OAA8B,EAAE,IAA2B,EAAE,OAA0B;QACxF,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAClE,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC5D,OAAO,IAAI,+CAAqB,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACrE,CAAC;IAED,uFAAuF;IACvF,WAAW,CAAC,MAAc;QACtB,OAAO,IAAI,0CAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IAC5F,CAAC;IAEO,IAAI,CAAC,KAAY;QACrB,IAAI,KAAK,YAAY,4BAAa;YAAE,OAAO,IAAA,qCAAsB,EAAC,KAAK,CAAC,CAAC;QACzE,MAAM,KAAK,CAAC;IAChB,CAAC;CACJ;AA7CD,0DA6CC","sourcesContent":["import { RuleFailError, renderRuleFailForHuman } from '@webpieces/rules-config';\nimport type { RuntimeGraph } from './runtime-graph-model';\nimport { DEFAULT_RUNTIME_GRAPH_PATH } from './runtime-graph-io';\nimport { SavedSnapshot } from './saved-snapshot';\nimport { DEFAULT_RUNTIME_GOAL_PATH, RUNTIME_GOAL_INIT_COMMAND, RuntimeGoalFile } from './runtime-goal-file';\nimport { RuntimeComparisonView } from './graph-render-model';\nimport { RuntimeGoalComparison } from './runtime-goal-comparison';\nimport type { RuntimeVizOptions } from './runtime-viz-options';\nimport { toError } from '../toError';\n\n/**\n * Loads either saved runtime file for the OTHER page's comparison, turning a missing or unusable file\n * into the reason Goal vs Current is unavailable — never into \"no differences\".\n */\nexport class RuntimeComparisonInputs {\n constructor(\n private readonly snapshot: SavedSnapshot = new SavedSnapshot(),\n private readonly goalFile: RuntimeGoalFile = new RuntimeGoalFile(),\n ) {}\n\n /** The saved current graph, or why it cannot be compared. */\n current(workspaceRoot: string): RuntimeGraph | string {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- an unusable comparison input becomes the mode's stated reason; the page itself still renders\n try {\n return this.snapshot.loadRuntime(workspaceRoot);\n } catch (err: unknown) {\n const error = toError(err);\n return `No usable saved current runtime graph at ${DEFAULT_RUNTIME_GRAPH_PATH}: ${this.text(error)}`;\n }\n }\n\n /** The saved goal, or why it cannot be compared (missing: how to start one). */\n goal(workspaceRoot: string): RuntimeGraph | string {\n if (!this.goalFile.exists(workspaceRoot))\n return `No runtime goal at ${DEFAULT_RUNTIME_GOAL_PATH}. Start a proposal with: ${RUNTIME_GOAL_INIT_COMMAND}`;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- an unusable comparison input becomes the mode's stated reason; the page itself still renders\n try {\n return this.goalFile.load(workspaceRoot);\n } catch (err: unknown) {\n const error = toError(err);\n return this.text(error);\n }\n }\n\n view(current: RuntimeGraph | string, goal: RuntimeGraph | string, options: RuntimeVizOptions): RuntimeComparisonView {\n if (typeof current === 'string') return this.unavailable(current);\n if (typeof goal === 'string') return this.unavailable(goal);\n return new RuntimeGoalComparison().build(current, goal, options);\n }\n\n /** Goal vs Current with no overlay: the mode is disabled and says `reason` instead. */\n unavailable(reason: string): RuntimeComparisonView {\n return new RuntimeComparisonView(false, reason, 'Current → Goal', '', null, {}, {}, []);\n }\n\n private text(error: Error): string {\n if (error instanceof RuleFailError) return renderRuleFailForHuman(error);\n throw error;\n }\n}\n"]}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goal vs Current (#1239): a SEMANTIC diff of two saved RuntimeGraph files.
|
|
3
|
+
*
|
|
4
|
+
* Both graphs are flattened into FACTS keyed by stable underlying identities — a service's project
|
|
5
|
+
* key, a queue's `Api.method`, a contract queue's API, an external system's identity — never by a
|
|
6
|
+
* shortened display name or a renderer-generated edge id. A fact is a NODE (service, queue, contract
|
|
7
|
+
* queue, external system, remote service, external library, trigger), a RELATIONSHIP (rpc call,
|
|
8
|
+
* enqueue, delivery, external use, remote call, unresolved use) or a CONTRACT (an `apis` entry).
|
|
9
|
+
*
|
|
10
|
+
* Comparison policy:
|
|
11
|
+
* - Values are canonical strings. Set-like arrays are de-duplicated and sorted, so object key order
|
|
12
|
+
* and array order alone never produce a change.
|
|
13
|
+
* - Queue facts are compared per method / per contract BEFORE presentation grouping, and every
|
|
14
|
+
* pubsub edge is split into its enqueue and delivery relationships, so a merged queue box cannot
|
|
15
|
+
* hide a difference between its members.
|
|
16
|
+
* - Bookkeeping is ignored: `levelBecause` (explanatory text the level machinery regenerates),
|
|
17
|
+
* `wiringUses` / `wiringImplements` (reviewed provenance), and `dependsOn` (the BUILD graph, which
|
|
18
|
+
* this runtime comparison deliberately does not cover). `maxLevel` is reported in the summary.
|
|
19
|
+
* - A service's outgoing relationships are one of its properties, so a service whose calls changed
|
|
20
|
+
* is itself CHANGED and its details show the before/after call list.
|
|
21
|
+
*/
|
|
22
|
+
import type { RuntimeEdge, RuntimeGraph } from './runtime-graph-model';
|
|
23
|
+
export declare enum RuntimeDiffStatus {
|
|
24
|
+
ADDED = "added",
|
|
25
|
+
REMOVED = "removed",
|
|
26
|
+
CHANGED = "changed",
|
|
27
|
+
UNCHANGED = "unchanged"
|
|
28
|
+
}
|
|
29
|
+
export declare enum RuntimeFactKind {
|
|
30
|
+
NODE = "node",
|
|
31
|
+
RELATIONSHIP = "relationship",
|
|
32
|
+
CONTRACT = "contract"
|
|
33
|
+
}
|
|
34
|
+
/** One identity's canonical properties in one graph. */
|
|
35
|
+
export declare class RuntimeFact {
|
|
36
|
+
readonly id: string;
|
|
37
|
+
readonly kind: RuntimeFactKind;
|
|
38
|
+
readonly category: string;
|
|
39
|
+
readonly label: string;
|
|
40
|
+
readonly properties: Record<string, string>;
|
|
41
|
+
constructor(id: string, kind: RuntimeFactKind, category: string, label: string, properties: Record<string, string>);
|
|
42
|
+
}
|
|
43
|
+
/** One identity compared across Current → Goal. `before` is null when added, `after` when removed. */
|
|
44
|
+
export declare class RuntimeFactChange {
|
|
45
|
+
readonly id: string;
|
|
46
|
+
readonly kind: RuntimeFactKind;
|
|
47
|
+
readonly category: string;
|
|
48
|
+
readonly label: string;
|
|
49
|
+
readonly status: RuntimeDiffStatus;
|
|
50
|
+
readonly before: Record<string, string> | null;
|
|
51
|
+
readonly after: Record<string, string> | null;
|
|
52
|
+
readonly changedFields: string[];
|
|
53
|
+
constructor(id: string, kind: RuntimeFactKind, category: string, label: string, status: RuntimeDiffStatus, before: Record<string, string> | null, after: Record<string, string> | null, changedFields: string[]);
|
|
54
|
+
/** Human lines for a details panel: the status, then `field: before → after` per changed field. */
|
|
55
|
+
lines(): string[];
|
|
56
|
+
private listing;
|
|
57
|
+
}
|
|
58
|
+
export declare class RuntimeDiffCounts {
|
|
59
|
+
readonly added: number;
|
|
60
|
+
readonly removed: number;
|
|
61
|
+
readonly changed: number;
|
|
62
|
+
readonly unchanged: number;
|
|
63
|
+
constructor(added: number, removed: number, changed: number, unchanged: number);
|
|
64
|
+
}
|
|
65
|
+
/** Every compared identity, sorted by id, plus graph-level bookkeeping. */
|
|
66
|
+
export declare class RuntimeGraphDiffResult {
|
|
67
|
+
readonly changes: RuntimeFactChange[];
|
|
68
|
+
readonly maxLevelBefore: number;
|
|
69
|
+
readonly maxLevelAfter: number;
|
|
70
|
+
private readonly byId;
|
|
71
|
+
constructor(changes: RuntimeFactChange[], maxLevelBefore: number, maxLevelAfter: number);
|
|
72
|
+
get(id: string): RuntimeFactChange | undefined;
|
|
73
|
+
counts(): RuntimeDiffCounts;
|
|
74
|
+
identical(): boolean;
|
|
75
|
+
/** The changed identities only (added, removed, changed), in id order. */
|
|
76
|
+
differences(): RuntimeFactChange[];
|
|
77
|
+
}
|
|
78
|
+
/** Flattens one RuntimeGraph into its facts. */
|
|
79
|
+
export declare class RuntimeFactSet {
|
|
80
|
+
private readonly graph;
|
|
81
|
+
readonly facts: Map<string, RuntimeFact>;
|
|
82
|
+
constructor(graph: RuntimeGraph);
|
|
83
|
+
private add;
|
|
84
|
+
private relationships;
|
|
85
|
+
/** `queue:Api.method` for a method-level hop; one `contract-queue:Api` per contract otherwise. */
|
|
86
|
+
queueIds(edge: RuntimeEdge): string[];
|
|
87
|
+
private services;
|
|
88
|
+
private queues;
|
|
89
|
+
private externals;
|
|
90
|
+
private remotes;
|
|
91
|
+
private triggers;
|
|
92
|
+
private contracts;
|
|
93
|
+
private owner;
|
|
94
|
+
private node;
|
|
95
|
+
private relation;
|
|
96
|
+
/** Set semantics: de-duplicated and sorted, so order alone is never a change. */
|
|
97
|
+
private set;
|
|
98
|
+
private split;
|
|
99
|
+
private value;
|
|
100
|
+
/** JSON with sorted keys, for the two map-or-string fields. */
|
|
101
|
+
private canonical;
|
|
102
|
+
}
|
|
103
|
+
/** Compares two saved runtime graphs, Current → Goal. Pure; deterministic for equivalent inputs. */
|
|
104
|
+
export declare class RuntimeGraphDiff {
|
|
105
|
+
compare(current: RuntimeGraph, goal: RuntimeGraph): RuntimeGraphDiffResult;
|
|
106
|
+
private change;
|
|
107
|
+
}
|