@webpieces/nx-webpieces-rules 0.4.700 → 0.4.702
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/executors/generate/executor.js +5 -1
- package/src/executors/generate/executor.js.map +1 -1
- package/src/executors/validate-runtime-architecture/executor.d.ts +10 -4
- package/src/executors/validate-runtime-architecture/executor.js +46 -35
- package/src/executors/validate-runtime-architecture/executor.js.map +1 -1
- package/src/lib/api-usage/api-ast.d.ts +1 -16
- package/src/lib/api-usage/api-ast.js +1 -42
- package/src/lib/api-usage/api-ast.js.map +1 -1
- package/src/lib/api-usage/api-relations.d.ts +1 -17
- package/src/lib/api-usage/api-relations.js +10 -2
- package/src/lib/api-usage/api-relations.js.map +1 -1
- package/src/lib/api-usage/api-scanner.d.ts +1 -1
- package/src/lib/api-usage/api-scanner.js +1 -10
- package/src/lib/api-usage/api-scanner.js.map +1 -1
- package/src/lib/cut-legacy-cycle-resolver.d.ts +47 -0
- package/src/lib/cut-legacy-cycle-resolver.js +68 -0
- package/src/lib/cut-legacy-cycle-resolver.js.map +1 -0
- package/src/lib/graph-loader.js +3 -0
- package/src/lib/graph-loader.js.map +1 -1
- package/src/lib/graph-metadata.js +10 -0
- package/src/lib/graph-metadata.js.map +1 -1
- package/src/lib/graph-sorter.d.ts +8 -1
- package/src/lib/graph-sorter.js.map +1 -1
- package/src/lib/runtime-config.d.ts +0 -7
- package/src/lib/runtime-config.js +0 -5
- package/src/lib/runtime-config.js.map +1 -1
- package/src/lib/runtime-cycles.d.ts +10 -12
- package/src/lib/runtime-cycles.js +11 -14
- package/src/lib/runtime-cycles.js.map +1 -1
- package/src/lib/runtime-graph-levels.d.ts +52 -6
- package/src/lib/runtime-graph-levels.js +118 -19
- package/src/lib/runtime-graph-levels.js.map +1 -1
- package/src/lib/runtime-graph-model.d.ts +8 -1
- package/src/lib/runtime-graph-model.js.map +1 -1
- package/src/lib/runtime-graph.d.ts +1 -1
- package/src/lib/runtime-graph.js +10 -41
- package/src/lib/runtime-graph.js.map +1 -1
- package/src/lib/runtime-visualizer.js +7 -3
- package/src/lib/runtime-visualizer.js.map +1 -1
- package/src/lib/runtime-viz-theme.d.ts +7 -0
- package/src/lib/runtime-viz-theme.js +8 -1
- package/src/lib/runtime-viz-theme.js.map +1 -1
- package/src/lib/service-name-resolver.d.ts +1 -1
- package/src/lib/service-name-resolver.js +1 -1
- package/src/lib/service-name-resolver.js.map +1 -1
- package/src/lib/runtime-host-nodes.d.ts +0 -10
- package/src/lib/runtime-host-nodes.js +0 -15
- package/src/lib/runtime-host-nodes.js.map +0 -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 `-`, 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\">⏰</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 — <em>what a box is</em></h3>\n ${item(sw.service, '<strong>service</strong> — a deployable in this repo; fill is its dependency level')}\n ${item(sw.queue, '<strong>queue</strong> — 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> — a datastore outside this repo')}\n ${item(sw.storage, '<strong>object storage</strong> — a bucket outside this repo')}\n ${item(sw.external, '<strong>external system</strong> — 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 — 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> — a scheduler fires this endpoint')}\n </div>\n <div class=\"legend-col\">\n <h3>Lines — <em>what a call is</em></h3>\n ${item(sw.solid, '<strong>solid = rpc</strong> — the request follows the arrow, the response flows back')}\n ${item(sw.dashed, '<strong>dashed = event</strong> — 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> — 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 — 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: <contracts it serves>\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 <lib>)</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 `-`, 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\">⏰</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 — <em>what a box is</em></h3>\n ${item(sw.service, '<strong>service</strong> — a deployable in this repo; fill is its dependency level')}\n ${item(sw.queue, '<strong>queue</strong> — 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> — a datastore outside this repo')}\n ${item(sw.storage, '<strong>object storage</strong> — a bucket outside this repo')}\n ${item(sw.external, '<strong>external system</strong> — 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 — 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> — a scheduler fires this endpoint')}\n </div>\n <div class=\"legend-col\">\n <h3>Lines — <em>what a call is</em></h3>\n ${item(sw.solid, '<strong>solid = rpc</strong> — the request follows the arrow, the response flows back')}\n ${item(sw.dashed, '<strong>dashed = event</strong> — 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> — 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 — 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: <contracts it serves>\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 <lib>)</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'
|
|
6
|
+
* runtime (`new ClientConfig('helper-fsdb')`), 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'
|
|
7
|
+
* runtime (`new ClientConfig('helper-fsdb')`), 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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service-name-resolver.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/service-name-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Service Name Resolver\n *\n * Resolves the `serviceName` field written per project into\n * architecture/dependencies.json — the name a CLIENT uses to address this app at\n * runtime (`new ClientConfig('helper-fsdb', new DeployedServiceHost())`), i.e. its deployed Cloud Run\n * service name, NOT its nx project name.\n *\n * It is DECLARED, never derived. The three naming spaces have no mechanical\n * relationship, and any strip-the-suffix rule gets it wrong somewhere:\n *\n * nx project | serviceName | Cloud Run\n * helper-svr | helper-portal | helper-portal\n * helper-fsdb-svr | helper-fsdb | helper-fsdb\n * lang-server | lang | lang\n *\n * Declared in the project's own project.json, next to the code it names:\n *\n * { \"metadata\": { \"webpieces\": { \"serviceName\": \"helper-fsdb\" } } }\n *\n * Absent is legal and common — a library or a browser app is never addressed by\n * name. A node WITHOUT one simply cannot be the resolved target of a targeted\n * client call, and the runtime graph says so out loud rather than guessing.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { ProjectInfo } from './project-info';\nimport { toError } from '../toError';\n\n/** The project.json key path holding the declared name: metadata.webpieces.serviceName. */\nexport const SERVICE_NAME_METADATA_PATH = 'metadata.webpieces.serviceName';\n\n/** The project.json key path holding a client's declared target: metadata.webpieces.callsService. */\nexport const CALLS_SERVICE_METADATA_PATH = 'metadata.webpieces.callsService';\n\nexport class ServiceNameResolution {\n constructor(\n /** Declared service name, or null when none is declared (or resolution failed). */\n public readonly serviceName: string | null,\n /** Problem description when the declaration is present but unusable, otherwise null. */\n public readonly problem: string | null,\n ) {}\n}\n\n/**\n * Read `metadata.webpieces.serviceName` from the project's project.json. A missing file, a missing\n * key, or an unparseable file all resolve to \"not declared\" — only a PRESENT-but-wrong value (empty\n * or not a string) is a problem, because that is a typo the author wants told about.\n */\n// webpieces-disable no-function-outside-class -- pure resolver, mirrors resolveRole/resolveDrawOnGraph\nexport function resolveServiceName(info: ProjectInfo, workspaceRoot: string): ServiceNameResolution {\n const raw = readWebpiecesField(path.join(workspaceRoot, info.root, 'project.json'), 'serviceName');\n if (raw === undefined) return new ServiceNameResolution(null, null);\n if (typeof raw !== 'string' || raw.trim().length === 0) {\n return new ServiceNameResolution(\n null,\n `${info.name}: ${SERVICE_NAME_METADATA_PATH} in project.json must be a non-empty string`,\n );\n }\n return new ServiceNameResolution(raw.trim(), null);\n}\n\n/**\n * A client's declared target — the service it addresses when the call site cannot carry a literal\n * `ClientConfig` (the client is built in a SHARED library from a config field, so the literal lives\n * in the app, not the lib, one indirection away). Symmetric to `serviceName`: the IMPLEMENTING side\n * declares the name it answers to, the CALLING side declares the name it calls.\n *\n * Two shapes:\n * - a single string — every untargeted `uses` in this project aims at that one service (the common\n * case: most clients talk to exactly one server);\n * - an `{ apiClassName: serviceName }` map — the mixed case, a client that genuinely calls several.\n */\nexport class CallsServiceResolution {\n constructor(\n /** Declared target(s), or null when none is declared (or resolution failed). */\n public readonly callsService: string | Record<string, string> | null,\n /** Problem description when the declaration is present but unusable, otherwise null. */\n public readonly problem: string | null,\n ) {}\n}\n\n/**\n * Read `metadata.webpieces.callsService` from the project's project.json. A missing file, a missing\n * key, or an unparseable file all resolve to \"not declared\". A PRESENT value must be either a\n * non-empty string or a non-empty object of non-empty string values — anything else is a typo the\n * author wants told about.\n */\n// webpieces-disable no-function-outside-class -- pure resolver, mirrors resolveServiceName\nexport function resolveCallsService(info: ProjectInfo, workspaceRoot: string): CallsServiceResolution {\n const raw = readWebpiecesField(path.join(workspaceRoot, info.root, 'project.json'), 'callsService');\n if (raw === undefined) return new CallsServiceResolution(null, null);\n\n if (typeof raw === 'string') {\n if (raw.trim().length === 0) {\n return new CallsServiceResolution(\n null,\n `${info.name}: ${CALLS_SERVICE_METADATA_PATH} in project.json must be a non-empty string`,\n );\n }\n return new CallsServiceResolution(raw.trim(), null);\n }\n\n if (isPlainRecord(raw)) {\n const apis = Object.keys(raw);\n if (apis.length === 0) {\n return new CallsServiceResolution(\n null,\n `${info.name}: ${CALLS_SERVICE_METADATA_PATH} map in project.json must have at least one entry`,\n );\n }\n const map: Record<string, string> = {};\n for (const api of apis) {\n const target = raw[api];\n if (typeof target !== 'string' || target.trim().length === 0) {\n return new CallsServiceResolution(\n null,\n `${info.name}: ${CALLS_SERVICE_METADATA_PATH}['${api}'] in project.json must be a ` +\n `non-empty string (an api-class -> serviceName map)`,\n );\n }\n map[api] = target.trim();\n }\n return new CallsServiceResolution(map, null);\n }\n\n return new CallsServiceResolution(\n null,\n `${info.name}: ${CALLS_SERVICE_METADATA_PATH} in project.json must be a non-empty string, or an ` +\n `{ apiClassName: serviceName } map for a client that calls several services`,\n );\n}\n\n/** True when a value is a plain object (a map), not an array or null. */\n// webpieces-disable no-any-unknown -- narrowing opaque project.json JSON\n// webpieces-disable no-function-outside-class -- pure type guard for the resolver above\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Two projects claiming the same service name make every targeted client edge ambiguous, so the\n * graph would have to guess. Reported as a metadata problem instead. Appends to `problems`.\n */\n// webpieces-disable no-function-outside-class -- pure validator, mirrors validateRoleDependencies\nexport function validateUniqueServiceNames(names: Map<string, string>, problems: string[]): void {\n const projectsByName = new Map<string, string[]>();\n for (const project of [...names.keys()].sort()) {\n const serviceName = names.get(project)!;\n const claimants = projectsByName.get(serviceName) ?? [];\n claimants.push(project);\n projectsByName.set(serviceName, claimants);\n }\n for (const serviceName of [...projectsByName.keys()].sort()) {\n const claimants = projectsByName.get(serviceName)!;\n if (claimants.length < 2) continue;\n problems.push(\n `serviceName '${serviceName}' is declared by ${claimants.length} projects ` +\n `(${claimants.join(', ')}) — a client naming it could not be routed to one of them. ` +\n `Give each project a distinct ${SERVICE_NAME_METADATA_PATH}.`,\n );\n }\n}\n\n/** The raw `metadata.webpieces.<field>` value, or undefined when not present at all. */\n// webpieces-disable no-any-unknown -- project.json is opaque consumer JSON until narrowed here\n// webpieces-disable no-function-outside-class -- pure file accessor, matches the sibling resolvers\nfunction readWebpiecesField(projectJsonPath: string, field: string): unknown {\n if (!fs.existsSync(projectJsonPath)) return undefined;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const parsed = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));\n return parsed?.metadata?.webpieces?.[field];\n } catch (err: unknown) {\n const error = toError(err);\n console.warn(`⚠️ Skipping unparseable ${projectJsonPath}: ${error.message}`);\n return undefined;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"service-name-resolver.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/service-name-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Service Name Resolver\n *\n * Resolves the `serviceName` field written per project into\n * architecture/dependencies.json — the name a CLIENT uses to address this app at\n * runtime (`new ClientConfig('helper-fsdb')`), i.e. its deployed Cloud Run\n * service name, NOT its nx project name.\n *\n * It is DECLARED, never derived. The three naming spaces have no mechanical\n * relationship, and any strip-the-suffix rule gets it wrong somewhere:\n *\n * nx project | serviceName | Cloud Run\n * helper-svr | helper-portal | helper-portal\n * helper-fsdb-svr | helper-fsdb | helper-fsdb\n * lang-server | lang | lang\n *\n * Declared in the project's own project.json, next to the code it names:\n *\n * { \"metadata\": { \"webpieces\": { \"serviceName\": \"helper-fsdb\" } } }\n *\n * Absent is legal and common — a library or a browser app is never addressed by\n * name. A node WITHOUT one simply cannot be the resolved target of a targeted\n * client call, and the runtime graph says so out loud rather than guessing.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { ProjectInfo } from './project-info';\nimport { toError } from '../toError';\n\n/** The project.json key path holding the declared name: metadata.webpieces.serviceName. */\nexport const SERVICE_NAME_METADATA_PATH = 'metadata.webpieces.serviceName';\n\n/** The project.json key path holding a client's declared target: metadata.webpieces.callsService. */\nexport const CALLS_SERVICE_METADATA_PATH = 'metadata.webpieces.callsService';\n\nexport class ServiceNameResolution {\n constructor(\n /** Declared service name, or null when none is declared (or resolution failed). */\n public readonly serviceName: string | null,\n /** Problem description when the declaration is present but unusable, otherwise null. */\n public readonly problem: string | null,\n ) {}\n}\n\n/**\n * Read `metadata.webpieces.serviceName` from the project's project.json. A missing file, a missing\n * key, or an unparseable file all resolve to \"not declared\" — only a PRESENT-but-wrong value (empty\n * or not a string) is a problem, because that is a typo the author wants told about.\n */\n// webpieces-disable no-function-outside-class -- pure resolver, mirrors resolveRole/resolveDrawOnGraph\nexport function resolveServiceName(info: ProjectInfo, workspaceRoot: string): ServiceNameResolution {\n const raw = readWebpiecesField(path.join(workspaceRoot, info.root, 'project.json'), 'serviceName');\n if (raw === undefined) return new ServiceNameResolution(null, null);\n if (typeof raw !== 'string' || raw.trim().length === 0) {\n return new ServiceNameResolution(\n null,\n `${info.name}: ${SERVICE_NAME_METADATA_PATH} in project.json must be a non-empty string`,\n );\n }\n return new ServiceNameResolution(raw.trim(), null);\n}\n\n/**\n * A client's declared target — the service it addresses when the call site cannot carry a literal\n * `ClientConfig` (the client is built in a SHARED library from a config field, so the literal lives\n * in the app, not the lib, one indirection away). Symmetric to `serviceName`: the IMPLEMENTING side\n * declares the name it answers to, the CALLING side declares the name it calls.\n *\n * Two shapes:\n * - a single string — every untargeted `uses` in this project aims at that one service (the common\n * case: most clients talk to exactly one server);\n * - an `{ apiClassName: serviceName }` map — the mixed case, a client that genuinely calls several.\n */\nexport class CallsServiceResolution {\n constructor(\n /** Declared target(s), or null when none is declared (or resolution failed). */\n public readonly callsService: string | Record<string, string> | null,\n /** Problem description when the declaration is present but unusable, otherwise null. */\n public readonly problem: string | null,\n ) {}\n}\n\n/**\n * Read `metadata.webpieces.callsService` from the project's project.json. A missing file, a missing\n * key, or an unparseable file all resolve to \"not declared\". A PRESENT value must be either a\n * non-empty string or a non-empty object of non-empty string values — anything else is a typo the\n * author wants told about.\n */\n// webpieces-disable no-function-outside-class -- pure resolver, mirrors resolveServiceName\nexport function resolveCallsService(info: ProjectInfo, workspaceRoot: string): CallsServiceResolution {\n const raw = readWebpiecesField(path.join(workspaceRoot, info.root, 'project.json'), 'callsService');\n if (raw === undefined) return new CallsServiceResolution(null, null);\n\n if (typeof raw === 'string') {\n if (raw.trim().length === 0) {\n return new CallsServiceResolution(\n null,\n `${info.name}: ${CALLS_SERVICE_METADATA_PATH} in project.json must be a non-empty string`,\n );\n }\n return new CallsServiceResolution(raw.trim(), null);\n }\n\n if (isPlainRecord(raw)) {\n const apis = Object.keys(raw);\n if (apis.length === 0) {\n return new CallsServiceResolution(\n null,\n `${info.name}: ${CALLS_SERVICE_METADATA_PATH} map in project.json must have at least one entry`,\n );\n }\n const map: Record<string, string> = {};\n for (const api of apis) {\n const target = raw[api];\n if (typeof target !== 'string' || target.trim().length === 0) {\n return new CallsServiceResolution(\n null,\n `${info.name}: ${CALLS_SERVICE_METADATA_PATH}['${api}'] in project.json must be a ` +\n `non-empty string (an api-class -> serviceName map)`,\n );\n }\n map[api] = target.trim();\n }\n return new CallsServiceResolution(map, null);\n }\n\n return new CallsServiceResolution(\n null,\n `${info.name}: ${CALLS_SERVICE_METADATA_PATH} in project.json must be a non-empty string, or an ` +\n `{ apiClassName: serviceName } map for a client that calls several services`,\n );\n}\n\n/** True when a value is a plain object (a map), not an array or null. */\n// webpieces-disable no-any-unknown -- narrowing opaque project.json JSON\n// webpieces-disable no-function-outside-class -- pure type guard for the resolver above\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Two projects claiming the same service name make every targeted client edge ambiguous, so the\n * graph would have to guess. Reported as a metadata problem instead. Appends to `problems`.\n */\n// webpieces-disable no-function-outside-class -- pure validator, mirrors validateRoleDependencies\nexport function validateUniqueServiceNames(names: Map<string, string>, problems: string[]): void {\n const projectsByName = new Map<string, string[]>();\n for (const project of [...names.keys()].sort()) {\n const serviceName = names.get(project)!;\n const claimants = projectsByName.get(serviceName) ?? [];\n claimants.push(project);\n projectsByName.set(serviceName, claimants);\n }\n for (const serviceName of [...projectsByName.keys()].sort()) {\n const claimants = projectsByName.get(serviceName)!;\n if (claimants.length < 2) continue;\n problems.push(\n `serviceName '${serviceName}' is declared by ${claimants.length} projects ` +\n `(${claimants.join(', ')}) — a client naming it could not be routed to one of them. ` +\n `Give each project a distinct ${SERVICE_NAME_METADATA_PATH}.`,\n );\n }\n}\n\n/** The raw `metadata.webpieces.<field>` value, or undefined when not present at all. */\n// webpieces-disable no-any-unknown -- project.json is opaque consumer JSON until narrowed here\n// webpieces-disable no-function-outside-class -- pure file accessor, matches the sibling resolvers\nfunction readWebpiecesField(projectJsonPath: string, field: string): unknown {\n if (!fs.existsSync(projectJsonPath)) return undefined;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const parsed = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));\n return parsed?.metadata?.webpieces?.[field];\n } catch (err: unknown) {\n const error = toError(err);\n console.warn(`⚠️ Skipping unparseable ${projectJsonPath}: ${error.message}`);\n return undefined;\n }\n}\n"]}
|
|
@@ -1,10 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"]}
|