@webpieces/nx-webpieces-rules 0.4.854 → 0.4.855

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.
Files changed (88) hide show
  1. package/executors.json +5 -0
  2. package/package.json +7 -7
  3. package/src/executors/generate/executor.js +31 -55
  4. package/src/executors/generate/executor.js.map +1 -1
  5. package/src/executors/runtime-wiring-check/executor.d.ts +10 -0
  6. package/src/executors/runtime-wiring-check/executor.js +42 -0
  7. package/src/executors/runtime-wiring-check/executor.js.map +1 -0
  8. package/src/executors/runtime-wiring-check/schema.json +14 -0
  9. package/src/executors/validate-api-lib-tag/executor.js +5 -3
  10. package/src/executors/validate-api-lib-tag/executor.js.map +1 -1
  11. package/src/executors/validate-api-relations/executor.js +11 -8
  12. package/src/executors/validate-api-relations/executor.js.map +1 -1
  13. package/src/executors/validate-architecture-unchanged/executor.d.ts +1 -1
  14. package/src/executors/validate-architecture-unchanged/executor.js +22 -24
  15. package/src/executors/validate-architecture-unchanged/executor.js.map +1 -1
  16. package/src/lib/api-usage/api-contract-approval.d.ts +7 -0
  17. package/src/lib/api-usage/api-contract-approval.js +50 -0
  18. package/src/lib/api-usage/api-contract-approval.js.map +1 -0
  19. package/src/lib/api-usage/api-contract-evidence.d.ts +6 -0
  20. package/src/lib/api-usage/api-contract-evidence.js +41 -0
  21. package/src/lib/api-usage/api-contract-evidence.js.map +1 -0
  22. package/src/lib/api-usage/api-relations-validator.d.ts +13 -2
  23. package/src/lib/api-usage/api-relations-validator.js +23 -0
  24. package/src/lib/api-usage/api-relations-validator.js.map +1 -1
  25. package/src/lib/api-usage/api-relations.d.ts +3 -0
  26. package/src/lib/api-usage/api-relations.js.map +1 -1
  27. package/src/lib/api-usage/api-scanner.d.ts +40 -0
  28. package/src/lib/api-usage/api-scanner.js +7 -1
  29. package/src/lib/api-usage/api-scanner.js.map +1 -1
  30. package/src/lib/graph-comparator.js +11 -1
  31. package/src/lib/graph-comparator.js.map +1 -1
  32. package/src/lib/graph-loader.js +12 -6
  33. package/src/lib/graph-loader.js.map +1 -1
  34. package/src/lib/graph-navigation.d.ts +5 -0
  35. package/src/lib/graph-navigation.js +102 -0
  36. package/src/lib/graph-navigation.js.map +1 -0
  37. package/src/lib/graph-sorter.d.ts +2 -0
  38. package/src/lib/graph-sorter.js.map +1 -1
  39. package/src/lib/graph-visualizer.d.ts +0 -11
  40. package/src/lib/graph-visualizer.js +13 -9
  41. package/src/lib/graph-visualizer.js.map +1 -1
  42. package/src/lib/runtime-details.d.ts +13 -0
  43. package/src/lib/runtime-details.js +191 -0
  44. package/src/lib/runtime-details.js.map +1 -0
  45. package/src/lib/runtime-graph-model.d.ts +4 -1
  46. package/src/lib/runtime-graph-model.js.map +1 -1
  47. package/src/lib/runtime-graph.js +10 -4
  48. package/src/lib/runtime-graph.js.map +1 -1
  49. package/src/lib/runtime-html-page.d.ts +2 -1
  50. package/src/lib/runtime-html-page.js +6 -1
  51. package/src/lib/runtime-html-page.js.map +1 -1
  52. package/src/lib/runtime-visualizer.d.ts +0 -46
  53. package/src/lib/runtime-visualizer.js +29 -43
  54. package/src/lib/runtime-visualizer.js.map +1 -1
  55. package/src/lib/runtime-viz-theme.js +4 -3
  56. package/src/lib/runtime-viz-theme.js.map +1 -1
  57. package/src/lib/runtime-wiring/DeclarationCompileAssertions.d.ts +4 -0
  58. package/src/lib/runtime-wiring/DeclarationCompileAssertions.js +27 -0
  59. package/src/lib/runtime-wiring/DeclarationCompileAssertions.js.map +1 -0
  60. package/src/lib/runtime-wiring/approved-graph.d.ts +8 -0
  61. package/src/lib/runtime-wiring/approved-graph.js +115 -0
  62. package/src/lib/runtime-wiring/approved-graph.js.map +1 -0
  63. package/src/lib/runtime-wiring/assembler.d.ts +24 -0
  64. package/src/lib/runtime-wiring/assembler.js +116 -0
  65. package/src/lib/runtime-wiring/assembler.js.map +1 -0
  66. package/src/lib/runtime-wiring/codec.d.ts +14 -0
  67. package/src/lib/runtime-wiring/codec.js +162 -0
  68. package/src/lib/runtime-wiring/codec.js.map +1 -0
  69. package/src/lib/runtime-wiring/declaration.d.ts +68 -0
  70. package/src/lib/runtime-wiring/declaration.js +88 -0
  71. package/src/lib/runtime-wiring/declaration.js.map +1 -0
  72. package/src/lib/runtime-wiring/source-extractor.d.ts +28 -0
  73. package/src/lib/runtime-wiring/source-extractor.js +253 -0
  74. package/src/lib/runtime-wiring/source-extractor.js.map +1 -0
  75. package/src/lib/runtime-wiring/source-values.d.ts +21 -0
  76. package/src/lib/runtime-wiring/source-values.js +204 -0
  77. package/src/lib/runtime-wiring/source-values.js.map +1 -0
  78. package/src/lib/runtime-wiring/verification.d.ts +9 -0
  79. package/src/lib/runtime-wiring/verification.js +56 -0
  80. package/src/lib/runtime-wiring/verification.js.map +1 -0
  81. package/src/plugin.d.ts +0 -21
  82. package/src/plugin.js +12 -7
  83. package/src/plugin.js.map +1 -1
  84. package/src/runtime-wiring-targets.d.ts +7 -0
  85. package/src/runtime-wiring-targets.js +58 -0
  86. package/src/runtime-wiring-targets.js.map +1 -0
  87. package/src/validation-targets.js +10 -2
  88. package/src/validation-targets.js.map +1 -1
@@ -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":"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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. */\n/** The colour of a `cutLegacyCycle` edge — red, because it is drawing DEBT, not a design. */\nexport const CUT_CYCLE_COLOR = '#D32F2F';\n\n/**\n * DOT attributes appended to a `cutLegacyCycle` edge: dashed and red so it reads as debt, and\n * `constraint=false` so it does not pull on a ranking it was deliberately excluded from.\n */\nexport const CUT_CYCLE_EDGE_ATTRS =\n `, style=dashed, color=\"${CUT_CYCLE_COLOR}\", fontcolor=\"${CUT_CYCLE_COLOR}\", constraint=false`;\n\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/** 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. */\n/** The colour of a `cutLegacyCycle` edge — red, because it is drawing DEBT, not a design. */\nexport const CUT_CYCLE_COLOR = '#D32F2F';\n\n/**\n * DOT attributes appended to a `cutLegacyCycle` edge: dashed and red so it reads as debt, and\n * `constraint=false` so it does not pull on a ranking it was deliberately excluded from.\n */\nexport const CUT_CYCLE_EDGE_ATTRS = `, style=dashed, color=\"${CUT_CYCLE_COLOR}\", fontcolor=\"${CUT_CYCLE_COLOR}\", constraint=false`;\n\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 names its contracts inline or exposes them in a Uses dropdown. 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 (N)\nUses (N)\n</pre>\n <div class=\"legend-note\">Hover, focus, or click <strong>Implements</strong> and <strong>Uses</strong> for complete qualified lists. One/two API arrows show names; larger groups have a Uses dropdown.</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"]}
@@ -0,0 +1,4 @@
1
+ /** Compiled with the published tooling; invalid typed relationships cannot enter assembly. */
2
+ export declare class DeclarationCompileAssertions {
3
+ check(): void;
4
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DeclarationCompileAssertions = void 0;
4
+ const declaration_1 = require("./declaration");
5
+ /** Compiled with the published tooling; invalid typed relationships cannot enter assembly. */
6
+ class DeclarationCompileAssertions {
7
+ check() {
8
+ const identity = new declaration_1.ContractIdentity('api', 'ExampleApi');
9
+ new declaration_1.WiringRelationship(identity, new declaration_1.UsesFacts('rpc', { kind: 'service', service: 'server' }));
10
+ new declaration_1.WiringRelationship(identity, new declaration_1.ImplementsFacts('rpc'));
11
+ // @ts-expect-error A uses relationship requires an explicit target.
12
+ new declaration_1.UsesFacts('rpc');
13
+ // @ts-expect-error An implementation must not carry a client target.
14
+ new declaration_1.ImplementsFacts('rpc', { kind: 'service', service: 'server' });
15
+ // @ts-expect-error The relationship discriminator cannot bypass the target requirement.
16
+ new declaration_1.WiringRelationship(identity, { direction: 'uses', transport: 'rpc' });
17
+ const implementationWithTarget = {
18
+ direction: 'implements',
19
+ transport: 'rpc',
20
+ target: { kind: 'service', service: 'server' },
21
+ };
22
+ // @ts-expect-error Structural implementation values cannot carry client targets.
23
+ new declaration_1.WiringRelationship(identity, implementationWithTarget);
24
+ }
25
+ }
26
+ exports.DeclarationCompileAssertions = DeclarationCompileAssertions;
27
+ //# sourceMappingURL=DeclarationCompileAssertions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeclarationCompileAssertions.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-wiring/DeclarationCompileAssertions.ts"],"names":[],"mappings":";;;AAAA,+CAAiG;AAEjG,8FAA8F;AAC9F,MAAa,4BAA4B;IACrC,KAAK;QACD,MAAM,QAAQ,GAAG,IAAI,8BAAgB,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;QAC3D,IAAI,gCAAkB,CAClB,QAAQ,EACR,IAAI,uBAAS,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAC/D,CAAC;QACF,IAAI,gCAAkB,CAAC,QAAQ,EAAE,IAAI,6BAAe,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7D,oEAAoE;QACpE,IAAI,uBAAS,CAAC,KAAK,CAAC,CAAC;QACrB,qEAAqE;QACrE,IAAI,6BAAe,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;QACnE,wFAAwF;QACxF,IAAI,gCAAkB,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1E,MAAM,wBAAwB,GAAG;YAC7B,SAAS,EAAE,YAAqB;YAChC,SAAS,EAAE,KAAc;YACzB,MAAM,EAAE,EAAE,IAAI,EAAE,SAAkB,EAAE,OAAO,EAAE,QAAQ,EAAE;SAC1D,CAAC;QACF,iFAAiF;QACjF,IAAI,gCAAkB,CAAC,QAAQ,EAAE,wBAAwB,CAAC,CAAC;IAC/D,CAAC;CACJ;AAtBD,oEAsBC","sourcesContent":["import { ContractIdentity, ImplementsFacts, UsesFacts, WiringRelationship } from './declaration';\n\n/** Compiled with the published tooling; invalid typed relationships cannot enter assembly. */\nexport class DeclarationCompileAssertions {\n check(): void {\n const identity = new ContractIdentity('api', 'ExampleApi');\n new WiringRelationship(\n identity,\n new UsesFacts('rpc', { kind: 'service', service: 'server' }),\n );\n new WiringRelationship(identity, new ImplementsFacts('rpc'));\n // @ts-expect-error A uses relationship requires an explicit target.\n new UsesFacts('rpc');\n // @ts-expect-error An implementation must not carry a client target.\n new ImplementsFacts('rpc', { kind: 'service', service: 'server' });\n // @ts-expect-error The relationship discriminator cannot bypass the target requirement.\n new WiringRelationship(identity, { direction: 'uses', transport: 'rpc' });\n const implementationWithTarget = {\n direction: 'implements' as const,\n transport: 'rpc' as const,\n target: { kind: 'service' as const, service: 'server' },\n };\n // @ts-expect-error Structural implementation values cannot carry client targets.\n new WiringRelationship(identity, implementationWithTarget);\n }\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import type { EnhancedGraph } from '../graph-sorter';
2
+ import type { ProjectInfo } from '../project-info';
3
+ /** Assembly consumes approvals only. Source verification is a separate per-project build target. */
4
+ export declare class ApprovedWiringGraph {
5
+ attach(workspaceRoot: string, graph: EnhancedGraph, infos: ReadonlyMap<string, ProjectInfo>, graphPath?: string): void;
6
+ private validateTargets;
7
+ private fail;
8
+ }
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApprovedWiringGraph = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const path = tslib_1.__importStar(require("path"));
7
+ const rules_config_1 = require("@webpieces/rules-config");
8
+ const codec_1 = require("./codec");
9
+ const assembler_1 = require("./assembler");
10
+ const graph_loader_1 = require("../graph-loader");
11
+ const api_contract_files_1 = require("../api-contract-files");
12
+ /** Assembly consumes approvals only. Source verification is a separate per-project build target. */
13
+ class ApprovedWiringGraph {
14
+ attach(workspaceRoot, graph, infos, graphPath = graph_loader_1.DEFAULT_GRAPH_PATH) {
15
+ const previous = (0, graph_loader_1.loadBlessedGraph)(workspaceRoot, graphPath);
16
+ if (previous === null)
17
+ this.fail('Missing saved graph. Complete the runtime declaration migration before generating architecture.');
18
+ const declarations = new Map();
19
+ for (const project of infos) {
20
+ const name = project[0];
21
+ const info = project[1];
22
+ const tagged = info.tags.includes('webpieces') || info.tags.includes('webpieces-lib');
23
+ const existingRelations = previous.projects[name]?.apiRelations;
24
+ if (!tagged && existingRelations !== undefined)
25
+ this.fail(`${name} has saved runtime facts but is untagged. Migrate it before using declaration-only generation.`);
26
+ if (!tagged)
27
+ continue;
28
+ const file = path.join(workspaceRoot, info.root, 'runtime-deps.json');
29
+ if (!fs.existsSync(file))
30
+ this.fail(`Missing approved declaration ${file}.`);
31
+ declarations.set(name, new codec_1.RuntimeDeclarationCodec().decode(fs.readFileSync(file, 'utf8'), name));
32
+ graph[name].runtimeDeclaration = `${info.root}/runtime-deps.json`;
33
+ }
34
+ const assembler = new assembler_1.RuntimeWiringAssembler(declarations);
35
+ for (const project of declarations) {
36
+ const name = project[0];
37
+ const declaration = project[1];
38
+ if (declaration.entry === undefined)
39
+ continue;
40
+ const entry = graph[name];
41
+ if (declaration.host !== undefined && entry.serviceName !== declaration.host)
42
+ this.fail(`${name}: plan host ${declaration.host} disagrees with project serviceName ${entry.serviceName}.`);
43
+ const relations = {};
44
+ for (const resolved of assembler.assemble(name)) {
45
+ const relation = relations[resolved.owner] ?? {
46
+ kind: 'uses',
47
+ implements: [],
48
+ uses: [],
49
+ };
50
+ const ref = { api: resolved.api, type: resolved.transport };
51
+ if (resolved.conditional !== undefined)
52
+ ref.conditional = resolved.conditional;
53
+ if (!resolved.via.startsWith(`${name}#`))
54
+ ref.declaredVia = resolved.via;
55
+ if (resolved.target?.kind === 'service')
56
+ ref.targetService = resolved.target.service;
57
+ if (resolved.transport === 'pubsub' && resolved.direction === 'uses')
58
+ ref.methodsInferred = true;
59
+ relation[resolved.direction].push(ref);
60
+ relation.kind =
61
+ relation.implements.length === 0
62
+ ? 'uses'
63
+ : relation.uses.length === 0
64
+ ? 'implements'
65
+ : 'uses-implements';
66
+ relations[resolved.owner] = relation;
67
+ }
68
+ entry.apiRelations = relations;
69
+ entry.runtimeComposition = true;
70
+ }
71
+ this.validateTargets(workspaceRoot, graphPath, graph);
72
+ }
73
+ validateTargets(workspaceRoot, graphPath, graph) {
74
+ const hosts = new Map();
75
+ for (const [name, entry] of Object.entries(graph)) {
76
+ if (entry.serviceName === undefined)
77
+ continue;
78
+ if (hosts.has(entry.serviceName))
79
+ this.fail(`Duplicate service identity ${entry.serviceName}: ${hosts.get(entry.serviceName)} and ${name}.`);
80
+ hosts.set(entry.serviceName, name);
81
+ }
82
+ const saved = (0, graph_loader_1.loadBlessedGraph)(workspaceRoot, graphPath);
83
+ if (saved === null)
84
+ this.fail('Missing saved contract references for approved runtime wiring.');
85
+ const contracts = new api_contract_files_1.ApiContractFiles().load(workspaceRoot, graphPath, saved.apiContractFiles);
86
+ for (const [name, entry] of Object.entries(graph))
87
+ for (const [owner, relation] of Object.entries(entry.apiRelations ?? {})) {
88
+ for (const ref of [...relation.implements, ...relation.uses]) {
89
+ if (ref.type !== 'external' &&
90
+ (contracts[ref.api]?.owner !== owner ||
91
+ contracts[ref.api]?.apiKind !== ref.type))
92
+ this.fail(`${name}: unknown qualified contract ${owner}#${ref.api}.`);
93
+ }
94
+ for (const ref of relation.uses) {
95
+ if (ref.targetService === undefined)
96
+ continue;
97
+ if (Object.values(saved.externalSystems).some((system) => system.apis.includes(ref.api)))
98
+ continue;
99
+ const provider = hosts.get(ref.targetService);
100
+ const implemented = provider === undefined
101
+ ? undefined
102
+ : graph[provider].apiRelations?.[owner]?.implements;
103
+ if (!implemented?.some((api) => api.api === ref.api && api.type === ref.type))
104
+ this.fail(`${name}: target ${ref.targetService} does not implement ${owner}#${ref.api} (${ref.type}).`);
105
+ }
106
+ }
107
+ }
108
+ fail(message) {
109
+ throw new rules_config_1.RuleFailError('validate-runtime-architecture', message, undefined, undefined, [
110
+ new rules_config_1.Option('Migrate canonical src/wiring.ts, tag owners webpieces/webpieces-lib, and review runtime-deps.json candidates before generation. Saved visualizations remain available.', true),
111
+ ]);
112
+ }
113
+ }
114
+ exports.ApprovedWiringGraph = ApprovedWiringGraph;
115
+ //# sourceMappingURL=approved-graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approved-graph.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-wiring/approved-graph.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { Option, RuleFailError } from '@webpieces/rules-config';\nimport { RuntimeDeclarationCodec } from './codec';\nimport { RuntimeWiringAssembler } from './assembler';\nimport type { RuntimeDeclaration } from './declaration';\nimport type { EnhancedGraph } from '../graph-sorter';\nimport type { ProjectInfo } from '../project-info';\nimport type { ApiRef, ProjectApiRelations } from '../api-usage/api-relations';\nimport { loadBlessedGraph, DEFAULT_GRAPH_PATH } from '../graph-loader';\nimport { ApiContractFiles } from '../api-contract-files';\n\n/** Assembly consumes approvals only. Source verification is a separate per-project build target. */\nexport class ApprovedWiringGraph {\n attach(\n workspaceRoot: string,\n graph: EnhancedGraph,\n infos: ReadonlyMap<string, ProjectInfo>,\n graphPath: string = DEFAULT_GRAPH_PATH,\n ): void {\n const previous = loadBlessedGraph(workspaceRoot, graphPath);\n if (previous === null)\n this.fail(\n 'Missing saved graph. Complete the runtime declaration migration before generating architecture.',\n );\n const declarations = new Map<string, RuntimeDeclaration>();\n for (const project of infos) {\n const name = project[0];\n const info = project[1];\n const tagged = info.tags.includes('webpieces') || info.tags.includes('webpieces-lib');\n const existingRelations = previous.projects[name]?.apiRelations;\n if (!tagged && existingRelations !== undefined)\n this.fail(\n `${name} has saved runtime facts but is untagged. Migrate it before using declaration-only generation.`,\n );\n if (!tagged) continue;\n const file = path.join(workspaceRoot, info.root, 'runtime-deps.json');\n if (!fs.existsSync(file)) this.fail(`Missing approved declaration ${file}.`);\n declarations.set(\n name,\n new RuntimeDeclarationCodec().decode(fs.readFileSync(file, 'utf8'), name),\n );\n graph[name].runtimeDeclaration = `${info.root}/runtime-deps.json`;\n }\n const assembler = new RuntimeWiringAssembler(declarations);\n for (const project of declarations) {\n const name = project[0];\n const declaration = project[1];\n if (declaration.entry === undefined) continue;\n const entry = graph[name];\n if (declaration.host !== undefined && entry.serviceName !== declaration.host)\n this.fail(\n `${name}: plan host ${declaration.host} disagrees with project serviceName ${entry.serviceName}.`,\n );\n const relations: ProjectApiRelations = {};\n for (const resolved of assembler.assemble(name)) {\n const relation = relations[resolved.owner] ?? {\n kind: 'uses',\n implements: [],\n uses: [],\n };\n const ref: ApiRef = { api: resolved.api, type: resolved.transport };\n if (resolved.conditional !== undefined) ref.conditional = resolved.conditional;\n if (!resolved.via.startsWith(`${name}#`)) ref.declaredVia = resolved.via;\n if (resolved.target?.kind === 'service')\n ref.targetService = resolved.target.service;\n if (resolved.transport === 'pubsub' && resolved.direction === 'uses')\n ref.methodsInferred = true;\n relation[resolved.direction].push(ref);\n relation.kind =\n relation.implements.length === 0\n ? 'uses'\n : relation.uses.length === 0\n ? 'implements'\n : 'uses-implements';\n relations[resolved.owner] = relation;\n }\n entry.apiRelations = relations;\n entry.runtimeComposition = true;\n }\n this.validateTargets(workspaceRoot, graphPath, graph);\n }\n\n private validateTargets(workspaceRoot: string, graphPath: string, graph: EnhancedGraph): void {\n const hosts = new Map<string, string>();\n for (const [name, entry] of Object.entries(graph)) {\n if (entry.serviceName === undefined) continue;\n if (hosts.has(entry.serviceName))\n this.fail(\n `Duplicate service identity ${entry.serviceName}: ${hosts.get(entry.serviceName)} and ${name}.`,\n );\n hosts.set(entry.serviceName, name);\n }\n const saved = loadBlessedGraph(workspaceRoot, graphPath);\n if (saved === null)\n this.fail('Missing saved contract references for approved runtime wiring.');\n const contracts = new ApiContractFiles().load(\n workspaceRoot,\n graphPath,\n saved.apiContractFiles,\n );\n for (const [name, entry] of Object.entries(graph))\n for (const [owner, relation] of Object.entries(entry.apiRelations ?? {})) {\n for (const ref of [...relation.implements, ...relation.uses]) {\n if (\n ref.type !== 'external' &&\n (contracts[ref.api]?.owner !== owner ||\n contracts[ref.api]?.apiKind !== ref.type)\n )\n this.fail(`${name}: unknown qualified contract ${owner}#${ref.api}.`);\n }\n for (const ref of relation.uses) {\n if (ref.targetService === undefined) continue;\n if (\n Object.values(saved!.externalSystems).some((system) =>\n system.apis.includes(ref.api),\n )\n )\n continue;\n const provider = hosts.get(ref.targetService);\n const implemented =\n provider === undefined\n ? undefined\n : graph[provider].apiRelations?.[owner]?.implements;\n if (!implemented?.some((api) => api.api === ref.api && api.type === ref.type))\n this.fail(\n `${name}: target ${ref.targetService} does not implement ${owner}#${ref.api} (${ref.type}).`,\n );\n }\n }\n }\n\n private fail(message: string): never {\n throw new RuleFailError('validate-runtime-architecture', message, undefined, undefined, [\n new Option(\n 'Migrate canonical src/wiring.ts, tag owners webpieces/webpieces-lib, and review runtime-deps.json candidates before generation. Saved visualizations remain available.',\n true,\n ),\n ]);\n }\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import type { ApiTransport } from '../api-usage/api-relations';
2
+ import type { DeclaredTarget, RuntimeDeclaration } from './declaration';
3
+ export declare class ResolvedWiringRelationship {
4
+ readonly owner: string;
5
+ readonly api: string;
6
+ readonly direction: 'implements' | 'uses';
7
+ readonly transport: ApiTransport;
8
+ readonly via: string;
9
+ readonly target?: DeclaredTarget | undefined;
10
+ readonly conditional?: string | undefined;
11
+ constructor(owner: string, api: string, direction: 'implements' | 'uses', transport: ApiTransport, via: string, target?: DeclaredTarget | undefined, conditional?: string | undefined);
12
+ }
13
+ /** Pure assembly of reviewed declarations; importing a library never activates its exports. */
14
+ export declare class RuntimeWiringAssembler {
15
+ private readonly declarations;
16
+ constructor(declarations: ReadonlyMap<string, RuntimeDeclaration>);
17
+ assemble(project: string): ResolvedWiringRelationship[];
18
+ private visit;
19
+ private resolveRelationships;
20
+ private isEnabled;
21
+ private resolveTarget;
22
+ private getDeclaration;
23
+ private fail;
24
+ }
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RuntimeWiringAssembler = exports.ResolvedWiringRelationship = void 0;
4
+ const rules_config_1 = require("@webpieces/rules-config");
5
+ const declaration_1 = require("./declaration");
6
+ class ResolvedWiringRelationship {
7
+ owner;
8
+ api;
9
+ direction;
10
+ transport;
11
+ via;
12
+ target;
13
+ conditional;
14
+ constructor(owner, api, direction, transport, via, target, conditional) {
15
+ this.owner = owner;
16
+ this.api = api;
17
+ this.direction = direction;
18
+ this.transport = transport;
19
+ this.via = via;
20
+ this.target = target;
21
+ this.conditional = conditional;
22
+ }
23
+ }
24
+ exports.ResolvedWiringRelationship = ResolvedWiringRelationship;
25
+ /** Pure assembly of reviewed declarations; importing a library never activates its exports. */
26
+ class RuntimeWiringAssembler {
27
+ declarations;
28
+ constructor(declarations) {
29
+ this.declarations = declarations;
30
+ }
31
+ assemble(project) {
32
+ const declaration = this.getDeclaration(project);
33
+ if (declaration.entry === undefined)
34
+ this.fail(`${project} has no approved app entry.`);
35
+ return this.visit(newSelection(project, declaration.entry), new Set());
36
+ }
37
+ visit(selection, stack) {
38
+ const key = `${selection.project}#${selection.exportedName}`;
39
+ if (stack.has(key))
40
+ this.fail(`Runtime composition cycle: ${[...stack, key].join(' -> ')}`);
41
+ const declaration = this.getDeclaration(selection.project);
42
+ const exported = Object.hasOwn(declaration.exports, selection.exportedName)
43
+ ? declaration.exports[selection.exportedName]
44
+ : undefined;
45
+ if (exported === undefined)
46
+ this.fail(`Missing approved export ${key}.`);
47
+ const active = new Set([...stack, key]);
48
+ const relationships = this.resolveRelationships(exported, selection);
49
+ for (const child of exported.selections) {
50
+ const targets = {};
51
+ for (const name of Object.keys(child.targets))
52
+ targets[name] = this.resolveTarget(child.targets[name], selection);
53
+ const next = newSelection(child.project, child.exportedName, targets, {
54
+ ...selection.policies,
55
+ ...child.policies,
56
+ });
57
+ relationships.push(...this.visit(next, active));
58
+ }
59
+ return relationships;
60
+ }
61
+ resolveRelationships(exported, selection) {
62
+ const result = [];
63
+ for (const relationship of exported.relationships) {
64
+ if (!this.isEnabled(relationship, selection))
65
+ continue;
66
+ const target = relationship.target === undefined
67
+ ? undefined
68
+ : this.resolveTarget(relationship.target, selection);
69
+ if (target?.kind === 'service' &&
70
+ target.contract !== undefined &&
71
+ (target.contract.project !== relationship.contract.project ||
72
+ target.contract.exportedName !== relationship.contract.exportedName))
73
+ this.fail(`Target capability ${target.contract.project}#${target.contract.exportedName} disagrees with ${relationship.contract.project}#${relationship.contract.exportedName}.`);
74
+ if (relationship.direction === 'uses' && target === undefined)
75
+ this.fail(`Missing target for ${relationship.contract.exportedName}.`);
76
+ result.push(new ResolvedWiringRelationship(relationship.contract.project, relationship.contract.exportedName, relationship.direction, relationship.transport, `${selection.project}#${selection.exportedName}`, target, relationship.policy !== undefined &&
77
+ selection.policies[relationship.policy] === 'runtime'
78
+ ? `${selection.project}#${selection.exportedName}:${relationship.policy}`
79
+ : undefined));
80
+ }
81
+ return result;
82
+ }
83
+ isEnabled(relationship, selection) {
84
+ if (relationship.policy === undefined)
85
+ return true;
86
+ const decision = selection.policies[relationship.policy];
87
+ if (decision === undefined)
88
+ this.fail(`Missing explicit policy ${relationship.policy} in ${selection.project}#${selection.exportedName}.`);
89
+ return decision === 'runtime' || decision === true;
90
+ }
91
+ resolveTarget(target, selection) {
92
+ if (target.kind !== 'parameter')
93
+ return target;
94
+ const argument = selection.targets[target.parameter];
95
+ if (argument === undefined || argument.kind === 'parameter')
96
+ this.fail(`Unresolved target parameter ${target.parameter} in ${selection.project}#${selection.exportedName}.`);
97
+ return argument;
98
+ }
99
+ getDeclaration(project) {
100
+ const declaration = this.declarations.get(project);
101
+ if (declaration === undefined)
102
+ this.fail(`Missing approved runtime-deps.json for ${project}.`);
103
+ return declaration;
104
+ }
105
+ fail(message) {
106
+ throw new rules_config_1.RuleFailError('validate-runtime-architecture', message, undefined, undefined, [
107
+ new rules_config_1.Option('Migrate src/wiring.ts and review its runtime-deps.json before generating the graph.', true),
108
+ ]);
109
+ }
110
+ }
111
+ exports.RuntimeWiringAssembler = RuntimeWiringAssembler;
112
+ // webpieces-disable no-function-outside-class -- construction helper for the pure assembly entry point
113
+ function newSelection(project, exportedName, targets = {}, policies = {}) {
114
+ return new declaration_1.WiringSelection(project, exportedName, targets, policies);
115
+ }
116
+ //# sourceMappingURL=assembler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assembler.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-wiring/assembler.ts"],"names":[],"mappings":"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","sourcesContent":["import { Option, RuleFailError } from '@webpieces/rules-config';\nimport type { ApiTransport } from '../api-usage/api-relations';\nimport type {\n DeclaredTarget,\n RuntimeDeclaration,\n WiringExport,\n WiringRelationship,\n} from './declaration';\nimport { WiringSelection } from './declaration';\n\nexport class ResolvedWiringRelationship {\n constructor(\n public readonly owner: string,\n public readonly api: string,\n public readonly direction: 'implements' | 'uses',\n public readonly transport: ApiTransport,\n public readonly via: string,\n public readonly target?: DeclaredTarget,\n public readonly conditional?: string,\n ) {}\n}\n\n/** Pure assembly of reviewed declarations; importing a library never activates its exports. */\nexport class RuntimeWiringAssembler {\n constructor(private readonly declarations: ReadonlyMap<string, RuntimeDeclaration>) {}\n\n assemble(project: string): ResolvedWiringRelationship[] {\n const declaration = this.getDeclaration(project);\n if (declaration.entry === undefined) this.fail(`${project} has no approved app entry.`);\n return this.visit(newSelection(project, declaration.entry!), new Set<string>());\n }\n\n private visit(selection: WiringSelection, stack: Set<string>): ResolvedWiringRelationship[] {\n const key = `${selection.project}#${selection.exportedName}`;\n if (stack.has(key)) this.fail(`Runtime composition cycle: ${[...stack, key].join(' -> ')}`);\n const declaration = this.getDeclaration(selection.project);\n const exported = Object.hasOwn(declaration.exports, selection.exportedName)\n ? declaration.exports[selection.exportedName]\n : undefined;\n if (exported === undefined) this.fail(`Missing approved export ${key}.`);\n const active = new Set([...stack, key]);\n const relationships = this.resolveRelationships(exported!, selection);\n for (const child of exported!.selections) {\n const targets: Record<string, DeclaredTarget> = {};\n for (const name of Object.keys(child.targets))\n targets[name] = this.resolveTarget(child.targets[name], selection);\n const next = newSelection(child.project, child.exportedName, targets, {\n ...selection.policies,\n ...child.policies,\n });\n relationships.push(...this.visit(next, active));\n }\n return relationships;\n }\n\n private resolveRelationships(\n exported: WiringExport,\n selection: WiringSelection,\n ): ResolvedWiringRelationship[] {\n const result: ResolvedWiringRelationship[] = [];\n for (const relationship of exported.relationships) {\n if (!this.isEnabled(relationship, selection)) continue;\n const target =\n relationship.target === undefined\n ? undefined\n : this.resolveTarget(relationship.target, selection);\n if (\n target?.kind === 'service' &&\n target.contract !== undefined &&\n (target.contract.project !== relationship.contract.project ||\n target.contract.exportedName !== relationship.contract.exportedName)\n )\n this.fail(\n `Target capability ${target.contract.project}#${target.contract.exportedName} disagrees with ${relationship.contract.project}#${relationship.contract.exportedName}.`,\n );\n if (relationship.direction === 'uses' && target === undefined)\n this.fail(`Missing target for ${relationship.contract.exportedName}.`);\n result.push(\n new ResolvedWiringRelationship(\n relationship.contract.project,\n relationship.contract.exportedName,\n relationship.direction,\n relationship.transport,\n `${selection.project}#${selection.exportedName}`,\n target,\n relationship.policy !== undefined &&\n selection.policies[relationship.policy] === 'runtime'\n ? `${selection.project}#${selection.exportedName}:${relationship.policy}`\n : undefined,\n ),\n );\n }\n return result;\n }\n\n private isEnabled(relationship: WiringRelationship, selection: WiringSelection): boolean {\n if (relationship.policy === undefined) return true;\n const decision = selection.policies[relationship.policy];\n if (decision === undefined)\n this.fail(\n `Missing explicit policy ${relationship.policy} in ${selection.project}#${selection.exportedName}.`,\n );\n return decision === 'runtime' || decision === true;\n }\n\n private resolveTarget(target: DeclaredTarget, selection: WiringSelection): DeclaredTarget {\n if (target.kind !== 'parameter') return target;\n const argument = selection.targets[target.parameter];\n if (argument === undefined || argument.kind === 'parameter')\n this.fail(\n `Unresolved target parameter ${target.parameter} in ${selection.project}#${selection.exportedName}.`,\n );\n return argument!;\n }\n\n private getDeclaration(project: string): RuntimeDeclaration {\n const declaration = this.declarations.get(project);\n if (declaration === undefined)\n this.fail(`Missing approved runtime-deps.json for ${project}.`);\n return declaration!;\n }\n\n private fail(message: string): never {\n throw new RuleFailError('validate-runtime-architecture', message, undefined, undefined, [\n new Option(\n 'Migrate src/wiring.ts and review its runtime-deps.json before generating the graph.',\n true,\n ),\n ]);\n }\n}\n\n// webpieces-disable no-function-outside-class -- construction helper for the pure assembly entry point\nfunction newSelection(\n project: string,\n exportedName: string,\n targets: Record<string, DeclaredTarget> = {},\n policies: Record<string, boolean | 'runtime'> = {},\n): WiringSelection {\n return new WiringSelection(project, exportedName, targets, policies);\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import { RuntimeDeclaration } from './declaration';
2
+ /** JSON is validated before it is allowed to influence graph assembly. */
3
+ export declare class RuntimeDeclarationCodec {
4
+ decode(text: string, expectedProject: string): RuntimeDeclaration;
5
+ private parse;
6
+ private exported;
7
+ private relationship;
8
+ private selection;
9
+ private target;
10
+ private record;
11
+ private array;
12
+ private string;
13
+ private fail;
14
+ }
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RuntimeDeclarationCodec = void 0;
4
+ const toError_1 = require("../../toError");
5
+ const rules_config_1 = require("@webpieces/rules-config");
6
+ const declaration_1 = require("./declaration");
7
+ const api_relations_1 = require("../api-usage/api-relations");
8
+ /** JSON is validated before it is allowed to influence graph assembly. */
9
+ class RuntimeDeclarationCodec {
10
+ decode(text, expectedProject) {
11
+ const root = this.record(this.parse(text, expectedProject), [
12
+ 'schemaVersion',
13
+ 'project',
14
+ 'framework',
15
+ 'exports',
16
+ 'entry',
17
+ 'host',
18
+ ]);
19
+ if (root['schemaVersion'] !== 1)
20
+ this.fail('Unsupported runtime-deps schemaVersion; migrate to version 1.');
21
+ if (root['project'] !== expectedProject)
22
+ this.fail(`Expected project ${expectedProject}, found ${String(root['project'])}.`);
23
+ const framework = this.string(root['framework']);
24
+ if (framework !== 'node' && framework !== 'angular' && framework !== 'browser')
25
+ this.fail(`Unsupported framework ${framework}.`);
26
+ const exports = {};
27
+ for (const [name, value] of Object.entries(this.record(root['exports'])))
28
+ exports[name] = this.exported(value);
29
+ return new declaration_1.RuntimeDeclaration(expectedProject, framework, exports, root['entry'] === undefined ? undefined : this.string(root['entry']), root['host'] === undefined ? undefined : this.string(root['host']));
30
+ }
31
+ // webpieces-disable no-any-unknown -- JSON syntax boundary; fields are narrowed by decode
32
+ parse(text, project) {
33
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
34
+ try {
35
+ return JSON.parse(text);
36
+ }
37
+ catch (err) {
38
+ const error = (0, toError_1.toError)(err);
39
+ this.fail(`${project}/runtime-deps.json contains invalid JSON.`, error);
40
+ }
41
+ }
42
+ // webpieces-disable no-any-unknown -- untrusted JSON is narrowed and validated at the input boundary
43
+ exported(value) {
44
+ const exported = this.record(value, ['kind', 'relationships', 'selections']);
45
+ const kind = this.string(exported['kind']);
46
+ if (kind !== 'binding' &&
47
+ kind !== 'routing' &&
48
+ kind !== 'providers' &&
49
+ kind !== 'plan' &&
50
+ kind !== 'external')
51
+ this.fail(`Invalid export kind ${kind}.`);
52
+ return new declaration_1.WiringExport(kind, this.array(exported['relationships']).map((value) => this.relationship(value)), this.array(exported['selections']).map((value) => this.selection(value)));
53
+ }
54
+ // webpieces-disable no-any-unknown -- untrusted relationship JSON is narrowed before construction
55
+ relationship(value) {
56
+ const relation = this.record(value, [
57
+ 'contract',
58
+ 'direction',
59
+ 'transport',
60
+ 'target',
61
+ 'policy',
62
+ ]);
63
+ const contract = this.record(relation['contract'], ['project', 'exportedName']);
64
+ const direction = this.string(relation['direction']);
65
+ const transport = this.string(relation['transport']);
66
+ if (direction !== 'implements' && direction !== 'uses')
67
+ this.fail(`Invalid direction ${direction}.`);
68
+ if (transport !== 'rpc' && transport !== 'pubsub' && transport !== 'external')
69
+ this.fail(`Invalid transport ${transport}.`);
70
+ const target = relation['target'] === undefined ? undefined : this.target(relation['target']);
71
+ const policy = relation['policy'] === undefined ? undefined : this.string(relation['policy']);
72
+ if (direction === 'uses' && target === undefined)
73
+ this.fail('Uses requires an explicit target.');
74
+ if (direction === 'implements' && target !== undefined)
75
+ this.fail('Implements cannot declare a client target.');
76
+ return new declaration_1.WiringRelationship(new declaration_1.ContractIdentity(this.string(contract['project']), this.string(contract['exportedName'])), direction === 'uses'
77
+ ? new declaration_1.UsesFacts(transport, target, policy)
78
+ : new declaration_1.ImplementsFacts(transport, policy));
79
+ }
80
+ // webpieces-disable no-any-unknown -- untrusted selection arguments are validated before substitution
81
+ selection(value) {
82
+ const selection = this.record(value, ['project', 'exportedName', 'targets', 'policies']);
83
+ const targets = {};
84
+ const policies = {};
85
+ for (const [name, target] of Object.entries(this.record(selection['targets'])))
86
+ targets[name] = this.target(target);
87
+ for (const [name, policy] of Object.entries(this.record(selection['policies']))) {
88
+ if (typeof policy !== 'boolean' && policy !== 'runtime')
89
+ this.fail(`Policy ${name} must be explicitly true, false, or runtime.`);
90
+ policies[name] = policy;
91
+ }
92
+ return new declaration_1.WiringSelection(this.string(selection['project']), this.string(selection['exportedName']), targets, policies);
93
+ }
94
+ // webpieces-disable no-any-unknown -- target discriminant controls the exact accepted JSON shape
95
+ target(value) {
96
+ const target = this.record(value);
97
+ switch (target['kind']) {
98
+ case 'service': {
99
+ this.record(value, ['kind', 'service', 'contract']);
100
+ const contract = target['contract'] === undefined
101
+ ? undefined
102
+ : this.record(target['contract'], ['project', 'exportedName']);
103
+ return {
104
+ kind: 'service',
105
+ service: this.string(target['service']),
106
+ contract: contract === undefined
107
+ ? undefined
108
+ : new declaration_1.ContractIdentity(this.string(contract['project']), this.string(contract['exportedName'])),
109
+ };
110
+ }
111
+ case 'parameter':
112
+ this.record(value, ['kind', 'parameter']);
113
+ return { kind: 'parameter', parameter: this.string(target['parameter']) };
114
+ case 'unknown':
115
+ this.record(value, ['kind', 'reason']);
116
+ return { kind: 'unknown', reason: this.string(target['reason']) };
117
+ case 'external': {
118
+ this.record(value, ['kind', 'system']);
119
+ const system = this.record(target['system'], ['kind', 'label']);
120
+ const kind = this.string(system['kind']);
121
+ if (!(0, api_relations_1.isExternalSystemKind)(kind))
122
+ this.fail(`Invalid external system kind ${kind}.`);
123
+ return { kind: 'external', system: { kind, label: this.string(system['label']) } };
124
+ }
125
+ default:
126
+ return this.fail('Targets require an explicit service, parameter, external, or unknown kind.');
127
+ }
128
+ }
129
+ // webpieces-disable no-any-unknown -- JSON record boundary; never a runtime topology type
130
+ record(value, allowed) {
131
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
132
+ this.fail('Expected a JSON object.');
133
+ const record = value;
134
+ for (const key of Object.keys(record))
135
+ if (['__proto__', 'constructor', 'prototype'].includes(key))
136
+ this.fail(`Reserved runtime-deps field ${key}.`);
137
+ if (allowed !== undefined)
138
+ for (const key of Object.keys(record))
139
+ if (!allowed.includes(key))
140
+ this.fail(`Unknown runtime-deps field ${key}.`);
141
+ return record;
142
+ }
143
+ // webpieces-disable no-any-unknown -- JSON array boundary
144
+ array(value) {
145
+ if (!Array.isArray(value))
146
+ this.fail('Expected a JSON array.');
147
+ return value;
148
+ }
149
+ // webpieces-disable no-any-unknown -- JSON scalar boundary
150
+ string(value) {
151
+ if (typeof value !== 'string' || value.trim() === '')
152
+ this.fail('Expected a non-empty string.');
153
+ return value;
154
+ }
155
+ fail(message, cause) {
156
+ throw new rules_config_1.RuleFailError('validate-runtime-architecture', message, undefined, undefined, [
157
+ new rules_config_1.Option('Review and correct runtime-deps.json using the versioned runtime wiring schema.', true),
158
+ ], undefined, cause);
159
+ }
160
+ }
161
+ exports.RuntimeDeclarationCodec = RuntimeDeclarationCodec;
162
+ //# sourceMappingURL=codec.js.map