@webpieces/nx-webpieces-rules 0.4.698 → 0.4.700

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.
@@ -15,7 +15,7 @@
15
15
  *
16
16
  * WHICH X is decided by the call site, not by "everyone who implements Y", in
17
17
  * priority order: (1) a literal at the call site —
18
- * `createRpcClient(Y, new ClientConfig('helper-fsdb'))` — kept as
18
+ * `createRpcClient(Y, new ClientConfig('helper-fsdb', new DeployedServiceHost()))` — kept as
19
19
  * `ApiRef.targetService`; else (2) the calling project's declared `callsService`
20
20
  * (project.json metadata.webpieces.callsService), the symmetric half of
21
21
  * `serviceName` for the shared-library case where the client is built once from a
@@ -29,16 +29,15 @@
29
29
  * one, so it must never degrade silently.
30
30
  */
31
31
  Object.defineProperty(exports, "__esModule", { value: true });
32
- exports.RuntimeGraphReport = exports.serializeRuntimeGraph = exports.loadRuntimeGraph = exports.runtimeGraphFileExists = exports.saveRuntimeGraph = exports.DEFAULT_RUNTIME_GRAPH_PATH = void 0;
33
- exports.runtimeAdjacency = runtimeAdjacency;
32
+ exports.runtimeAdjacency = exports.RuntimeGraphReport = exports.serializeRuntimeGraph = exports.loadRuntimeGraph = exports.runtimeGraphFileExists = exports.saveRuntimeGraph = exports.DEFAULT_RUNTIME_GRAPH_PATH = void 0;
34
33
  exports.deriveRuntimeGraph = deriveRuntimeGraph;
35
34
  exports.deriveRuntimeGraphReport = deriveRuntimeGraphReport;
36
- const graph_sorter_1 = require("./graph-sorter");
37
35
  const api_relations_1 = require("./api-usage/api-relations");
38
36
  const runtime_graph_decls_1 = require("./runtime-graph-decls");
39
37
  const runtime_graph_sorters_1 = require("./runtime-graph-sorters");
40
38
  const external_systems_1 = require("./api-usage/external-systems");
41
- const toError_1 = require("../toError");
39
+ const runtime_graph_levels_1 = require("./runtime-graph-levels");
40
+ const runtime_host_nodes_1 = require("./runtime-host-nodes");
42
41
  // Persistence lives in runtime-graph-io.ts; re-exported for the same reason as the model types.
43
42
  var runtime_graph_io_1 = require("./runtime-graph-io");
44
43
  Object.defineProperty(exports, "DEFAULT_RUNTIME_GRAPH_PATH", { enumerable: true, get: function () { return runtime_graph_io_1.DEFAULT_RUNTIME_GRAPH_PATH; } });
@@ -83,50 +82,6 @@ class RuntimeGraphReport {
83
82
  }
84
83
  }
85
84
  exports.RuntimeGraphReport = RuntimeGraphReport;
86
- /**
87
- * Adjacency (service -> [targets]) used for leveling + cycle checks.
88
- *
89
- * PUBSUB EDGES ARE EXCLUDED. A queue is precisely the thing that decouples producer from consumer:
90
- * the producer returns as soon as the task is enqueued and never waits on the consumer, so a queued
91
- * hop is not a runtime dependency in the sense levels and cycle detection mean. Counting them would
92
- * make the common and correct `A → queue → A` (a service deferring its own work) an architecture
93
- * cycle, and would rank services by an ordering that does not constrain deploy or startup.
94
- */
95
- // webpieces-disable no-function-outside-class -- pure graph helper, matches the sibling helpers in this file
96
- function adjacencyFromEdges(serviceNames, edges) {
97
- const adj = {};
98
- for (const name of serviceNames)
99
- adj[name] = [];
100
- for (const edge of edges) {
101
- if (edge.type === 'pubsub')
102
- continue;
103
- if (!adj[edge.from])
104
- adj[edge.from] = [];
105
- adj[edge.from].push(edge.to);
106
- }
107
- return adj;
108
- }
109
- /** Adjacency (service -> [targets]) from a loaded runtime graph. */
110
- function runtimeAdjacency(graph) {
111
- return adjacencyFromEdges(Object.keys(graph.services), graph.runtimeEdges);
112
- }
113
- /** Assign levels via topological sort; falls back to level 0 when a cycle exists. */
114
- function assignLevels(adjacency) {
115
- const levels = {};
116
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
117
- try {
118
- const sorted = (0, graph_sorter_1.sortGraphTopologically)(adjacency);
119
- for (const name of Object.keys(sorted))
120
- levels[name] = sorted[name].level;
121
- }
122
- catch (err) {
123
- const error = (0, toError_1.toError)(err);
124
- void error;
125
- for (const name of Object.keys(adjacency))
126
- levels[name] = 0;
127
- }
128
- return levels;
129
- }
130
85
  /**
131
86
  * Derives the runtime microservice graph from architecture/dependencies.json `apiRelations`:
132
87
  * implementers × users per API, split by transport. An rpc edge is a direct call; a pubsub edge flows
@@ -149,6 +104,13 @@ class RuntimeGraphDeriver {
149
104
  problems = [];
150
105
  /** role:server nodes hidden by isNonParticipantServer, in the order buildServices met them. */
151
106
  autoHidden = [];
107
+ /**
108
+ * identity -> the services that dial it and the contracts they dial it with, for every client
109
+ * whose base URL arrives at RUNTIME. Accumulated while edges are built, then merged into the
110
+ * graph's `externalSystems` in {@link assemble} so a partner destination is drawn as the
111
+ * external node it is instead of vanishing between fan-out and `unresolvedUses`.
112
+ */
113
+ runtimeHosts = new Map();
152
114
  /**
153
115
  * False when NO project in the graph carries `webpiecesRuntime`, i.e. the file was written
154
116
  * before the field existed. Auto-hiding is then off ENTIRELY, so an old dependencies.json
@@ -216,7 +178,20 @@ class RuntimeGraphDeriver {
216
178
  queues: edgeResult.queues,
217
179
  triggers: this.buildTriggers(decls),
218
180
  };
219
- (0, external_systems_1.attachExternalSystems)(graph, (0, external_systems_1.resolveExternalSystems)(this.externalSystemDecls, services));
181
+ const systems = (0, external_systems_1.resolveExternalSystems)(this.externalSystemDecls, services);
182
+ // Runtime destinations join the SAME table the declared vendor systems live in, so two
183
+ // clients naming one identity converge on one node exactly as two `@externalSystem twilio`
184
+ // contracts do, and the drift check that already watches `externalSystems` watches these too.
185
+ for (const identity of Array.from(this.runtimeHosts.keys()).sort()) {
186
+ const usage = this.runtimeHosts.get(identity);
187
+ systems[identity] = {
188
+ kind: 'runtime',
189
+ label: identity,
190
+ usedBy: [...usage.usedBy].sort(),
191
+ apis: [...usage.apis].sort(),
192
+ };
193
+ }
194
+ (0, external_systems_1.attachExternalSystems)(graph, systems);
220
195
  return new RuntimeGraphReport(graph, this.warnings, this.problems, [...this.autoHidden].sort());
221
196
  }
222
197
  /**
@@ -390,6 +365,13 @@ class RuntimeGraphDeriver {
390
365
  const queues = new Map();
391
366
  for (const decl of decls) {
392
367
  for (const ref of decl.usesApis) {
368
+ // A destination supplied at runtime is neither an in-repo implementer nor a missing
369
+ // one, so BOTH of the branches below would misreport it — fan-out invents edges to
370
+ // services we never call, `unresolvedUses` reads as a gap. It gets its own node.
371
+ if (ref.runtimeHost !== undefined) {
372
+ this.recordRuntimeHost(decl.name, ref.runtimeHost, ref.api);
373
+ continue;
374
+ }
393
375
  const implementers = apis.get(ref.api)?.implementedBy ?? [];
394
376
  if (implementers.length === 0) {
395
377
  // Nobody in-repo serves it. For a vendor contract that is the ANSWER, not a gap:
@@ -464,6 +446,16 @@ class RuntimeGraphDeriver {
464
446
  queue.consumedBy.push(to);
465
447
  }
466
448
  }
449
+ /** Remember that `user` dials the runtime destination `identity` through contract `api`. */
450
+ recordRuntimeHost(user, identity, api) {
451
+ let usage = this.runtimeHosts.get(identity);
452
+ if (usage === undefined) {
453
+ usage = new runtime_host_nodes_1.RuntimeHostUsage();
454
+ this.runtimeHosts.set(identity, usage);
455
+ }
456
+ usage.usedBy.add(user);
457
+ usage.apis.add(api);
458
+ }
467
459
  /**
468
460
  * WHICH implementers this one `uses` reaches. Resolution order (most specific wins):
469
461
  * 1. a literal `ClientConfig` at the call site (`ref.targetService`) — names ONE node;
@@ -533,7 +525,7 @@ class RuntimeGraphDeriver {
533
525
  if (implementers.length > 1) {
534
526
  this.warnings.push(`${user} uses "${ref.api}" with no literal client config, and ${implementers.length} services ` +
535
527
  `implement it (${implementers.join(', ')}) — an edge is drawn to EVERY one, so all but one ` +
536
- `are fiction. Name the target: createRpcClient(${ref.api}, new ClientConfig('<serviceName>')); ` +
528
+ `are fiction. Name the target: createRpcClient(${ref.api}, new ClientConfig('<serviceName>', new DeployedServiceHost())); ` +
537
529
  `or, when the client is built in a shared library (no literal can sit at the call site), ` +
538
530
  `declare metadata.webpieces.callsService: '<serviceName>' on ${user}'s project.json.`);
539
531
  }
@@ -596,7 +588,7 @@ class RuntimeGraphDeriver {
596
588
  this.autoHidden.push(decl.name);
597
589
  }
598
590
  }
599
- const levels = assignLevels(adjacencyFromEdges(Object.keys(services), edges));
591
+ const levels = (0, runtime_graph_levels_1.assignLevels)((0, runtime_graph_levels_1.adjacencyFromEdges)(Object.keys(services), edges));
600
592
  for (const name of Object.keys(services))
601
593
  services[name].level = levels[name] ?? 0;
602
594
  return services;
@@ -622,4 +614,8 @@ function deriveRuntimeGraph(projects, hiddenProjects = new Set(), apiContracts =
622
614
  function deriveRuntimeGraphReport(projects, hiddenProjects = new Set(), apiContracts = {}, externalSystems = {}) {
623
615
  return new RuntimeGraphDeriver(projects, hiddenProjects, apiContracts, externalSystems).assemble();
624
616
  }
617
+ // Levels + adjacency live in runtime-graph-levels.ts; re-exported for the same reason the model
618
+ // types and the io helpers above are — one obvious place to import the runtime graph from.
619
+ var runtime_graph_levels_2 = require("./runtime-graph-levels");
620
+ Object.defineProperty(exports, "runtimeAdjacency", { enumerable: true, get: function () { return runtime_graph_levels_2.runtimeAdjacency; } });
625
621
  //# sourceMappingURL=runtime-graph.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-graph.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-graph.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Runtime Graph\n *\n * Derives the runtime microservice graph SOLELY from architecture/dependencies.json\n * (the single source of truth): each project's `apiRelations` carry which api\n * classes it implements/uses and their transport (rpc | pubsub). Both\n * `architecture:generate` and `architecture:validate-runtime-architecture` call\n * the SAME `deriveRuntimeGraph`, so the committed graph and the validated graph\n * can never diverge.\n *\n * The runtime edge Z -> X (Z depends on X at runtime) is INFERRED: Z `uses` api\n * Y and X `implements` api Y. This edge does not exist in the compile-time\n * dependencies.json (both Z and X only compile-depend on the api library Y).\n *\n * WHICH X is decided by the call site, not by \"everyone who implements Y\", in\n * priority order: (1) a literal at the call site —\n * `createRpcClient(Y, new ClientConfig('helper-fsdb'))` — kept as\n * `ApiRef.targetService`; else (2) the calling project's declared `callsService`\n * (project.json metadata.webpieces.callsService), the symmetric half of\n * `serviceName` for the shared-library case where the client is built once from a\n * config field so no literal can sit at the call site; else (3) fan-out. A named\n * target from (1) or (2) is matched against each node's DECLARED `serviceName`.\n * Fanning an edge out to every implementer is catastrophic for a company-wide\n * contract registered in a shared library — it manufactures calls that cannot\n * happen, and cycles that do not exist. When a target cannot be resolved the old\n * fan-out still happens, but a warning names the call site (see\n * RuntimeGraphReport.warnings): a wrong-but-green graph is worse than a failing\n * one, so it must never degrade silently.\n */\n\nimport { sortGraphTopologically } from './graph-sorter';\nimport type { EnhancedGraph, GraphEntry } from './graph-sorter';\nimport type {\n ApiContracts,\n ApiMethodMeta,\n ApiRef,\n ApiTransport,\n ExternalSystemDecls,\n} from './api-usage/api-relations';\nimport { sortApiRefs } from './api-usage/api-relations';\nimport { RelationSink } from './runtime-graph-decls';\nimport type { ScanDecl } from './runtime-graph-decls';\nimport { dedupApiRefs, sortedQueues, sortedRecord, sortUnresolved } from './runtime-graph-sorters';\nimport { attachExternalSystems, resolveExternalSystems } from './api-usage/external-systems';\nimport type {\n RuntimeApi,\n RuntimeEdge,\n RuntimeExternalSystem,\n RuntimeGraph,\n RuntimeQueue,\n RuntimeService,\n RuntimeTrigger,\n RuntimeUnresolved,\n} from './runtime-graph-model';\nimport { toError } from '../toError';\n\n// The committed data shape lives in runtime-graph-model.ts; re-exported here so every existing\n// `from './runtime-graph'` import keeps working and there is still one obvious place to import from.\nexport type {\n RuntimeApi,\n RuntimeEdge,\n RuntimeExternalSystem,\n RuntimeGraph,\n RuntimeQueue,\n RuntimeService,\n RuntimeTrigger,\n RuntimeUnresolved,\n} from './runtime-graph-model';\n\n// Persistence lives in runtime-graph-io.ts; re-exported for the same reason as the model types.\nexport {\n DEFAULT_RUNTIME_GRAPH_PATH,\n saveRuntimeGraph,\n runtimeGraphFileExists,\n loadRuntimeGraph,\n serializeRuntimeGraph,\n} from './runtime-graph-io';\n\ninterface EdgeResult {\n edges: RuntimeEdge[];\n unresolved: RuntimeUnresolved[];\n queues: Record<string, RuntimeQueue>;\n}\n\n/**\n * The derived graph PLUS everything the derivation had to guess at. `warnings` is deliberately not\n * part of RuntimeGraph: it is not committed data, it is the report that stops a guessed edge from\n * passing for a derived one. Executors print it.\n */\nexport class RuntimeGraphReport {\n constructor(\n public readonly graph: RuntimeGraph,\n /** Human-readable lines naming every call site whose target the graph could not pin down. */\n public readonly warnings: string[],\n /**\n * Call sites that name a target the repo does not contain. Unlike a warning these FAIL the\n * build: the contract IS served in-repo, so the name is a typo or a stale rename, and the\n * only reason it stayed invisible is that the graph quietly fanned the edge out instead.\n * (A call to a service outside the repo never reaches here — nothing in-repo implements its\n * contract, so it is `unresolvedUses`.)\n */\n public readonly problems: string[] = [],\n /**\n * role:server nodes omitted from the DRAWING because they declare no webpieces runtime\n * package anywhere in their library closure and serve/call nothing in-repo. Neither a\n * warning nor a problem — this is intended behavior, and shouting on every clean run is how\n * warnings stop being read. Deliberately NOT persisted: a graph file that records its own\n * omissions gets committed and stops being read. Executors print it instead, so the\n * omission is always visible and never silent.\n */\n public readonly autoHidden: string[] = [],\n ) {}\n}\n\n/**\n * Adjacency (service -> [targets]) used for leveling + cycle checks.\n *\n * PUBSUB EDGES ARE EXCLUDED. A queue is precisely the thing that decouples producer from consumer:\n * the producer returns as soon as the task is enqueued and never waits on the consumer, so a queued\n * hop is not a runtime dependency in the sense levels and cycle detection mean. Counting them would\n * make the common and correct `A → queue → A` (a service deferring its own work) an architecture\n * cycle, and would rank services by an ordering that does not constrain deploy or startup.\n */\n// webpieces-disable no-function-outside-class -- pure graph helper, matches the sibling helpers in this file\nfunction adjacencyFromEdges(\n serviceNames: string[],\n edges: RuntimeEdge[],\n): Record<string, string[]> {\n const adj: Record<string, string[]> = {};\n for (const name of serviceNames) adj[name] = [];\n for (const edge of edges) {\n if (edge.type === 'pubsub') continue;\n if (!adj[edge.from]) adj[edge.from] = [];\n adj[edge.from].push(edge.to);\n }\n return adj;\n}\n\n/** Adjacency (service -> [targets]) from a loaded runtime graph. */\nexport function runtimeAdjacency(graph: RuntimeGraph): Record<string, string[]> {\n return adjacencyFromEdges(Object.keys(graph.services), graph.runtimeEdges);\n}\n\n/** Assign levels via topological sort; falls back to level 0 when a cycle exists. */\nfunction assignLevels(adjacency: Record<string, string[]>): Record<string, number> {\n const levels: Record<string, number> = {};\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const sorted = sortGraphTopologically(adjacency);\n for (const name of Object.keys(sorted)) levels[name] = sorted[name].level;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n for (const name of Object.keys(adjacency)) levels[name] = 0;\n }\n return levels;\n}\n\n/**\n * Derives the runtime microservice graph from architecture/dependencies.json `apiRelations`:\n * implementers × users per API, split by transport. An rpc edge is a direct call; a pubsub edge flows\n * through a queue (drawn producer → queue → consumer by the visualizer).\n *\n * Nodes are the runnable apps only — role:server / role:client. A library is NEVER a node; its\n * implements/uses (a shared client-factory or route-registration lib) are attributed to the\n * servers/clients that embed it, walked transitively over dependsOn (see collectDecls).\n */\nclass RuntimeGraphDeriver {\n /** apiClassName -> the api-lib project that owns the contract (from the apiRelations key). */\n private readonly apiOwners = new Map<string, string>();\n /** Addressable name -> the runtime node answering to it; how a targeted call resolves. */\n private readonly nodeByServiceName = new Map<string, string>();\n private readonly warnings: string[] = [];\n private readonly problems: string[] = [];\n /** role:server nodes hidden by isNonParticipantServer, in the order buildServices met them. */\n private readonly autoHidden: string[] = [];\n /**\n * False when NO project in the graph carries `webpiecesRuntime`, i.e. the file was written\n * before the field existed. Auto-hiding is then off ENTIRELY, so an old dependencies.json\n * renders exactly as it did — absence of the field must never be read as \"declares none\".\n * Any repo regenerated after this change has at least one webpieces project, so the guard\n * cannot wrongly suppress the feature.\n */\n private readonly markersKnown: boolean;\n\n constructor(\n private readonly projects: EnhancedGraph,\n /** Project names tagged drawOnGraph:false — kept in the JSON but flagged so the viz omits them. */\n private readonly hiddenProjects: Set<string>,\n /**\n * The committed per-contract method table from dependencies.json. Empty means the file\n * predates `apiContracts`: every pubsub edge then falls back to one unnamed queue per\n * service pair, exactly as before, instead of failing on a missing table.\n */\n private readonly apiContracts: ApiContracts = {},\n /**\n * Declared external systems from dependencies.json, keyed by identity. Empty means nothing\n * was declared, which renders exactly as it did before declarations existed.\n */\n private readonly externalSystemDecls: ExternalSystemDecls = {},\n ) {\n this.markersKnown = Object.values(projects).some(\n (entry: GraphEntry) => entry.webpiecesRuntime !== undefined,\n );\n // A node ALWAYS answers to its own module name, so a repo whose deployed names match its\n // project names needs no declaration at all — and no alias can ever redirect 'ai-chat' away\n // from the ai-chat module. Module names are therefore claimed FIRST and are unshadowable.\n for (const name of Object.keys(projects).sort()) {\n if (this.isNode(name)) this.nodeByServiceName.set(name, name);\n }\n // `serviceName` then adds the alias a repo needs when its deployed name is NOT its module\n // name (helper-svr serving 'helper-portal'). Colliding with a module is a misconfiguration,\n // not a precedence question: the alias is silently unreachable, so say so.\n for (const name of Object.keys(projects).sort()) {\n const declared = projects[name].serviceName;\n if (!this.isNode(name) || declared === undefined) continue;\n const claimant = this.nodeByServiceName.get(declared);\n if (claimant === undefined) {\n this.nodeByServiceName.set(declared, name);\n } else if (claimant !== name) {\n this.problems.push(\n `${name} declares serviceName '${declared}', but that is already the module name of ` +\n `${claimant} — the alias can never be reached. Rename one of them.`,\n );\n }\n }\n }\n\n assemble(): RuntimeGraphReport {\n const decls = this.collectDecls();\n const apis = this.buildApis(decls);\n const edgeResult = this.buildEdges(decls, apis);\n const services = this.buildServices(decls, edgeResult.edges);\n const apisObj: Record<string, RuntimeApi> = {};\n for (const api of Array.from(apis.keys()).sort()) apisObj[api] = apis.get(api)!;\n const graph: RuntimeGraph = {\n services,\n apis: apisObj,\n runtimeEdges: edgeResult.edges,\n unresolvedUses: edgeResult.unresolved,\n queues: edgeResult.queues,\n triggers: this.buildTriggers(decls),\n };\n attachExternalSystems(graph, resolveExternalSystems(this.externalSystemDecls, services));\n return new RuntimeGraphReport(graph, this.warnings, this.problems, [...this.autoHidden].sort());\n }\n\n /**\n * The clock- and outside-driven entry points: for every contract a node IMPLEMENTS, each method\n * declared `cron` or `external` becomes a trigger pointing AT that node.\n *\n * Driven off `implements` rather than `uses` on purpose — these have no in-repo caller at all,\n * which is why they never produced an edge and stayed invisible.\n */\n private buildTriggers(decls: ScanDecl[]): RuntimeTrigger[] {\n const triggers: RuntimeTrigger[] = [];\n for (const decl of decls) {\n for (const ref of decl.implementsApis) {\n for (const method of this.methodsOf(ref.api)) {\n if (method.kind !== 'cron' && method.kind !== 'external') continue;\n const trigger: RuntimeTrigger = {\n kind: method.kind,\n api: ref.api,\n method: method.name,\n service: decl.name,\n };\n if (method.kind === 'cron') trigger.queueName = method.queueName;\n // ONLY for 'external' (a clock needs no vendor name), and only when declared —\n // a graph committed before callers were required has none and must still render.\n if (method.kind === 'external' && method.caller !== undefined)\n trigger.caller = method.caller;\n triggers.push(trigger);\n }\n }\n }\n return triggers.sort(\n (a: RuntimeTrigger, b: RuntimeTrigger) =>\n a.kind.localeCompare(b.kind) ||\n a.api.localeCompare(b.api) ||\n a.method.localeCompare(b.method) ||\n a.service.localeCompare(b.service),\n );\n }\n\n /** The committed method table for a contract, or [] when it declares none (e.g. a vendor seam). */\n private methodsOf(api: string): ApiMethodMeta[] {\n return this.apiContracts[api]?.methods ?? [];\n }\n\n /**\n * One ScanDecl per RUNTIME NODE (role:server / role:client). A node's effective\n * relations are its OWN apiRelations PLUS those of every library in its transitive\n * dependsOn closure: a lib that calls createRpcClient/addRoutes runs inside the\n * server/client that embeds it, so the relation is attributed to that node — the\n * lib is never a runtime node. Recursion stops at other nodes (each server/client\n * owns its own relations). dependsOn is transitively reduced, but reduction\n * preserves reachability, so the recursive walk still reaches every lib a node\n * embeds through libraries.\n */\n private collectDecls(): ScanDecl[] {\n const decls: ScanDecl[] = [];\n for (const name of Object.keys(this.projects).sort()) {\n if (!this.isNode(name)) continue;\n const sink = new RelationSink(name);\n this.collectEffectiveRelations(name, sink, new Set<string>([name]));\n // EVERY node gets a decl, including one with zero relations. Gating this on \"has at\n // least one implements/uses\" silently DELETED any deployed server whose work arrives\n // from outside the repo (a pull subscription): the diagram looked complete while missing\n // a running service — confidently wrong, not visibly incomplete. isNode() is the whole\n // test; drawOnGraph:false is the ONE opt-out, and such a node renders as an isolated box.\n decls.push({\n name,\n implementsApis: dedupApiRefs(sortApiRefs(sink.implementsApis)),\n usesApis: dedupApiRefs(sortApiRefs(sink.usesApis)),\n implementsVia: sink.implementsVia,\n markerVia: sink.markerVia,\n });\n }\n return decls;\n }\n\n /** True when a project is a runtime node — a server or client app (never a library). */\n private isNode(name: string): boolean {\n const role = this.projects[name]?.role;\n return role === 'server' || role === 'client';\n }\n\n /**\n * True for a role:server that speaks NO webpieces runtime package anywhere in its library\n * closure AND serves/calls no in-repo contract — a legacy Express/NestJS service sitting in the\n * same monorepo. This graph is built entirely out of webpieces contracts, so such a service can\n * only ever draw as a disconnected box: it has no edges by construction and never will.\n *\n * It stays a NODE and stays in runtime-dependencies.json (drawOnGraph:false), which is the whole\n * difference between hiding it and the silent deletion #542 removed — the data view still shows\n * a deployed role:server, only the picture drops it. Removing it from isNode() instead would\n * also drop it from nodeByServiceName, so a targeted ClientConfig('orders-manager') from a drawn\n * service would stop resolving and decay into an unresolved use: a graph that got quietly WORSE\n * while looking cleaner. The COMPILE-TIME graph (dependencies.html) still draws these projects;\n * they genuinely exist, and only the runtime drawing claims to show webpieces services.\n *\n * role:client is deliberately never auto-hidden: a browser app can legitimately declare no\n * marker at all, and nothing checks clients for presence the way checkServersPresent checks\n * servers, so there is no equivalent failure to prevent.\n */\n private isNonParticipantServer(decl: ScanDecl): boolean {\n if (!this.markersKnown) return false;\n if (this.projects[decl.name]?.role !== 'server') return false;\n if (decl.markerVia.size > 0) return false;\n // Belt-and-braces: serving or calling an in-repo contract IS participation, whatever the\n // package list says. This is what keeps a mechanism we did not anticipate from being blanked.\n return decl.implementsApis.length === 0 && decl.usesApis.length === 0;\n }\n\n /**\n * Accumulate `name`'s own api relations, then recurse through its LIBRARY deps\n * (skipping other nodes, which own their own relations). `visited` guards against\n * re-walking a lib reachable by more than one path (and any cycle).\n */\n private collectEffectiveRelations(\n name: string,\n sink: RelationSink,\n visited: Set<string>,\n ): void {\n const entry = this.projects[name];\n if (entry === undefined) return;\n sink.addMarkers(entry.webpiecesRuntime, name);\n const relations = entry.apiRelations;\n if (relations !== undefined) {\n for (const owner of Object.keys(relations).sort()) {\n for (const ref of relations[owner].implements) {\n this.apiOwners.set(ref.api, owner);\n sink.addImplements(ref, name);\n }\n for (const ref of relations[owner].uses) {\n this.apiOwners.set(ref.api, owner);\n sink.addUses(ref);\n }\n }\n }\n for (const dep of entry.dependsOn) {\n if (visited.has(dep)) continue;\n visited.add(dep);\n if (this.isNode(dep)) continue; // another server/client owns its own relations\n this.collectEffectiveRelations(dep, sink, visited);\n }\n }\n\n /** apiClassName -> { implementedBy, usedBy, type }. */\n private buildApis(decls: ScanDecl[]): Map<string, RuntimeApi> {\n const apis = new Map<string, RuntimeApi>();\n const ensure = (api: string, type: ApiTransport): RuntimeApi => {\n let entry = apis.get(api);\n if (!entry) {\n entry = { implementedBy: [], usedBy: [], type };\n apis.set(api, entry);\n }\n return entry;\n };\n for (const decl of decls) {\n for (const ref of decl.implementsApis)\n ensure(ref.api, ref.type).implementedBy.push(decl.name);\n for (const ref of decl.usesApis) ensure(ref.api, ref.type).usedBy.push(decl.name);\n }\n for (const api of apis.keys()) {\n const entry = apis.get(api)!;\n entry.implementedBy.sort();\n entry.usedBy = Array.from(new Set(entry.usedBy)).sort();\n const owner = this.apiOwners.get(api);\n if (owner !== undefined) entry.owner = owner;\n }\n return apis;\n }\n\n /** Inferred edges U -> I via api A, split by transport; plus uses with no implementer. */\n private buildEdges(decls: ScanDecl[], apis: Map<string, RuntimeApi>): EdgeResult {\n const viaByKey = new Map<string, Set<string>>();\n const unresolved: RuntimeUnresolved[] = [];\n const queues = new Map<string, RuntimeQueue>();\n for (const decl of decls) {\n for (const ref of decl.usesApis) {\n const implementers = apis.get(ref.api)?.implementedBy ?? [];\n if (implementers.length === 0) {\n // Nobody in-repo serves it. For a vendor contract that is the ANSWER, not a gap:\n // the call leaves the repo, and the viz terminates it at a dashed vendor node.\n unresolved.push({ service: decl.name, api: ref.api });\n continue;\n }\n for (const target of this.targetsFor(decl.name, ref, implementers)) {\n // A service calling ITSELF synchronously is noise; a service ENQUEUEING to\n // itself is a real, common topology (deferring its own work), and dropping it\n // was hiding the single most interesting thing about a queue.\n if (target === decl.name && ref.type !== 'pubsub') continue;\n if (ref.type === 'pubsub') {\n this.addQueuedEdges(decl.name, target, ref.api, viaByKey, queues);\n continue;\n }\n const key = `${decl.name} ${target} ${ref.type}`;\n if (!viaByKey.has(key)) viaByKey.set(key, new Set());\n viaByKey.get(key)!.add(ref.api);\n }\n }\n }\n return {\n edges: this.edgesFromKeys(viaByKey),\n unresolved: sortUnresolved(unresolved),\n queues: sortedQueues(queues),\n };\n }\n\n /**\n * One edge PER QUEUED METHOD of `api`, plus the queue each flows through.\n *\n * Per method rather than per service pair because that is the unit Cloud Tasks and Terraform\n * create: two services exchanging three queued methods run three independently-configured,\n * independently-backed-up queues, and collapsing them into one arrow hides which one is stuck.\n *\n * A contract with no committed method table (a dependencies.json predating `apiContracts`)\n * degrades to a single unnamed queue for the pair — the old behavior — rather than vanishing.\n */\n private addQueuedEdges(\n from: string,\n to: string,\n api: string,\n viaByKey: Map<string, Set<string>>,\n queues: Map<string, RuntimeQueue>,\n ): void {\n const queued = this.methodsOf(api).filter((m: ApiMethodMeta) => m.kind === 'cloudtasks');\n if (queued.length === 0) {\n const key = `${from} ${to} pubsub `;\n if (!viaByKey.has(key)) viaByKey.set(key, new Set());\n viaByKey.get(key)!.add(api);\n return;\n }\n for (const method of queued) {\n const queueKey = `${api}.${method.name}`;\n const key = `${from} ${to} pubsub ${queueKey}`;\n if (!viaByKey.has(key)) viaByKey.set(key, new Set());\n viaByKey.get(key)!.add(api);\n\n let queue = queues.get(queueKey);\n if (queue === undefined) {\n queue = {\n api,\n method: method.name,\n // Every cloudtasks method carries a queueName; the fallback covers a\n // dependencies.json written before queueName became kind-specific.\n queueName: method.queueName ?? `${api}-${method.name}`,\n producedBy: [],\n consumedBy: [],\n };\n queues.set(queueKey, queue);\n }\n if (!queue.producedBy.includes(from)) queue.producedBy.push(from);\n if (!queue.consumedBy.includes(to)) queue.consumedBy.push(to);\n }\n }\n\n /**\n * WHICH implementers this one `uses` reaches. Resolution order (most specific wins):\n * 1. a literal `ClientConfig` at the call site (`ref.targetService`) — names ONE node;\n * 2. else the calling project's declared `callsService` (project.json) — for the shared-library\n * case where the literal cannot sit at the call site, it lives one indirection away in the app;\n * 3. else the historical fan-out — the only safe superset — recording WHY, so a fanned-out (i.e.\n * possibly fictional) edge can never pass for a derived one.\n * A named target (1 or 2) that resolves to no module, or to a module that does not serve the\n * contract, is a PROBLEM (fails the build), never a silent drop.\n */\n private targetsFor(user: string, ref: ApiRef, implementers: string[]): string[] {\n const literal = ref.targetService;\n if (literal !== undefined)\n return this.resolveNamedTarget(user, ref, implementers, literal, 'literal');\n\n const declared = this.callsServiceFor(user, ref.api);\n if (declared !== undefined)\n return this.resolveNamedTarget(user, ref, implementers, declared, 'callsService');\n\n return this.untargetedFanOut(user, ref, implementers);\n }\n\n /**\n * The declared `callsService` target for this node's use of `api`, or undefined when none applies.\n * A string declaration aims every untargeted use at one service; a map aims per api-class.\n */\n private callsServiceFor(user: string, api: string): string | undefined {\n const declared = this.projects[user]?.callsService;\n if (declared === undefined) return undefined;\n if (typeof declared === 'string') return declared;\n return declared[api];\n }\n\n /**\n * Resolve a NAMED target (from a call-site literal or a project-level `callsService`) to the one\n * module that serves it. A name no module answers to, or a module that does not serve the\n * contract, fails the build — the wording names which declaration to fix, `source` deciding it.\n */\n private resolveNamedTarget(\n user: string,\n ref: ApiRef,\n implementers: string[],\n wanted: string,\n source: 'literal' | 'callsService',\n ): string[] {\n const node = this.nodeByServiceName.get(wanted);\n const served = `${implementers.length} module(s) serve this contract (${implementers.join(', ')})`;\n if (node === undefined) {\n this.problems.push(\n source === 'callsService'\n ? `${user} declares metadata.webpieces.callsService '${wanted}' in its project.json, but NO ` +\n `module answers to that name. ${served}. Point callsService at a module name, or declare ` +\n `metadata.webpieces.serviceName: '${wanted}' on the module that serves \"${ref.api}\".`\n : `${user} calls \"${ref.api}\" at service '${wanted}', but NO module answers to that name. ` +\n `${served}. Either use the module name, or declare metadata.webpieces.serviceName: ` +\n `'${wanted}' on the module that serves it (and translate any environment prefix in ` +\n `ClientRegistry.setDeriver, not here).`,\n );\n return implementers;\n }\n if (!implementers.includes(node)) {\n const via =\n source === 'callsService'\n ? `${user} declares callsService '${wanted}' (module ${node})`\n : `${user} calls \"${ref.api}\" at service '${wanted}' (module ${node})`;\n this.problems.push(\n `${via}, but ${node} does NOT serve \"${ref.api}\" — ${implementers.join(', ')} do. At runtime that ` +\n `call has nothing to answer it.`,\n );\n return implementers;\n }\n return [node];\n }\n\n /**\n * A use with no literal client config. One implementer is unambiguous, so it stays silent; more\n * than one means every edge but one is fiction — exactly the failure this mechanism exists to\n * stop — so it is reported even though the graph still (conservatively) draws them all.\n */\n private untargetedFanOut(user: string, ref: ApiRef, implementers: string[]): string[] {\n if (implementers.length > 1) {\n this.warnings.push(\n `${user} uses \"${ref.api}\" with no literal client config, and ${implementers.length} services ` +\n `implement it (${implementers.join(', ')}) — an edge is drawn to EVERY one, so all but one ` +\n `are fiction. Name the target: createRpcClient(${ref.api}, new ClientConfig('<serviceName>')); ` +\n `or, when the client is built in a shared library (no literal can sit at the call site), ` +\n `declare metadata.webpieces.callsService: '<serviceName>' on ${user}'s project.json.`,\n );\n }\n return implementers;\n }\n\n /**\n * Rebuild edges from their `\"from to type queue\"` keys. The 4th part is the queue (\"Api.method\")\n * and is empty for every non-queued edge; it is part of the KEY so two queued methods between\n * the same pair stay two edges instead of collapsing into one.\n */\n private edgesFromKeys(viaByKey: Map<string, Set<string>>): RuntimeEdge[] {\n const edges: RuntimeEdge[] = [];\n for (const key of viaByKey.keys()) {\n const parts = key.split(' ');\n const edge: RuntimeEdge = {\n from: parts[0],\n to: parts[1],\n via: Array.from(viaByKey.get(key)!).sort(),\n type: parts[2] as ApiTransport,\n };\n if (parts[3] !== undefined && parts[3] !== '') edge.queue = parts[3];\n edges.push(edge);\n }\n edges.sort(\n (a: RuntimeEdge, b: RuntimeEdge) =>\n a.from.localeCompare(b.from) ||\n a.to.localeCompare(b.to) ||\n (a.type ?? '').localeCompare(b.type ?? '') ||\n (a.queue ?? '').localeCompare(b.queue ?? ''),\n );\n return edges;\n }\n\n private buildServices(decls: ScanDecl[], edges: RuntimeEdge[]): Record<string, RuntimeService> {\n const services: Record<string, RuntimeService> = {};\n for (const decl of decls) {\n // A queued hop depends on the QUEUE, not on the peer: the producer hands off and returns,\n // so naming the consumer here would assert a coupling that does not exist (and would make\n // a service that defers work to itself look self-dependent).\n const dependsOn = Array.from(\n new Set(\n edges\n .filter((e: RuntimeEdge) => e.from === decl.name)\n .map((e: RuntimeEdge) =>\n e.queue === undefined ? e.to : `queue:${e.queue}`,\n ),\n ),\n ).sort();\n // Keys are written in this order; an undefined value is omitted by JSON.stringify, so\n // the committed JSON stays clean AND deterministic without conditional assembly.\n const service: RuntimeService = {\n level: 0,\n role: this.projects[decl.name]?.role, // labels a relation-less node for what it IS\n serviceName: this.projects[decl.name]?.serviceName,\n callsService: this.projects[decl.name]?.callsService,\n implements: decl.implementsApis.map((r: ApiRef) => r.api),\n implementsVia:\n decl.implementsVia.size > 0 ? sortedRecord(decl.implementsVia) : undefined,\n // One api used against two services is two refs but ONE api in this list.\n uses: Array.from(new Set(decl.usesApis.map((r: ApiRef) => r.api))),\n dependsOn,\n };\n services[decl.name] = service;\n // The explicit tag wins and is NOT reported as auto-hidden — somebody asked for it.\n if (this.hiddenProjects.has(decl.name)) {\n service.drawOnGraph = false;\n } else if (this.isNonParticipantServer(decl)) {\n service.drawOnGraph = false;\n this.autoHidden.push(decl.name);\n }\n }\n const levels = assignLevels(adjacencyFromEdges(Object.keys(services), edges));\n for (const name of Object.keys(services)) services[name].level = levels[name] ?? 0;\n return services;\n }\n}\n\n/**\n * Derive the runtime graph from dependencies.json's project `apiRelations` — the\n * single source of truth shared by generate + validate. `hiddenProjects`\n * (drawOnGraph:false, defaults to none) are kept in the graph but flagged so the\n * runtime visualizer omits their nodes + edges.\n */\n// webpieces-disable no-function-outside-class -- module entry point for the runtime graph derivation\nexport function deriveRuntimeGraph(\n projects: EnhancedGraph,\n hiddenProjects: Set<string> = new Set<string>(),\n apiContracts: ApiContracts = {},\n): RuntimeGraph {\n return deriveRuntimeGraphReport(projects, hiddenProjects, apiContracts).graph;\n}\n\n/**\n * The same derivation, plus the warnings it produced (every edge it had to GUESS at). Executors use\n * this form and print the warnings; `deriveRuntimeGraph` is the convenience form for callers that\n * only want the data. The warnings are deliberately kept OUT of runtime-dependencies.json — a graph\n * file that records its own doubts would just get committed and stop being read.\n */\n// webpieces-disable no-function-outside-class -- module entry point for the runtime graph derivation\nexport function deriveRuntimeGraphReport(\n projects: EnhancedGraph,\n hiddenProjects: Set<string> = new Set<string>(),\n apiContracts: ApiContracts = {},\n externalSystems: ExternalSystemDecls = {},\n): RuntimeGraphReport {\n return new RuntimeGraphDeriver(\n projects,\n hiddenProjects,\n apiContracts,\n externalSystems,\n ).assemble();\n}\n"]}
1
+ {"version":3,"file":"runtime-graph.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-graph.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Runtime Graph\n *\n * Derives the runtime microservice graph SOLELY from architecture/dependencies.json\n * (the single source of truth): each project's `apiRelations` carry which api\n * classes it implements/uses and their transport (rpc | pubsub). Both\n * `architecture:generate` and `architecture:validate-runtime-architecture` call\n * the SAME `deriveRuntimeGraph`, so the committed graph and the validated graph\n * can never diverge.\n *\n * The runtime edge Z -> X (Z depends on X at runtime) is INFERRED: Z `uses` api\n * Y and X `implements` api Y. This edge does not exist in the compile-time\n * dependencies.json (both Z and X only compile-depend on the api library Y).\n *\n * WHICH X is decided by the call site, not by \"everyone who implements Y\", in\n * priority order: (1) a literal at the call site —\n * `createRpcClient(Y, new ClientConfig('helper-fsdb', new DeployedServiceHost()))` — kept as\n * `ApiRef.targetService`; else (2) the calling project's declared `callsService`\n * (project.json metadata.webpieces.callsService), the symmetric half of\n * `serviceName` for the shared-library case where the client is built once from a\n * config field so no literal can sit at the call site; else (3) fan-out. A named\n * target from (1) or (2) is matched against each node's DECLARED `serviceName`.\n * Fanning an edge out to every implementer is catastrophic for a company-wide\n * contract registered in a shared library — it manufactures calls that cannot\n * happen, and cycles that do not exist. When a target cannot be resolved the old\n * fan-out still happens, but a warning names the call site (see\n * RuntimeGraphReport.warnings): a wrong-but-green graph is worse than a failing\n * one, so it must never degrade silently.\n */\n\nimport { sortGraphTopologically } from './graph-sorter';\nimport type { EnhancedGraph, GraphEntry } from './graph-sorter';\nimport type {\n ApiContracts,\n ApiMethodMeta,\n ApiRef,\n ApiTransport,\n ExternalSystemDecls,\n} from './api-usage/api-relations';\nimport { sortApiRefs } from './api-usage/api-relations';\nimport { RelationSink } from './runtime-graph-decls';\nimport type { ScanDecl } from './runtime-graph-decls';\nimport { dedupApiRefs, sortedQueues, sortedRecord, sortUnresolved } from './runtime-graph-sorters';\nimport { attachExternalSystems, resolveExternalSystems } from './api-usage/external-systems';\nimport type {\n RuntimeApi,\n RuntimeEdge,\n RuntimeExternalSystem,\n RuntimeGraph,\n RuntimeQueue,\n RuntimeService,\n RuntimeTrigger,\n RuntimeUnresolved,\n} from './runtime-graph-model';\nimport { toError } from '../toError';\n\n// The committed data shape lives in runtime-graph-model.ts; re-exported here so every existing\n// `from './runtime-graph'` import keeps working and there is still one obvious place to import from.\nexport type {\n RuntimeApi,\n RuntimeEdge,\n RuntimeExternalSystem,\n RuntimeGraph,\n RuntimeQueue,\n RuntimeService,\n RuntimeTrigger,\n RuntimeUnresolved,\n} from './runtime-graph-model';\nimport { adjacencyFromEdges, assignLevels } from './runtime-graph-levels';\nimport { RuntimeHostUsage } from './runtime-host-nodes';\n\n// Persistence lives in runtime-graph-io.ts; re-exported for the same reason as the model types.\nexport {\n DEFAULT_RUNTIME_GRAPH_PATH,\n saveRuntimeGraph,\n runtimeGraphFileExists,\n loadRuntimeGraph,\n serializeRuntimeGraph,\n} from './runtime-graph-io';\n\ninterface EdgeResult {\n edges: RuntimeEdge[];\n unresolved: RuntimeUnresolved[];\n queues: Record<string, RuntimeQueue>;\n}\n\n/**\n * The derived graph PLUS everything the derivation had to guess at. `warnings` is deliberately not\n * part of RuntimeGraph: it is not committed data, it is the report that stops a guessed edge from\n * passing for a derived one. Executors print it.\n */\nexport class RuntimeGraphReport {\n constructor(\n public readonly graph: RuntimeGraph,\n /** Human-readable lines naming every call site whose target the graph could not pin down. */\n public readonly warnings: string[],\n /**\n * Call sites that name a target the repo does not contain. Unlike a warning these FAIL the\n * build: the contract IS served in-repo, so the name is a typo or a stale rename, and the\n * only reason it stayed invisible is that the graph quietly fanned the edge out instead.\n * (A call to a service outside the repo never reaches here — nothing in-repo implements its\n * contract, so it is `unresolvedUses`.)\n */\n public readonly problems: string[] = [],\n /**\n * role:server nodes omitted from the DRAWING because they declare no webpieces runtime\n * package anywhere in their library closure and serve/call nothing in-repo. Neither a\n * warning nor a problem — this is intended behavior, and shouting on every clean run is how\n * warnings stop being read. Deliberately NOT persisted: a graph file that records its own\n * omissions gets committed and stops being read. Executors print it instead, so the\n * omission is always visible and never silent.\n */\n public readonly autoHidden: string[] = [],\n ) {}\n}\n\n/**\n * Derives the runtime microservice graph from architecture/dependencies.json `apiRelations`:\n * implementers × users per API, split by transport. An rpc edge is a direct call; a pubsub edge flows\n * through a queue (drawn producer → queue → consumer by the visualizer).\n *\n * Nodes are the runnable apps only — role:server / role:client. A library is NEVER a node; its\n * implements/uses (a shared client-factory or route-registration lib) are attributed to the\n * servers/clients that embed it, walked transitively over dependsOn (see collectDecls).\n */\nclass RuntimeGraphDeriver {\n /** apiClassName -> the api-lib project that owns the contract (from the apiRelations key). */\n private readonly apiOwners = new Map<string, string>();\n /** Addressable name -> the runtime node answering to it; how a targeted call resolves. */\n private readonly nodeByServiceName = new Map<string, string>();\n private readonly warnings: string[] = [];\n private readonly problems: string[] = [];\n /** role:server nodes hidden by isNonParticipantServer, in the order buildServices met them. */\n private readonly autoHidden: string[] = [];\n\n /**\n * identity -> the services that dial it and the contracts they dial it with, for every client\n * whose base URL arrives at RUNTIME. Accumulated while edges are built, then merged into the\n * graph's `externalSystems` in {@link assemble} so a partner destination is drawn as the\n * external node it is instead of vanishing between fan-out and `unresolvedUses`.\n */\n private readonly runtimeHosts = new Map<string, RuntimeHostUsage>();\n /**\n * False when NO project in the graph carries `webpiecesRuntime`, i.e. the file was written\n * before the field existed. Auto-hiding is then off ENTIRELY, so an old dependencies.json\n * renders exactly as it did — absence of the field must never be read as \"declares none\".\n * Any repo regenerated after this change has at least one webpieces project, so the guard\n * cannot wrongly suppress the feature.\n */\n private readonly markersKnown: boolean;\n\n constructor(\n private readonly projects: EnhancedGraph,\n /** Project names tagged drawOnGraph:false — kept in the JSON but flagged so the viz omits them. */\n private readonly hiddenProjects: Set<string>,\n /**\n * The committed per-contract method table from dependencies.json. Empty means the file\n * predates `apiContracts`: every pubsub edge then falls back to one unnamed queue per\n * service pair, exactly as before, instead of failing on a missing table.\n */\n private readonly apiContracts: ApiContracts = {},\n /**\n * Declared external systems from dependencies.json, keyed by identity. Empty means nothing\n * was declared, which renders exactly as it did before declarations existed.\n */\n private readonly externalSystemDecls: ExternalSystemDecls = {},\n ) {\n this.markersKnown = Object.values(projects).some(\n (entry: GraphEntry) => entry.webpiecesRuntime !== undefined,\n );\n // A node ALWAYS answers to its own module name, so a repo whose deployed names match its\n // project names needs no declaration at all — and no alias can ever redirect 'ai-chat' away\n // from the ai-chat module. Module names are therefore claimed FIRST and are unshadowable.\n for (const name of Object.keys(projects).sort()) {\n if (this.isNode(name)) this.nodeByServiceName.set(name, name);\n }\n // `serviceName` then adds the alias a repo needs when its deployed name is NOT its module\n // name (helper-svr serving 'helper-portal'). Colliding with a module is a misconfiguration,\n // not a precedence question: the alias is silently unreachable, so say so.\n for (const name of Object.keys(projects).sort()) {\n const declared = projects[name].serviceName;\n if (!this.isNode(name) || declared === undefined) continue;\n const claimant = this.nodeByServiceName.get(declared);\n if (claimant === undefined) {\n this.nodeByServiceName.set(declared, name);\n } else if (claimant !== name) {\n this.problems.push(\n `${name} declares serviceName '${declared}', but that is already the module name of ` +\n `${claimant} — the alias can never be reached. Rename one of them.`,\n );\n }\n }\n }\n\n assemble(): RuntimeGraphReport {\n const decls = this.collectDecls();\n const apis = this.buildApis(decls);\n const edgeResult = this.buildEdges(decls, apis);\n const services = this.buildServices(decls, edgeResult.edges);\n const apisObj: Record<string, RuntimeApi> = {};\n for (const api of Array.from(apis.keys()).sort()) apisObj[api] = apis.get(api)!;\n const graph: RuntimeGraph = {\n services,\n apis: apisObj,\n runtimeEdges: edgeResult.edges,\n unresolvedUses: edgeResult.unresolved,\n queues: edgeResult.queues,\n triggers: this.buildTriggers(decls),\n };\n const systems = resolveExternalSystems(this.externalSystemDecls, services);\n // Runtime destinations join the SAME table the declared vendor systems live in, so two\n // clients naming one identity converge on one node exactly as two `@externalSystem twilio`\n // contracts do, and the drift check that already watches `externalSystems` watches these too.\n for (const identity of Array.from(this.runtimeHosts.keys()).sort()) {\n const usage = this.runtimeHosts.get(identity)!;\n systems[identity] = {\n kind: 'runtime',\n label: identity,\n usedBy: [...usage.usedBy].sort(),\n apis: [...usage.apis].sort(),\n };\n }\n attachExternalSystems(graph, systems);\n return new RuntimeGraphReport(graph, this.warnings, this.problems, [...this.autoHidden].sort());\n }\n\n /**\n * The clock- and outside-driven entry points: for every contract a node IMPLEMENTS, each method\n * declared `cron` or `external` becomes a trigger pointing AT that node.\n *\n * Driven off `implements` rather than `uses` on purpose — these have no in-repo caller at all,\n * which is why they never produced an edge and stayed invisible.\n */\n private buildTriggers(decls: ScanDecl[]): RuntimeTrigger[] {\n const triggers: RuntimeTrigger[] = [];\n for (const decl of decls) {\n for (const ref of decl.implementsApis) {\n for (const method of this.methodsOf(ref.api)) {\n if (method.kind !== 'cron' && method.kind !== 'external') continue;\n const trigger: RuntimeTrigger = {\n kind: method.kind,\n api: ref.api,\n method: method.name,\n service: decl.name,\n };\n if (method.kind === 'cron') trigger.queueName = method.queueName;\n // ONLY for 'external' (a clock needs no vendor name), and only when declared —\n // a graph committed before callers were required has none and must still render.\n if (method.kind === 'external' && method.caller !== undefined)\n trigger.caller = method.caller;\n triggers.push(trigger);\n }\n }\n }\n return triggers.sort(\n (a: RuntimeTrigger, b: RuntimeTrigger) =>\n a.kind.localeCompare(b.kind) ||\n a.api.localeCompare(b.api) ||\n a.method.localeCompare(b.method) ||\n a.service.localeCompare(b.service),\n );\n }\n\n /** The committed method table for a contract, or [] when it declares none (e.g. a vendor seam). */\n private methodsOf(api: string): ApiMethodMeta[] {\n return this.apiContracts[api]?.methods ?? [];\n }\n\n /**\n * One ScanDecl per RUNTIME NODE (role:server / role:client). A node's effective\n * relations are its OWN apiRelations PLUS those of every library in its transitive\n * dependsOn closure: a lib that calls createRpcClient/addRoutes runs inside the\n * server/client that embeds it, so the relation is attributed to that node — the\n * lib is never a runtime node. Recursion stops at other nodes (each server/client\n * owns its own relations). dependsOn is transitively reduced, but reduction\n * preserves reachability, so the recursive walk still reaches every lib a node\n * embeds through libraries.\n */\n private collectDecls(): ScanDecl[] {\n const decls: ScanDecl[] = [];\n for (const name of Object.keys(this.projects).sort()) {\n if (!this.isNode(name)) continue;\n const sink = new RelationSink(name);\n this.collectEffectiveRelations(name, sink, new Set<string>([name]));\n // EVERY node gets a decl, including one with zero relations. Gating this on \"has at\n // least one implements/uses\" silently DELETED any deployed server whose work arrives\n // from outside the repo (a pull subscription): the diagram looked complete while missing\n // a running service — confidently wrong, not visibly incomplete. isNode() is the whole\n // test; drawOnGraph:false is the ONE opt-out, and such a node renders as an isolated box.\n decls.push({\n name,\n implementsApis: dedupApiRefs(sortApiRefs(sink.implementsApis)),\n usesApis: dedupApiRefs(sortApiRefs(sink.usesApis)),\n implementsVia: sink.implementsVia,\n markerVia: sink.markerVia,\n });\n }\n return decls;\n }\n\n /** True when a project is a runtime node — a server or client app (never a library). */\n private isNode(name: string): boolean {\n const role = this.projects[name]?.role;\n return role === 'server' || role === 'client';\n }\n\n /**\n * True for a role:server that speaks NO webpieces runtime package anywhere in its library\n * closure AND serves/calls no in-repo contract — a legacy Express/NestJS service sitting in the\n * same monorepo. This graph is built entirely out of webpieces contracts, so such a service can\n * only ever draw as a disconnected box: it has no edges by construction and never will.\n *\n * It stays a NODE and stays in runtime-dependencies.json (drawOnGraph:false), which is the whole\n * difference between hiding it and the silent deletion #542 removed — the data view still shows\n * a deployed role:server, only the picture drops it. Removing it from isNode() instead would\n * also drop it from nodeByServiceName, so a targeted ClientConfig('orders-manager') from a drawn\n * service would stop resolving and decay into an unresolved use: a graph that got quietly WORSE\n * while looking cleaner. The COMPILE-TIME graph (dependencies.html) still draws these projects;\n * they genuinely exist, and only the runtime drawing claims to show webpieces services.\n *\n * role:client is deliberately never auto-hidden: a browser app can legitimately declare no\n * marker at all, and nothing checks clients for presence the way checkServersPresent checks\n * servers, so there is no equivalent failure to prevent.\n */\n private isNonParticipantServer(decl: ScanDecl): boolean {\n if (!this.markersKnown) return false;\n if (this.projects[decl.name]?.role !== 'server') return false;\n if (decl.markerVia.size > 0) return false;\n // Belt-and-braces: serving or calling an in-repo contract IS participation, whatever the\n // package list says. This is what keeps a mechanism we did not anticipate from being blanked.\n return decl.implementsApis.length === 0 && decl.usesApis.length === 0;\n }\n\n /**\n * Accumulate `name`'s own api relations, then recurse through its LIBRARY deps\n * (skipping other nodes, which own their own relations). `visited` guards against\n * re-walking a lib reachable by more than one path (and any cycle).\n */\n private collectEffectiveRelations(\n name: string,\n sink: RelationSink,\n visited: Set<string>,\n ): void {\n const entry = this.projects[name];\n if (entry === undefined) return;\n sink.addMarkers(entry.webpiecesRuntime, name);\n const relations = entry.apiRelations;\n if (relations !== undefined) {\n for (const owner of Object.keys(relations).sort()) {\n for (const ref of relations[owner].implements) {\n this.apiOwners.set(ref.api, owner);\n sink.addImplements(ref, name);\n }\n for (const ref of relations[owner].uses) {\n this.apiOwners.set(ref.api, owner);\n sink.addUses(ref);\n }\n }\n }\n for (const dep of entry.dependsOn) {\n if (visited.has(dep)) continue;\n visited.add(dep);\n if (this.isNode(dep)) continue; // another server/client owns its own relations\n this.collectEffectiveRelations(dep, sink, visited);\n }\n }\n\n /** apiClassName -> { implementedBy, usedBy, type }. */\n private buildApis(decls: ScanDecl[]): Map<string, RuntimeApi> {\n const apis = new Map<string, RuntimeApi>();\n const ensure = (api: string, type: ApiTransport): RuntimeApi => {\n let entry = apis.get(api);\n if (!entry) {\n entry = { implementedBy: [], usedBy: [], type };\n apis.set(api, entry);\n }\n return entry;\n };\n for (const decl of decls) {\n for (const ref of decl.implementsApis)\n ensure(ref.api, ref.type).implementedBy.push(decl.name);\n for (const ref of decl.usesApis) ensure(ref.api, ref.type).usedBy.push(decl.name);\n }\n for (const api of apis.keys()) {\n const entry = apis.get(api)!;\n entry.implementedBy.sort();\n entry.usedBy = Array.from(new Set(entry.usedBy)).sort();\n const owner = this.apiOwners.get(api);\n if (owner !== undefined) entry.owner = owner;\n }\n return apis;\n }\n\n /** Inferred edges U -> I via api A, split by transport; plus uses with no implementer. */\n private buildEdges(decls: ScanDecl[], apis: Map<string, RuntimeApi>): EdgeResult {\n const viaByKey = new Map<string, Set<string>>();\n const unresolved: RuntimeUnresolved[] = [];\n const queues = new Map<string, RuntimeQueue>();\n for (const decl of decls) {\n for (const ref of decl.usesApis) {\n // A destination supplied at runtime is neither an in-repo implementer nor a missing\n // one, so BOTH of the branches below would misreport it — fan-out invents edges to\n // services we never call, `unresolvedUses` reads as a gap. It gets its own node.\n if (ref.runtimeHost !== undefined) {\n this.recordRuntimeHost(decl.name, ref.runtimeHost, ref.api);\n continue;\n }\n const implementers = apis.get(ref.api)?.implementedBy ?? [];\n if (implementers.length === 0) {\n // Nobody in-repo serves it. For a vendor contract that is the ANSWER, not a gap:\n // the call leaves the repo, and the viz terminates it at a dashed vendor node.\n unresolved.push({ service: decl.name, api: ref.api });\n continue;\n }\n for (const target of this.targetsFor(decl.name, ref, implementers)) {\n // A service calling ITSELF synchronously is noise; a service ENQUEUEING to\n // itself is a real, common topology (deferring its own work), and dropping it\n // was hiding the single most interesting thing about a queue.\n if (target === decl.name && ref.type !== 'pubsub') continue;\n if (ref.type === 'pubsub') {\n this.addQueuedEdges(decl.name, target, ref.api, viaByKey, queues);\n continue;\n }\n const key = `${decl.name} ${target} ${ref.type}`;\n if (!viaByKey.has(key)) viaByKey.set(key, new Set());\n viaByKey.get(key)!.add(ref.api);\n }\n }\n }\n return {\n edges: this.edgesFromKeys(viaByKey),\n unresolved: sortUnresolved(unresolved),\n queues: sortedQueues(queues),\n };\n }\n\n /**\n * One edge PER QUEUED METHOD of `api`, plus the queue each flows through.\n *\n * Per method rather than per service pair because that is the unit Cloud Tasks and Terraform\n * create: two services exchanging three queued methods run three independently-configured,\n * independently-backed-up queues, and collapsing them into one arrow hides which one is stuck.\n *\n * A contract with no committed method table (a dependencies.json predating `apiContracts`)\n * degrades to a single unnamed queue for the pair — the old behavior — rather than vanishing.\n */\n private addQueuedEdges(\n from: string,\n to: string,\n api: string,\n viaByKey: Map<string, Set<string>>,\n queues: Map<string, RuntimeQueue>,\n ): void {\n const queued = this.methodsOf(api).filter((m: ApiMethodMeta) => m.kind === 'cloudtasks');\n if (queued.length === 0) {\n const key = `${from} ${to} pubsub `;\n if (!viaByKey.has(key)) viaByKey.set(key, new Set());\n viaByKey.get(key)!.add(api);\n return;\n }\n for (const method of queued) {\n const queueKey = `${api}.${method.name}`;\n const key = `${from} ${to} pubsub ${queueKey}`;\n if (!viaByKey.has(key)) viaByKey.set(key, new Set());\n viaByKey.get(key)!.add(api);\n\n let queue = queues.get(queueKey);\n if (queue === undefined) {\n queue = {\n api,\n method: method.name,\n // Every cloudtasks method carries a queueName; the fallback covers a\n // dependencies.json written before queueName became kind-specific.\n queueName: method.queueName ?? `${api}-${method.name}`,\n producedBy: [],\n consumedBy: [],\n };\n queues.set(queueKey, queue);\n }\n if (!queue.producedBy.includes(from)) queue.producedBy.push(from);\n if (!queue.consumedBy.includes(to)) queue.consumedBy.push(to);\n }\n }\n\n /** Remember that `user` dials the runtime destination `identity` through contract `api`. */\n private recordRuntimeHost(user: string, identity: string, api: string): void {\n let usage = this.runtimeHosts.get(identity);\n if (usage === undefined) {\n usage = new RuntimeHostUsage();\n this.runtimeHosts.set(identity, usage);\n }\n usage.usedBy.add(user);\n usage.apis.add(api);\n }\n\n /**\n * WHICH implementers this one `uses` reaches. Resolution order (most specific wins):\n * 1. a literal `ClientConfig` at the call site (`ref.targetService`) — names ONE node;\n * 2. else the calling project's declared `callsService` (project.json) — for the shared-library\n * case where the literal cannot sit at the call site, it lives one indirection away in the app;\n * 3. else the historical fan-out — the only safe superset — recording WHY, so a fanned-out (i.e.\n * possibly fictional) edge can never pass for a derived one.\n * A named target (1 or 2) that resolves to no module, or to a module that does not serve the\n * contract, is a PROBLEM (fails the build), never a silent drop.\n */\n private targetsFor(user: string, ref: ApiRef, implementers: string[]): string[] {\n const literal = ref.targetService;\n if (literal !== undefined)\n return this.resolveNamedTarget(user, ref, implementers, literal, 'literal');\n\n const declared = this.callsServiceFor(user, ref.api);\n if (declared !== undefined)\n return this.resolveNamedTarget(user, ref, implementers, declared, 'callsService');\n\n return this.untargetedFanOut(user, ref, implementers);\n }\n\n /**\n * The declared `callsService` target for this node's use of `api`, or undefined when none applies.\n * A string declaration aims every untargeted use at one service; a map aims per api-class.\n */\n private callsServiceFor(user: string, api: string): string | undefined {\n const declared = this.projects[user]?.callsService;\n if (declared === undefined) return undefined;\n if (typeof declared === 'string') return declared;\n return declared[api];\n }\n\n /**\n * Resolve a NAMED target (from a call-site literal or a project-level `callsService`) to the one\n * module that serves it. A name no module answers to, or a module that does not serve the\n * contract, fails the build — the wording names which declaration to fix, `source` deciding it.\n */\n private resolveNamedTarget(\n user: string,\n ref: ApiRef,\n implementers: string[],\n wanted: string,\n source: 'literal' | 'callsService',\n ): string[] {\n const node = this.nodeByServiceName.get(wanted);\n const served = `${implementers.length} module(s) serve this contract (${implementers.join(', ')})`;\n if (node === undefined) {\n this.problems.push(\n source === 'callsService'\n ? `${user} declares metadata.webpieces.callsService '${wanted}' in its project.json, but NO ` +\n `module answers to that name. ${served}. Point callsService at a module name, or declare ` +\n `metadata.webpieces.serviceName: '${wanted}' on the module that serves \"${ref.api}\".`\n : `${user} calls \"${ref.api}\" at service '${wanted}', but NO module answers to that name. ` +\n `${served}. Either use the module name, or declare metadata.webpieces.serviceName: ` +\n `'${wanted}' on the module that serves it (and translate any environment prefix in ` +\n `ClientRegistry.setDeriver, not here).`,\n );\n return implementers;\n }\n if (!implementers.includes(node)) {\n const via =\n source === 'callsService'\n ? `${user} declares callsService '${wanted}' (module ${node})`\n : `${user} calls \"${ref.api}\" at service '${wanted}' (module ${node})`;\n this.problems.push(\n `${via}, but ${node} does NOT serve \"${ref.api}\" — ${implementers.join(', ')} do. At runtime that ` +\n `call has nothing to answer it.`,\n );\n return implementers;\n }\n return [node];\n }\n\n /**\n * A use with no literal client config. One implementer is unambiguous, so it stays silent; more\n * than one means every edge but one is fiction — exactly the failure this mechanism exists to\n * stop — so it is reported even though the graph still (conservatively) draws them all.\n */\n private untargetedFanOut(user: string, ref: ApiRef, implementers: string[]): string[] {\n if (implementers.length > 1) {\n this.warnings.push(\n `${user} uses \"${ref.api}\" with no literal client config, and ${implementers.length} services ` +\n `implement it (${implementers.join(', ')}) — an edge is drawn to EVERY one, so all but one ` +\n `are fiction. Name the target: createRpcClient(${ref.api}, new ClientConfig('<serviceName>', new DeployedServiceHost())); ` +\n `or, when the client is built in a shared library (no literal can sit at the call site), ` +\n `declare metadata.webpieces.callsService: '<serviceName>' on ${user}'s project.json.`,\n );\n }\n return implementers;\n }\n\n /**\n * Rebuild edges from their `\"from to type queue\"` keys. The 4th part is the queue (\"Api.method\")\n * and is empty for every non-queued edge; it is part of the KEY so two queued methods between\n * the same pair stay two edges instead of collapsing into one.\n */\n private edgesFromKeys(viaByKey: Map<string, Set<string>>): RuntimeEdge[] {\n const edges: RuntimeEdge[] = [];\n for (const key of viaByKey.keys()) {\n const parts = key.split(' ');\n const edge: RuntimeEdge = {\n from: parts[0],\n to: parts[1],\n via: Array.from(viaByKey.get(key)!).sort(),\n type: parts[2] as ApiTransport,\n };\n if (parts[3] !== undefined && parts[3] !== '') edge.queue = parts[3];\n edges.push(edge);\n }\n edges.sort(\n (a: RuntimeEdge, b: RuntimeEdge) =>\n a.from.localeCompare(b.from) ||\n a.to.localeCompare(b.to) ||\n (a.type ?? '').localeCompare(b.type ?? '') ||\n (a.queue ?? '').localeCompare(b.queue ?? ''),\n );\n return edges;\n }\n\n private buildServices(decls: ScanDecl[], edges: RuntimeEdge[]): Record<string, RuntimeService> {\n const services: Record<string, RuntimeService> = {};\n for (const decl of decls) {\n // A queued hop depends on the QUEUE, not on the peer: the producer hands off and returns,\n // so naming the consumer here would assert a coupling that does not exist (and would make\n // a service that defers work to itself look self-dependent).\n const dependsOn = Array.from(\n new Set(\n edges\n .filter((e: RuntimeEdge) => e.from === decl.name)\n .map((e: RuntimeEdge) =>\n e.queue === undefined ? e.to : `queue:${e.queue}`,\n ),\n ),\n ).sort();\n // Keys are written in this order; an undefined value is omitted by JSON.stringify, so\n // the committed JSON stays clean AND deterministic without conditional assembly.\n const service: RuntimeService = {\n level: 0,\n role: this.projects[decl.name]?.role, // labels a relation-less node for what it IS\n serviceName: this.projects[decl.name]?.serviceName,\n callsService: this.projects[decl.name]?.callsService,\n implements: decl.implementsApis.map((r: ApiRef) => r.api),\n implementsVia:\n decl.implementsVia.size > 0 ? sortedRecord(decl.implementsVia) : undefined,\n // One api used against two services is two refs but ONE api in this list.\n uses: Array.from(new Set(decl.usesApis.map((r: ApiRef) => r.api))),\n dependsOn,\n };\n services[decl.name] = service;\n // The explicit tag wins and is NOT reported as auto-hidden — somebody asked for it.\n if (this.hiddenProjects.has(decl.name)) {\n service.drawOnGraph = false;\n } else if (this.isNonParticipantServer(decl)) {\n service.drawOnGraph = false;\n this.autoHidden.push(decl.name);\n }\n }\n const levels = assignLevels(adjacencyFromEdges(Object.keys(services), edges));\n for (const name of Object.keys(services)) services[name].level = levels[name] ?? 0;\n return services;\n }\n}\n\n/**\n * Derive the runtime graph from dependencies.json's project `apiRelations` — the\n * single source of truth shared by generate + validate. `hiddenProjects`\n * (drawOnGraph:false, defaults to none) are kept in the graph but flagged so the\n * runtime visualizer omits their nodes + edges.\n */\n// webpieces-disable no-function-outside-class -- module entry point for the runtime graph derivation\nexport function deriveRuntimeGraph(\n projects: EnhancedGraph,\n hiddenProjects: Set<string> = new Set<string>(),\n apiContracts: ApiContracts = {},\n): RuntimeGraph {\n return deriveRuntimeGraphReport(projects, hiddenProjects, apiContracts).graph;\n}\n\n/**\n * The same derivation, plus the warnings it produced (every edge it had to GUESS at). Executors use\n * this form and print the warnings; `deriveRuntimeGraph` is the convenience form for callers that\n * only want the data. The warnings are deliberately kept OUT of runtime-dependencies.json — a graph\n * file that records its own doubts would just get committed and stop being read.\n */\n// webpieces-disable no-function-outside-class -- module entry point for the runtime graph derivation\nexport function deriveRuntimeGraphReport(\n projects: EnhancedGraph,\n hiddenProjects: Set<string> = new Set<string>(),\n apiContracts: ApiContracts = {},\n externalSystems: ExternalSystemDecls = {},\n): RuntimeGraphReport {\n return new RuntimeGraphDeriver(\n projects,\n hiddenProjects,\n apiContracts,\n externalSystems,\n ).assemble();\n}\n\n// Levels + adjacency live in runtime-graph-levels.ts; re-exported for the same reason the model\n// types and the io helpers above are — one obvious place to import the runtime graph from.\nexport { runtimeAdjacency } from './runtime-graph-levels';\n"]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Who dials ONE runtime destination, and through which contracts — the accumulator behind
3
+ * `RuntimeGraphDeriver.recordRuntimeHost`. A class rather than an object literal, per this repo's
4
+ * rule, and Sets rather than arrays because two call sites in one service naming the same partner
5
+ * identity are one arrow, not two.
6
+ */
7
+ export declare class RuntimeHostUsage {
8
+ readonly usedBy: Set<string>;
9
+ readonly apis: Set<string>;
10
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RuntimeHostUsage = void 0;
4
+ /**
5
+ * Who dials ONE runtime destination, and through which contracts — the accumulator behind
6
+ * `RuntimeGraphDeriver.recordRuntimeHost`. A class rather than an object literal, per this repo's
7
+ * rule, and Sets rather than arrays because two call sites in one service naming the same partner
8
+ * identity are one arrow, not two.
9
+ */
10
+ class RuntimeHostUsage {
11
+ usedBy = new Set();
12
+ apis = new Set();
13
+ }
14
+ exports.RuntimeHostUsage = RuntimeHostUsage;
15
+ //# sourceMappingURL=runtime-host-nodes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-host-nodes.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-host-nodes.ts"],"names":[],"mappings":";;;AAAA;;;;;GAKG;AACH,MAAa,gBAAgB;IAChB,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IAC3B,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;CACrC;AAHD,4CAGC","sourcesContent":["/**\n * Who dials ONE runtime destination, and through which contracts — the accumulator behind\n * `RuntimeGraphDeriver.recordRuntimeHost`. A class rather than an object literal, per this repo's\n * rule, and Sets rather than arrays because two call sites in one service naming the same partner\n * identity are one arrow, not two.\n */\nexport class RuntimeHostUsage {\n readonly usedBy = new Set<string>();\n readonly apis = new Set<string>();\n}\n"]}
@@ -43,6 +43,12 @@ export declare const QUEUE_LABEL_PREFIX = " |";
43
43
  export declare const QUEUE_CLASS = "wp_queue";
44
44
  /** Fill for the upright cylinder standing for an external DATASTORE (firestore, postgres, ...). */
45
45
  export declare const DATABASE_FILL = "#E1F5FE";
46
+ /**
47
+ * Fill for a destination whose ADDRESS arrives at runtime (a partner-registered webhook URL). Amber
48
+ * rather than the neutral external grey on purpose: this is the one outbound hop where the address
49
+ * itself is attacker-influenced data, and the graph exists to make it findable.
50
+ */
51
+ export declare const RUNTIME_HOST_FILL = "#FFE0B2";
46
52
  /** Shape per external-system kind. Anything unrecognised falls back to the generic dashed box. */
47
53
  export declare const EXTERNAL_SHAPES: Record<string, string>;
48
54
  /** Fill per external-system kind, paired with {@link EXTERNAL_SHAPES}. */
@@ -12,7 +12,7 @@
12
12
  * cycle-free.
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.CRON_BORDER = exports.CRON_FILL = exports.EXTERNAL_BORDER = exports.EXTERNAL_FILL = exports.EXTERNAL_FILLS = exports.EXTERNAL_SHAPES = exports.DATABASE_FILL = exports.QUEUE_CLASS = exports.QUEUE_LABEL_PREFIX = exports.QUEUE_SHAPE = exports.QUEUE_FILL = exports.LEVEL_COLORS = void 0;
15
+ exports.CRON_BORDER = exports.CRON_FILL = exports.EXTERNAL_BORDER = exports.EXTERNAL_FILL = exports.EXTERNAL_FILLS = exports.EXTERNAL_SHAPES = exports.RUNTIME_HOST_FILL = exports.DATABASE_FILL = exports.QUEUE_CLASS = exports.QUEUE_LABEL_PREFIX = exports.QUEUE_SHAPE = exports.QUEUE_FILL = exports.LEVEL_COLORS = void 0;
16
16
  exports.legendHtml = legendHtml;
17
17
  exports.LEVEL_COLORS = {
18
18
  0: '#E8F5E9',
@@ -52,12 +52,21 @@ exports.QUEUE_LABEL_PREFIX = ' |';
52
52
  exports.QUEUE_CLASS = 'wp_queue';
53
53
  /** Fill for the upright cylinder standing for an external DATASTORE (firestore, postgres, ...). */
54
54
  exports.DATABASE_FILL = '#E1F5FE';
55
+ /**
56
+ * Fill for a destination whose ADDRESS arrives at runtime (a partner-registered webhook URL). Amber
57
+ * rather than the neutral external grey on purpose: this is the one outbound hop where the address
58
+ * itself is attacker-influenced data, and the graph exists to make it findable.
59
+ */
60
+ exports.RUNTIME_HOST_FILL = '#FFE0B2';
55
61
  /** Shape per external-system kind. Anything unrecognised falls back to the generic dashed box. */
56
62
  exports.EXTERNAL_SHAPES = {
57
63
  database: 'cylinder',
58
64
  cache: 'cylinder',
59
65
  queue: 'Mrecord',
60
66
  storage: 'folder',
67
+ // A destination dialled by address rather than by name: a doubled outline, because what is on
68
+ // the far side is not one system we can name but whatever the data said.
69
+ runtime: 'doubleoctagon',
61
70
  };
62
71
  /** Fill per external-system kind, paired with {@link EXTERNAL_SHAPES}. */
63
72
  exports.EXTERNAL_FILLS = {
@@ -65,6 +74,7 @@ exports.EXTERNAL_FILLS = {
65
74
  cache: exports.DATABASE_FILL,
66
75
  queue: exports.QUEUE_FILL,
67
76
  storage: '#F3E5F5',
77
+ runtime: exports.RUNTIME_HOST_FILL,
68
78
  };
69
79
  /** Fill + border for the dashed terminal node standing for a system outside this repo. */
70
80
  exports.EXTERNAL_FILL = '#FAFAFA';
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-viz-theme.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-viz-theme.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AA2GH,gCAkCC;AA3IY,QAAA,YAAY,GAA2B;IAChD,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,SAAS;CACf,CAAC;AAEW,QAAA,UAAU,GAAG,SAAS,CAAC;AAEpC;;;;;;;;;;;;;;;;GAgBG;AACU,QAAA,WAAW,GAAG,SAAS,CAAC;AACxB,QAAA,kBAAkB,GAAG,IAAI,CAAC;AAEvC;;;;;;;;GAQG;AACU,QAAA,WAAW,GAAG,UAAU,CAAC;AAEtC,mGAAmG;AACtF,QAAA,aAAa,GAAG,SAAS,CAAC;AAEvC,kGAAkG;AACrF,QAAA,eAAe,GAA2B;IACnD,QAAQ,EAAE,UAAU;IACpB,KAAK,EAAE,UAAU;IACjB,KAAK,EAAE,SAAS;IAChB,OAAO,EAAE,QAAQ;CACpB,CAAC;AAEF,0EAA0E;AAC7D,QAAA,cAAc,GAA2B;IAClD,QAAQ,EAAE,qBAAa;IACvB,KAAK,EAAE,qBAAa;IACpB,KAAK,EAAE,kBAAU;IACjB,OAAO,EAAE,SAAS;CACrB,CAAC;AAEF,0FAA0F;AAC7E,QAAA,aAAa,GAAG,SAAS,CAAC;AAC1B,QAAA,eAAe,GAAG,SAAS,CAAC;AAEzC,iFAAiF;AACpE,QAAA,SAAS,GAAG,SAAS,CAAC;AACtB,QAAA,WAAW,GAAG,SAAS,CAAC;AAErC,MAAM,cAAc;IACP,OAAO,GACZ,kHAAkH,CAAC;IACvH;;;OAGG;IACM,KAAK,GACV,6FAA6F,kBAAU,mBAAmB;QAC1H,kEAAkE,CAAC;IAC9D,QAAQ,GACb,6FAA6F,qBAAa,mBAAmB;QAC7H,mEAAmE,CAAC;IAC/D,OAAO,GACZ,wGAAwG,CAAC;IACpG,QAAQ,GACb,8EAA8E,qBAAa,IAAI;QAC/F,WAAW,uBAAe,kCAAkC,CAAC;IACxD,IAAI,GACT,oEAAoE,iBAAS,aAAa,mBAAW,KAAK;QAC1G,8EAA8E,CAAC;IAC1E,KAAK,GACV,qGAAqG;QACrG,qDAAqD,CAAC;IACjD,MAAM,GACX,oGAAoG;QACpG,6EAA6E,CAAC;IACzE,SAAS,GACd,4EAA4E,mBAAW,wBAAwB;QAC/G,yCAAyC,mBAAW,WAAW,CAAC;CACvE;AAED;;;;GAIG;AACH,0GAA0G;AAC1G,SAAgB,UAAU;IACtB,MAAM,EAAE,GAAG,IAAI,cAAc,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,IAAY,EAAU,EAAE,CAClD,6CAA6C,MAAM,gBAAgB,IAAI,eAAe,CAAC;IAC3F,OAAO;;;;;kBAKO,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,0FAA0F,CAAC;kBAC5G,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,0SAA0S,CAAC;kBAC1T,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,iEAAiE,CAAC;kBACpF,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,oEAAoE,CAAC;kBACtF,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,ujBAAujB,CAAC;kBAC1kB,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,+DAA+D,CAAC;;;;kBAI9E,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,6FAA6F,CAAC;kBAC7G,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,yIAAyI,CAAC;kBAC1J,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,sDAAsD,CAAC;;;;;;;;;;;;;WAajF,CAAC;AACZ,CAAC","sourcesContent":["/**\n * The runtime graph's VISUAL VOCABULARY, and the legend that explains it.\n *\n * Every colour, shape and fill the drawing uses lives here, next to the legend swatches that teach\n * the reader what each one means. They are one unit on purpose: the legend's job is to be a true\n * account of the picture, and the fastest way for a legend to start lying is for it to sit in a\n * different file from the constants it describes and drift as those change.\n *\n * Imported by runtime-visualizer.ts, which owns the DRAWING (which nodes and edges to emit). This\n * module knows nothing about the graph model, which is what keeps the dependency one-way and\n * cycle-free.\n */\n\nexport const LEVEL_COLORS: Record<number, string> = {\n 0: '#E8F5E9',\n 1: '#E3F2FD',\n 2: '#FFF3E0',\n 3: '#FCE4EC',\n};\n\nexport const QUEUE_FILL = '#FFF3E0';\n\n/**\n * The queue node is an `Mrecord` whose FIRST field is empty, which draws a rounded outline with a\n * vertical cap line near one end — a cylinder lying on its side, distinguishing a queue from the\n * upright cylinder that now means a database.\n *\n * Two things here are load-bearing and easy to break:\n *\n * 1. NO surrounding `{}`. Record fields lay out along the rank direction, and this graph is\n * `rankdir=TB` (see {@link generateRuntimeDot}), where the default is horizontal — which is what\n * we want. Adding braces TOGGLES that, turning the cap line into a band across the top.\n * 2. The leading space is the empty field. The record parser trims it to nothing, which is the\n * point; it must survive as its own field, so the `|` cannot be dropped.\n *\n * Graphviz has no sideways cylinder and never has: `orientation=` is documented as rotating POLYGON\n * shapes, and `cylinder` is drawn with beziers, so it silently ignores the attribute (graphviz issue\n * #2244, open since 2022 and still reproducible on 13.0.0). This is the closest native shape.\n */\nexport const QUEUE_SHAPE = 'Mrecord';\nexport const QUEUE_LABEL_PREFIX = ' |';\n\n/**\n * Marker class stamped on every queue node. Graphviz copies `class` straight into the rendered\n * `<g class=\"node wp_queue\">`, which is how runtime-visualizer.client.ts finds these nodes and\n * redraws them as true horizontal cylinders in the browser.\n *\n * A CLASS rather than an id prefix, because queue-kind EXTERNAL systems are queues too and share the\n * `system__` id space with databases — which must stay upright. Underscored, not hyphenated: DOT\n * emits a hyphen as `&#45;`, which is harmless but needlessly surprising to anyone reading the SVG.\n */\nexport const QUEUE_CLASS = 'wp_queue';\n\n/** Fill for the upright cylinder standing for an external DATASTORE (firestore, postgres, ...). */\nexport const DATABASE_FILL = '#E1F5FE';\n\n/** Shape per external-system kind. Anything unrecognised falls back to the generic dashed box. */\nexport const EXTERNAL_SHAPES: Record<string, string> = {\n database: 'cylinder',\n cache: 'cylinder',\n queue: 'Mrecord',\n storage: 'folder',\n};\n\n/** Fill per external-system kind, paired with {@link EXTERNAL_SHAPES}. */\nexport const EXTERNAL_FILLS: Record<string, string> = {\n database: DATABASE_FILL,\n cache: DATABASE_FILL,\n queue: QUEUE_FILL,\n storage: '#F3E5F5',\n};\n\n/** Fill + border for the dashed terminal node standing for a system outside this repo. */\nexport const EXTERNAL_FILL = '#FAFAFA';\nexport const EXTERNAL_BORDER = '#9E9E9E';\n\n/** Fill + border for the clock node standing for a scheduler-driven endpoint. */\nexport const CRON_FILL = '#FFF9C4';\nexport const CRON_BORDER = '#F9A825';\n\nclass LegendSwatches {\n readonly service =\n '<svg width=\"46\" height=\"26\"><rect x=\"1\" y=\"3\" width=\"44\" height=\"20\" rx=\"7\" fill=\"#E8F5E9\" stroke=\"#333\"/></svg>';\n /**\n * A cylinder on its side — the SAME geometry runtime-visualizer.client.ts draws on the real\n * node, so the legend cannot drift from the picture it explains.\n */\n readonly queue =\n `<svg width=\"46\" height=\"26\"><path d=\"M9,5 H37 A8,8 0 0 1 37,21 H9 A8,8 0 0 1 9,5 Z\" fill=\"${QUEUE_FILL}\" stroke=\"#333\"/>` +\n '<path d=\"M9,5 A8,8 0 0 1 9,21\" fill=\"none\" stroke=\"#333\"/></svg>';\n readonly database =\n `<svg width=\"46\" height=\"26\"><path d=\"M8,7 a15,4 0 0 1 30,0 v12 a15,4 0 0 1 -30,0 z\" fill=\"${DATABASE_FILL}\" stroke=\"#333\"/>` +\n '<path d=\"M8,7 a15,4 0 0 0 30,0\" fill=\"none\" stroke=\"#333\"/></svg>';\n readonly storage =\n '<svg width=\"46\" height=\"26\"><path d=\"M2,22 V6 H16 l3,3 H44 V22 Z\" fill=\"#F3E5F5\" stroke=\"#333\"/></svg>';\n readonly external =\n `<svg width=\"46\" height=\"26\"><rect x=\"1\" y=\"3\" width=\"44\" height=\"20\" fill=\"${EXTERNAL_FILL}\" ` +\n `stroke=\"${EXTERNAL_BORDER}\" stroke-dasharray=\"4,3\"/></svg>`;\n readonly cron =\n `<svg width=\"46\" height=\"26\"><circle cx=\"23\" cy=\"13\" r=\"11\" fill=\"${CRON_FILL}\" stroke=\"${CRON_BORDER}\"/>` +\n '<text x=\"23\" y=\"18\" font-size=\"12\" text-anchor=\"middle\">&#9200;</text></svg>';\n readonly solid =\n '<svg width=\"60\" height=\"20\"><line x1=\"2\" y1=\"10\" x2=\"48\" y2=\"10\" stroke=\"#333\" stroke-width=\"1.5\"/>' +\n '<path d=\"M48,6 L57,10 L48,14 Z\" fill=\"#333\"/></svg>';\n readonly dashed =\n '<svg width=\"60\" height=\"20\"><line x1=\"2\" y1=\"10\" x2=\"48\" y2=\"10\" stroke=\"#333\" stroke-width=\"1.5\" ' +\n 'stroke-dasharray=\"5,4\"/><path d=\"M48,6 L57,10 L48,14 Z\" fill=\"#333\"/></svg>';\n readonly scheduled =\n `<svg width=\"60\" height=\"20\"><line x1=\"2\" y1=\"10\" x2=\"48\" y2=\"10\" stroke=\"${CRON_BORDER}\" stroke-width=\"1.5\"/>` +\n `<path d=\"M48,6 L57,10 L48,14 Z\" fill=\"${CRON_BORDER}\"/></svg>`;\n}\n\n/**\n * The legend. Three columns — what a box IS, what a line MEANS, how to read a box — replacing the\n * three paragraphs of prose that used to restate the picture in words. Styled after\n * {@link GraphVisualizer}'s legend so the two graphs in this repo look like one tool.\n */\n// webpieces-disable no-function-outside-class -- HTML builder, matching the sibling builders in this file\nexport function legendHtml(): string {\n const sw = new LegendSwatches();\n const item = (swatch: string, text: string): string =>\n `<div class=\"legend-item\"><span class=\"sw\">${swatch}</span><span>${text}</span></div>`;\n return `<div class=\"legend\">\n <h2>Legend</h2>\n <div class=\"legend-columns\">\n <div class=\"legend-col\">\n <h3>Node shapes &mdash; <em>what a box is</em></h3>\n ${item(sw.service, '<strong>service</strong> &mdash; a deployable in this repo; fill is its dependency level')}\n ${item(sw.queue, '<strong>queue</strong> &mdash; each <em>line</em> in the box is one Cloud Tasks queue, the unit Terraform actually creates. Queues of one contract that flow between the <em>same</em> producer and consumer share a box; every one is still named, so you can always see <em>which</em> queue is stuck.')}\n ${item(sw.database, '<strong>database</strong> &mdash; a datastore outside this repo')}\n ${item(sw.storage, '<strong>object storage</strong> &mdash; a bucket outside this repo')}\n ${item(sw.external, '<strong>external system</strong> &mdash; outside this repo; nothing here implements it. Pointing <strong>OUT</strong> = a system this repo calls (firestore, gmail). Pointing <strong>IN</strong> = an endpoint driven from outside, and the box names the <strong>CALLER</strong> (<code>twilio</code>), not our contract &mdash; the contract and method are on the arrow. One vendor is ONE box however many endpoints it posts to, and the same box whether we call it or it calls us. A dotted box labelled <code>? unknown caller</code> means the endpoint never declared one.')}\n ${item(sw.cron, '<strong>cron</strong> &mdash; a scheduler fires this endpoint')}\n </div>\n <div class=\"legend-col\">\n <h3>Lines &mdash; <em>what a call is</em></h3>\n ${item(sw.solid, '<strong>solid = rpc</strong> &mdash; the request follows the arrow, the response flows back')}\n ${item(sw.dashed, '<strong>dashed = event</strong> &mdash; asynchronous: the event flows in the direction of the arrow and returns once it is in the queue')}\n ${item(sw.scheduled, '<strong>scheduled</strong> &mdash; a cron invocation')}\n <div class=\"legend-note\"><em>Every line is labeled with the contract the call flows over. A service that enqueues to itself loops through its own queue &mdash; a queue decouples the two sides, so it is not a dependency cycle.</em></div>\n </div>\n <div class=\"legend-col\">\n <h3>Reading a box</h3>\n <pre class=\"legend-box-anatomy\">name\n(server|client, L#)\nimplements: &lt;contracts it serves&gt;\n</pre>\n <div class=\"legend-note\">A box lists only what it <strong>serves</strong>. What it <em>calls</em> is on its outgoing arrows.</div>\n <div class=\"legend-note\"><code>(via &lt;lib&gt;)</code> = served through an embedded library, not its own source.</div>\n </div>\n </div>\n </div>`;\n}\n"]}
1
+ {"version":3,"file":"runtime-viz-theme.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-viz-theme.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The runtime graph's VISUAL VOCABULARY, and the legend that explains it.\n *\n * Every colour, shape and fill the drawing uses lives here, next to the legend swatches that teach\n * the reader what each one means. They are one unit on purpose: the legend's job is to be a true\n * account of the picture, and the fastest way for a legend to start lying is for it to sit in a\n * different file from the constants it describes and drift as those change.\n *\n * Imported by runtime-visualizer.ts, which owns the DRAWING (which nodes and edges to emit). This\n * module knows nothing about the graph model, which is what keeps the dependency one-way and\n * cycle-free.\n */\n\nexport const LEVEL_COLORS: Record<number, string> = {\n 0: '#E8F5E9',\n 1: '#E3F2FD',\n 2: '#FFF3E0',\n 3: '#FCE4EC',\n};\n\nexport const QUEUE_FILL = '#FFF3E0';\n\n/**\n * The queue node is an `Mrecord` whose FIRST field is empty, which draws a rounded outline with a\n * vertical cap line near one end — a cylinder lying on its side, distinguishing a queue from the\n * upright cylinder that now means a database.\n *\n * Two things here are load-bearing and easy to break:\n *\n * 1. NO surrounding `{}`. Record fields lay out along the rank direction, and this graph is\n * `rankdir=TB` (see {@link generateRuntimeDot}), where the default is horizontal — which is what\n * we want. Adding braces TOGGLES that, turning the cap line into a band across the top.\n * 2. The leading space is the empty field. The record parser trims it to nothing, which is the\n * point; it must survive as its own field, so the `|` cannot be dropped.\n *\n * Graphviz has no sideways cylinder and never has: `orientation=` is documented as rotating POLYGON\n * shapes, and `cylinder` is drawn with beziers, so it silently ignores the attribute (graphviz issue\n * #2244, open since 2022 and still reproducible on 13.0.0). This is the closest native shape.\n */\nexport const QUEUE_SHAPE = 'Mrecord';\nexport const QUEUE_LABEL_PREFIX = ' |';\n\n/**\n * Marker class stamped on every queue node. Graphviz copies `class` straight into the rendered\n * `<g class=\"node wp_queue\">`, which is how runtime-visualizer.client.ts finds these nodes and\n * redraws them as true horizontal cylinders in the browser.\n *\n * A CLASS rather than an id prefix, because queue-kind EXTERNAL systems are queues too and share the\n * `system__` id space with databases — which must stay upright. Underscored, not hyphenated: DOT\n * emits a hyphen as `&#45;`, which is harmless but needlessly surprising to anyone reading the SVG.\n */\nexport const QUEUE_CLASS = 'wp_queue';\n\n/** Fill for the upright cylinder standing for an external DATASTORE (firestore, postgres, ...). */\nexport const DATABASE_FILL = '#E1F5FE';\n\n/**\n * Fill for a destination whose ADDRESS arrives at runtime (a partner-registered webhook URL). Amber\n * rather than the neutral external grey on purpose: this is the one outbound hop where the address\n * itself is attacker-influenced data, and the graph exists to make it findable.\n */\nexport const RUNTIME_HOST_FILL = '#FFE0B2';\n\n/** Shape per external-system kind. Anything unrecognised falls back to the generic dashed box. */\nexport const EXTERNAL_SHAPES: Record<string, string> = {\n database: 'cylinder',\n cache: 'cylinder',\n queue: 'Mrecord',\n storage: 'folder',\n // A destination dialled by address rather than by name: a doubled outline, because what is on\n // the far side is not one system we can name but whatever the data said.\n runtime: 'doubleoctagon',\n};\n\n/** Fill per external-system kind, paired with {@link EXTERNAL_SHAPES}. */\nexport const EXTERNAL_FILLS: Record<string, string> = {\n database: DATABASE_FILL,\n cache: DATABASE_FILL,\n queue: QUEUE_FILL,\n storage: '#F3E5F5',\n runtime: RUNTIME_HOST_FILL,\n};\n\n\n/** Fill + border for the dashed terminal node standing for a system outside this repo. */\nexport const EXTERNAL_FILL = '#FAFAFA';\nexport const EXTERNAL_BORDER = '#9E9E9E';\n\n/** Fill + border for the clock node standing for a scheduler-driven endpoint. */\nexport const CRON_FILL = '#FFF9C4';\nexport const CRON_BORDER = '#F9A825';\n\nclass LegendSwatches {\n readonly service =\n '<svg width=\"46\" height=\"26\"><rect x=\"1\" y=\"3\" width=\"44\" height=\"20\" rx=\"7\" fill=\"#E8F5E9\" stroke=\"#333\"/></svg>';\n /**\n * A cylinder on its side — the SAME geometry runtime-visualizer.client.ts draws on the real\n * node, so the legend cannot drift from the picture it explains.\n */\n readonly queue =\n `<svg width=\"46\" height=\"26\"><path d=\"M9,5 H37 A8,8 0 0 1 37,21 H9 A8,8 0 0 1 9,5 Z\" fill=\"${QUEUE_FILL}\" stroke=\"#333\"/>` +\n '<path d=\"M9,5 A8,8 0 0 1 9,21\" fill=\"none\" stroke=\"#333\"/></svg>';\n readonly database =\n `<svg width=\"46\" height=\"26\"><path d=\"M8,7 a15,4 0 0 1 30,0 v12 a15,4 0 0 1 -30,0 z\" fill=\"${DATABASE_FILL}\" stroke=\"#333\"/>` +\n '<path d=\"M8,7 a15,4 0 0 0 30,0\" fill=\"none\" stroke=\"#333\"/></svg>';\n readonly storage =\n '<svg width=\"46\" height=\"26\"><path d=\"M2,22 V6 H16 l3,3 H44 V22 Z\" fill=\"#F3E5F5\" stroke=\"#333\"/></svg>';\n readonly external =\n `<svg width=\"46\" height=\"26\"><rect x=\"1\" y=\"3\" width=\"44\" height=\"20\" fill=\"${EXTERNAL_FILL}\" ` +\n `stroke=\"${EXTERNAL_BORDER}\" stroke-dasharray=\"4,3\"/></svg>`;\n readonly cron =\n `<svg width=\"46\" height=\"26\"><circle cx=\"23\" cy=\"13\" r=\"11\" fill=\"${CRON_FILL}\" stroke=\"${CRON_BORDER}\"/>` +\n '<text x=\"23\" y=\"18\" font-size=\"12\" text-anchor=\"middle\">&#9200;</text></svg>';\n readonly solid =\n '<svg width=\"60\" height=\"20\"><line x1=\"2\" y1=\"10\" x2=\"48\" y2=\"10\" stroke=\"#333\" stroke-width=\"1.5\"/>' +\n '<path d=\"M48,6 L57,10 L48,14 Z\" fill=\"#333\"/></svg>';\n readonly dashed =\n '<svg width=\"60\" height=\"20\"><line x1=\"2\" y1=\"10\" x2=\"48\" y2=\"10\" stroke=\"#333\" stroke-width=\"1.5\" ' +\n 'stroke-dasharray=\"5,4\"/><path d=\"M48,6 L57,10 L48,14 Z\" fill=\"#333\"/></svg>';\n readonly scheduled =\n `<svg width=\"60\" height=\"20\"><line x1=\"2\" y1=\"10\" x2=\"48\" y2=\"10\" stroke=\"${CRON_BORDER}\" stroke-width=\"1.5\"/>` +\n `<path d=\"M48,6 L57,10 L48,14 Z\" fill=\"${CRON_BORDER}\"/></svg>`;\n}\n\n/**\n * The legend. Three columns — what a box IS, what a line MEANS, how to read a box — replacing the\n * three paragraphs of prose that used to restate the picture in words. Styled after\n * {@link GraphVisualizer}'s legend so the two graphs in this repo look like one tool.\n */\n// webpieces-disable no-function-outside-class -- HTML builder, matching the sibling builders in this file\nexport function legendHtml(): string {\n const sw = new LegendSwatches();\n const item = (swatch: string, text: string): string =>\n `<div class=\"legend-item\"><span class=\"sw\">${swatch}</span><span>${text}</span></div>`;\n return `<div class=\"legend\">\n <h2>Legend</h2>\n <div class=\"legend-columns\">\n <div class=\"legend-col\">\n <h3>Node shapes &mdash; <em>what a box is</em></h3>\n ${item(sw.service, '<strong>service</strong> &mdash; a deployable in this repo; fill is its dependency level')}\n ${item(sw.queue, '<strong>queue</strong> &mdash; each <em>line</em> in the box is one Cloud Tasks queue, the unit Terraform actually creates. Queues of one contract that flow between the <em>same</em> producer and consumer share a box; every one is still named, so you can always see <em>which</em> queue is stuck.')}\n ${item(sw.database, '<strong>database</strong> &mdash; a datastore outside this repo')}\n ${item(sw.storage, '<strong>object storage</strong> &mdash; a bucket outside this repo')}\n ${item(sw.external, '<strong>external system</strong> &mdash; outside this repo; nothing here implements it. Pointing <strong>OUT</strong> = a system this repo calls (firestore, gmail). Pointing <strong>IN</strong> = an endpoint driven from outside, and the box names the <strong>CALLER</strong> (<code>twilio</code>), not our contract &mdash; the contract and method are on the arrow. One vendor is ONE box however many endpoints it posts to, and the same box whether we call it or it calls us. A dotted box labelled <code>? unknown caller</code> means the endpoint never declared one.')}\n ${item(sw.cron, '<strong>cron</strong> &mdash; a scheduler fires this endpoint')}\n </div>\n <div class=\"legend-col\">\n <h3>Lines &mdash; <em>what a call is</em></h3>\n ${item(sw.solid, '<strong>solid = rpc</strong> &mdash; the request follows the arrow, the response flows back')}\n ${item(sw.dashed, '<strong>dashed = event</strong> &mdash; asynchronous: the event flows in the direction of the arrow and returns once it is in the queue')}\n ${item(sw.scheduled, '<strong>scheduled</strong> &mdash; a cron invocation')}\n <div class=\"legend-note\"><em>Every line is labeled with the contract the call flows over. A service that enqueues to itself loops through its own queue &mdash; a queue decouples the two sides, so it is not a dependency cycle.</em></div>\n </div>\n <div class=\"legend-col\">\n <h3>Reading a box</h3>\n <pre class=\"legend-box-anatomy\">name\n(server|client, L#)\nimplements: &lt;contracts it serves&gt;\n</pre>\n <div class=\"legend-note\">A box lists only what it <strong>serves</strong>. What it <em>calls</em> is on its outgoing arrows.</div>\n <div class=\"legend-note\"><code>(via &lt;lib&gt;)</code> = served through an embedded library, not its own source.</div>\n </div>\n </div>\n </div>`;\n}\n"]}
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Resolves the `serviceName` field written per project into
5
5
  * architecture/dependencies.json — the name a CLIENT uses to address this app at
6
- * runtime (`new ClientConfig('helper-fsdb')`), i.e. its deployed Cloud Run
6
+ * runtime (`new ClientConfig('helper-fsdb', new DeployedServiceHost())`), i.e. its deployed Cloud Run
7
7
  * service name, NOT its nx project name.
8
8
  *
9
9
  * It is DECLARED, never derived. The three naming spaces have no mechanical
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Resolves the `serviceName` field written per project into
6
6
  * architecture/dependencies.json — the name a CLIENT uses to address this app at
7
- * runtime (`new ClientConfig('helper-fsdb')`), i.e. its deployed Cloud Run
7
+ * runtime (`new ClientConfig('helper-fsdb', new DeployedServiceHost())`), i.e. its deployed Cloud Run
8
8
  * service name, NOT its nx project name.
9
9
  *
10
10
  * It is DECLARED, never derived. The three naming spaces have no mechanical