@webpieces/nx-webpieces-rules 0.4.529 → 0.4.531
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/lib/api-usage/api-ast.d.ts +29 -1
- package/src/lib/api-usage/api-ast.js +79 -1
- package/src/lib/api-usage/api-ast.js.map +1 -1
- package/src/lib/api-usage/api-contract-errors.d.ts +15 -1
- package/src/lib/api-usage/api-contract-errors.js +30 -1
- package/src/lib/api-usage/api-contract-errors.js.map +1 -1
- package/src/lib/api-usage/api-relations.d.ts +42 -0
- package/src/lib/api-usage/api-relations.js +31 -1
- package/src/lib/api-usage/api-relations.js.map +1 -1
- package/src/lib/api-usage/api-scanner.d.ts +8 -2
- package/src/lib/api-usage/api-scanner.js +8 -1
- package/src/lib/api-usage/api-scanner.js.map +1 -1
- package/src/lib/runtime-graph-model.d.ts +12 -0
- package/src/lib/runtime-graph-model.js.map +1 -1
- package/src/lib/runtime-graph-sorters.d.ts +26 -0
- package/src/lib/runtime-graph-sorters.js +66 -0
- package/src/lib/runtime-graph-sorters.js.map +1 -0
- package/src/lib/runtime-graph.js +12 -51
- package/src/lib/runtime-graph.js.map +1 -1
- package/src/lib/runtime-visualizer.d.ts +5 -3
- package/src/lib/runtime-visualizer.js +73 -153
- package/src/lib/runtime-visualizer.js.map +1 -1
- package/src/lib/runtime-viz-theme.d.ts +61 -0
- package/src/lib/runtime-viz-theme.js +137 -0
- package/src/lib/runtime-viz-theme.js.map +1 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime graph's VISUAL VOCABULARY, and the legend that explains it.
|
|
3
|
+
*
|
|
4
|
+
* Every colour, shape and fill the drawing uses lives here, next to the legend swatches that teach
|
|
5
|
+
* the reader what each one means. They are one unit on purpose: the legend's job is to be a true
|
|
6
|
+
* account of the picture, and the fastest way for a legend to start lying is for it to sit in a
|
|
7
|
+
* different file from the constants it describes and drift as those change.
|
|
8
|
+
*
|
|
9
|
+
* Imported by runtime-visualizer.ts, which owns the DRAWING (which nodes and edges to emit). This
|
|
10
|
+
* module knows nothing about the graph model, which is what keeps the dependency one-way and
|
|
11
|
+
* cycle-free.
|
|
12
|
+
*/
|
|
13
|
+
export declare const LEVEL_COLORS: Record<number, string>;
|
|
14
|
+
export declare const QUEUE_FILL = "#FFF3E0";
|
|
15
|
+
/**
|
|
16
|
+
* The queue node is an `Mrecord` whose FIRST field is empty, which draws a rounded outline with a
|
|
17
|
+
* vertical cap line near one end — a cylinder lying on its side, distinguishing a queue from the
|
|
18
|
+
* upright cylinder that now means a database.
|
|
19
|
+
*
|
|
20
|
+
* Two things here are load-bearing and easy to break:
|
|
21
|
+
*
|
|
22
|
+
* 1. NO surrounding `{}`. Record fields lay out along the rank direction, and this graph is
|
|
23
|
+
* `rankdir=TB` (see {@link generateRuntimeDot}), where the default is horizontal — which is what
|
|
24
|
+
* we want. Adding braces TOGGLES that, turning the cap line into a band across the top.
|
|
25
|
+
* 2. The leading space is the empty field. The record parser trims it to nothing, which is the
|
|
26
|
+
* point; it must survive as its own field, so the `|` cannot be dropped.
|
|
27
|
+
*
|
|
28
|
+
* Graphviz has no sideways cylinder and never has: `orientation=` is documented as rotating POLYGON
|
|
29
|
+
* shapes, and `cylinder` is drawn with beziers, so it silently ignores the attribute (graphviz issue
|
|
30
|
+
* #2244, open since 2022 and still reproducible on 13.0.0). This is the closest native shape.
|
|
31
|
+
*/
|
|
32
|
+
export declare const QUEUE_SHAPE = "Mrecord";
|
|
33
|
+
export declare const QUEUE_LABEL_PREFIX = " |";
|
|
34
|
+
/**
|
|
35
|
+
* Marker class stamped on every queue node. Graphviz copies `class` straight into the rendered
|
|
36
|
+
* `<g class="node wp_queue">`, which is how runtime-visualizer.client.js finds these nodes and
|
|
37
|
+
* redraws them as true horizontal cylinders in the browser.
|
|
38
|
+
*
|
|
39
|
+
* A CLASS rather than an id prefix, because queue-kind EXTERNAL systems are queues too and share the
|
|
40
|
+
* `system__` id space with databases — which must stay upright. Underscored, not hyphenated: DOT
|
|
41
|
+
* emits a hyphen as `-`, which is harmless but needlessly surprising to anyone reading the SVG.
|
|
42
|
+
*/
|
|
43
|
+
export declare const QUEUE_CLASS = "wp_queue";
|
|
44
|
+
/** Fill for the upright cylinder standing for an external DATASTORE (firestore, postgres, ...). */
|
|
45
|
+
export declare const DATABASE_FILL = "#E1F5FE";
|
|
46
|
+
/** Shape per external-system kind. Anything unrecognised falls back to the generic dashed box. */
|
|
47
|
+
export declare const EXTERNAL_SHAPES: Record<string, string>;
|
|
48
|
+
/** Fill per external-system kind, paired with {@link EXTERNAL_SHAPES}. */
|
|
49
|
+
export declare const EXTERNAL_FILLS: Record<string, string>;
|
|
50
|
+
/** Fill + border for the dashed terminal node standing for a system outside this repo. */
|
|
51
|
+
export declare const EXTERNAL_FILL = "#FAFAFA";
|
|
52
|
+
export declare const EXTERNAL_BORDER = "#9E9E9E";
|
|
53
|
+
/** Fill + border for the clock node standing for a scheduler-driven endpoint. */
|
|
54
|
+
export declare const CRON_FILL = "#FFF9C4";
|
|
55
|
+
export declare const CRON_BORDER = "#F9A825";
|
|
56
|
+
/**
|
|
57
|
+
* The legend. Three columns — what a box IS, what a line MEANS, how to read a box — replacing the
|
|
58
|
+
* three paragraphs of prose that used to restate the picture in words. Styled after
|
|
59
|
+
* {@link GraphVisualizer}'s legend so the two graphs in this repo look like one tool.
|
|
60
|
+
*/
|
|
61
|
+
export declare function legendHtml(): string;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The runtime graph's VISUAL VOCABULARY, and the legend that explains it.
|
|
4
|
+
*
|
|
5
|
+
* Every colour, shape and fill the drawing uses lives here, next to the legend swatches that teach
|
|
6
|
+
* the reader what each one means. They are one unit on purpose: the legend's job is to be a true
|
|
7
|
+
* account of the picture, and the fastest way for a legend to start lying is for it to sit in a
|
|
8
|
+
* different file from the constants it describes and drift as those change.
|
|
9
|
+
*
|
|
10
|
+
* Imported by runtime-visualizer.ts, which owns the DRAWING (which nodes and edges to emit). This
|
|
11
|
+
* module knows nothing about the graph model, which is what keeps the dependency one-way and
|
|
12
|
+
* cycle-free.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.CRON_BORDER = exports.CRON_FILL = exports.EXTERNAL_BORDER = exports.EXTERNAL_FILL = exports.EXTERNAL_FILLS = exports.EXTERNAL_SHAPES = exports.DATABASE_FILL = exports.QUEUE_CLASS = exports.QUEUE_LABEL_PREFIX = exports.QUEUE_SHAPE = exports.QUEUE_FILL = exports.LEVEL_COLORS = void 0;
|
|
16
|
+
exports.legendHtml = legendHtml;
|
|
17
|
+
exports.LEVEL_COLORS = {
|
|
18
|
+
0: '#E8F5E9',
|
|
19
|
+
1: '#E3F2FD',
|
|
20
|
+
2: '#FFF3E0',
|
|
21
|
+
3: '#FCE4EC',
|
|
22
|
+
};
|
|
23
|
+
exports.QUEUE_FILL = '#FFF3E0';
|
|
24
|
+
/**
|
|
25
|
+
* The queue node is an `Mrecord` whose FIRST field is empty, which draws a rounded outline with a
|
|
26
|
+
* vertical cap line near one end — a cylinder lying on its side, distinguishing a queue from the
|
|
27
|
+
* upright cylinder that now means a database.
|
|
28
|
+
*
|
|
29
|
+
* Two things here are load-bearing and easy to break:
|
|
30
|
+
*
|
|
31
|
+
* 1. NO surrounding `{}`. Record fields lay out along the rank direction, and this graph is
|
|
32
|
+
* `rankdir=TB` (see {@link generateRuntimeDot}), where the default is horizontal — which is what
|
|
33
|
+
* we want. Adding braces TOGGLES that, turning the cap line into a band across the top.
|
|
34
|
+
* 2. The leading space is the empty field. The record parser trims it to nothing, which is the
|
|
35
|
+
* point; it must survive as its own field, so the `|` cannot be dropped.
|
|
36
|
+
*
|
|
37
|
+
* Graphviz has no sideways cylinder and never has: `orientation=` is documented as rotating POLYGON
|
|
38
|
+
* shapes, and `cylinder` is drawn with beziers, so it silently ignores the attribute (graphviz issue
|
|
39
|
+
* #2244, open since 2022 and still reproducible on 13.0.0). This is the closest native shape.
|
|
40
|
+
*/
|
|
41
|
+
exports.QUEUE_SHAPE = 'Mrecord';
|
|
42
|
+
exports.QUEUE_LABEL_PREFIX = ' |';
|
|
43
|
+
/**
|
|
44
|
+
* Marker class stamped on every queue node. Graphviz copies `class` straight into the rendered
|
|
45
|
+
* `<g class="node wp_queue">`, which is how runtime-visualizer.client.js finds these nodes and
|
|
46
|
+
* redraws them as true horizontal cylinders in the browser.
|
|
47
|
+
*
|
|
48
|
+
* A CLASS rather than an id prefix, because queue-kind EXTERNAL systems are queues too and share the
|
|
49
|
+
* `system__` id space with databases — which must stay upright. Underscored, not hyphenated: DOT
|
|
50
|
+
* emits a hyphen as `-`, which is harmless but needlessly surprising to anyone reading the SVG.
|
|
51
|
+
*/
|
|
52
|
+
exports.QUEUE_CLASS = 'wp_queue';
|
|
53
|
+
/** Fill for the upright cylinder standing for an external DATASTORE (firestore, postgres, ...). */
|
|
54
|
+
exports.DATABASE_FILL = '#E1F5FE';
|
|
55
|
+
/** Shape per external-system kind. Anything unrecognised falls back to the generic dashed box. */
|
|
56
|
+
exports.EXTERNAL_SHAPES = {
|
|
57
|
+
database: 'cylinder',
|
|
58
|
+
cache: 'cylinder',
|
|
59
|
+
queue: 'Mrecord',
|
|
60
|
+
storage: 'folder',
|
|
61
|
+
};
|
|
62
|
+
/** Fill per external-system kind, paired with {@link EXTERNAL_SHAPES}. */
|
|
63
|
+
exports.EXTERNAL_FILLS = {
|
|
64
|
+
database: exports.DATABASE_FILL,
|
|
65
|
+
cache: exports.DATABASE_FILL,
|
|
66
|
+
queue: exports.QUEUE_FILL,
|
|
67
|
+
storage: '#F3E5F5',
|
|
68
|
+
};
|
|
69
|
+
/** Fill + border for the dashed terminal node standing for a system outside this repo. */
|
|
70
|
+
exports.EXTERNAL_FILL = '#FAFAFA';
|
|
71
|
+
exports.EXTERNAL_BORDER = '#9E9E9E';
|
|
72
|
+
/** Fill + border for the clock node standing for a scheduler-driven endpoint. */
|
|
73
|
+
exports.CRON_FILL = '#FFF9C4';
|
|
74
|
+
exports.CRON_BORDER = '#F9A825';
|
|
75
|
+
class LegendSwatches {
|
|
76
|
+
service = '<svg width="46" height="26"><rect x="1" y="3" width="44" height="20" rx="7" fill="#E8F5E9" stroke="#333"/></svg>';
|
|
77
|
+
/**
|
|
78
|
+
* A cylinder on its side — the SAME geometry runtime-visualizer.client.js draws on the real
|
|
79
|
+
* node, so the legend cannot drift from the picture it explains.
|
|
80
|
+
*/
|
|
81
|
+
queue = `<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="${exports.QUEUE_FILL}" stroke="#333"/>` +
|
|
82
|
+
'<path d="M9,5 A8,8 0 0 1 9,21" fill="none" stroke="#333"/></svg>';
|
|
83
|
+
database = `<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="${exports.DATABASE_FILL}" stroke="#333"/>` +
|
|
84
|
+
'<path d="M8,7 a15,4 0 0 0 30,0" fill="none" stroke="#333"/></svg>';
|
|
85
|
+
storage = '<svg width="46" height="26"><path d="M2,22 V6 H16 l3,3 H44 V22 Z" fill="#F3E5F5" stroke="#333"/></svg>';
|
|
86
|
+
external = `<svg width="46" height="26"><rect x="1" y="3" width="44" height="20" fill="${exports.EXTERNAL_FILL}" ` +
|
|
87
|
+
`stroke="${exports.EXTERNAL_BORDER}" stroke-dasharray="4,3"/></svg>`;
|
|
88
|
+
cron = `<svg width="46" height="26"><circle cx="23" cy="13" r="11" fill="${exports.CRON_FILL}" stroke="${exports.CRON_BORDER}"/>` +
|
|
89
|
+
'<text x="23" y="18" font-size="12" text-anchor="middle">⏰</text></svg>';
|
|
90
|
+
solid = '<svg width="60" height="20"><line x1="2" y1="10" x2="48" y2="10" stroke="#333" stroke-width="1.5"/>' +
|
|
91
|
+
'<path d="M48,6 L57,10 L48,14 Z" fill="#333"/></svg>';
|
|
92
|
+
dashed = '<svg width="60" height="20"><line x1="2" y1="10" x2="48" y2="10" stroke="#333" stroke-width="1.5" ' +
|
|
93
|
+
'stroke-dasharray="5,4"/><path d="M48,6 L57,10 L48,14 Z" fill="#333"/></svg>';
|
|
94
|
+
scheduled = `<svg width="60" height="20"><line x1="2" y1="10" x2="48" y2="10" stroke="${exports.CRON_BORDER}" stroke-width="1.5"/>` +
|
|
95
|
+
`<path d="M48,6 L57,10 L48,14 Z" fill="${exports.CRON_BORDER}"/></svg>`;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The legend. Three columns — what a box IS, what a line MEANS, how to read a box — replacing the
|
|
99
|
+
* three paragraphs of prose that used to restate the picture in words. Styled after
|
|
100
|
+
* {@link GraphVisualizer}'s legend so the two graphs in this repo look like one tool.
|
|
101
|
+
*/
|
|
102
|
+
// webpieces-disable no-function-outside-class -- HTML builder, matching the sibling builders in this file
|
|
103
|
+
function legendHtml() {
|
|
104
|
+
const sw = new LegendSwatches();
|
|
105
|
+
const item = (swatch, text) => `<div class="legend-item"><span class="sw">${swatch}</span><span>${text}</span></div>`;
|
|
106
|
+
return `<div class="legend">
|
|
107
|
+
<h2>Legend</h2>
|
|
108
|
+
<div class="legend-columns">
|
|
109
|
+
<div class="legend-col">
|
|
110
|
+
<h3>Node shapes — <em>what a box is</em></h3>
|
|
111
|
+
${item(sw.service, '<strong>service</strong> — a deployable in this repo; fill is its dependency level')}
|
|
112
|
+
${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.')}
|
|
113
|
+
${item(sw.database, '<strong>database</strong> — a datastore outside this repo')}
|
|
114
|
+
${item(sw.storage, '<strong>object storage</strong> — a bucket outside this repo')}
|
|
115
|
+
${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.')}
|
|
116
|
+
${item(sw.cron, '<strong>cron</strong> — a scheduler fires this endpoint')}
|
|
117
|
+
</div>
|
|
118
|
+
<div class="legend-col">
|
|
119
|
+
<h3>Lines — <em>what a call is</em></h3>
|
|
120
|
+
${item(sw.solid, '<strong>solid = rpc</strong> — the request follows the arrow, the response flows back')}
|
|
121
|
+
${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')}
|
|
122
|
+
${item(sw.scheduled, '<strong>scheduled</strong> — a cron invocation')}
|
|
123
|
+
<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>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="legend-col">
|
|
126
|
+
<h3>Reading a box</h3>
|
|
127
|
+
<pre class="legend-box-anatomy">name
|
|
128
|
+
(server|client, L#)
|
|
129
|
+
implements: <contracts it serves>
|
|
130
|
+
</pre>
|
|
131
|
+
<div class="legend-note">A box lists only what it <strong>serves</strong>. What it <em>calls</em> is on its outgoing arrows.</div>
|
|
132
|
+
<div class="legend-note"><code>(via <lib>)</code> = served through an embedded library, not its own source.</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>`;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=runtime-viz-theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-viz-theme.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-viz-theme.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AA2GH,gCAkCC;AA3IY,QAAA,YAAY,GAA2B;IAChD,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,SAAS;IACZ,CAAC,EAAE,SAAS;CACf,CAAC;AAEW,QAAA,UAAU,GAAG,SAAS,CAAC;AAEpC;;;;;;;;;;;;;;;;GAgBG;AACU,QAAA,WAAW,GAAG,SAAS,CAAC;AACxB,QAAA,kBAAkB,GAAG,IAAI,CAAC;AAEvC;;;;;;;;GAQG;AACU,QAAA,WAAW,GAAG,UAAU,CAAC;AAEtC,mGAAmG;AACtF,QAAA,aAAa,GAAG,SAAS,CAAC;AAEvC,kGAAkG;AACrF,QAAA,eAAe,GAA2B;IACnD,QAAQ,EAAE,UAAU;IACpB,KAAK,EAAE,UAAU;IACjB,KAAK,EAAE,SAAS;IAChB,OAAO,EAAE,QAAQ;CACpB,CAAC;AAEF,0EAA0E;AAC7D,QAAA,cAAc,GAA2B;IAClD,QAAQ,EAAE,qBAAa;IACvB,KAAK,EAAE,qBAAa;IACpB,KAAK,EAAE,kBAAU;IACjB,OAAO,EAAE,SAAS;CACrB,CAAC;AAEF,0FAA0F;AAC7E,QAAA,aAAa,GAAG,SAAS,CAAC;AAC1B,QAAA,eAAe,GAAG,SAAS,CAAC;AAEzC,iFAAiF;AACpE,QAAA,SAAS,GAAG,SAAS,CAAC;AACtB,QAAA,WAAW,GAAG,SAAS,CAAC;AAErC,MAAM,cAAc;IACP,OAAO,GACZ,kHAAkH,CAAC;IACvH;;;OAGG;IACM,KAAK,GACV,6FAA6F,kBAAU,mBAAmB;QAC1H,kEAAkE,CAAC;IAC9D,QAAQ,GACb,6FAA6F,qBAAa,mBAAmB;QAC7H,mEAAmE,CAAC;IAC/D,OAAO,GACZ,wGAAwG,CAAC;IACpG,QAAQ,GACb,8EAA8E,qBAAa,IAAI;QAC/F,WAAW,uBAAe,kCAAkC,CAAC;IACxD,IAAI,GACT,oEAAoE,iBAAS,aAAa,mBAAW,KAAK;QAC1G,8EAA8E,CAAC;IAC1E,KAAK,GACV,qGAAqG;QACrG,qDAAqD,CAAC;IACjD,MAAM,GACX,oGAAoG;QACpG,6EAA6E,CAAC;IACzE,SAAS,GACd,4EAA4E,mBAAW,wBAAwB;QAC/G,yCAAyC,mBAAW,WAAW,CAAC;CACvE;AAED;;;;GAIG;AACH,0GAA0G;AAC1G,SAAgB,UAAU;IACtB,MAAM,EAAE,GAAG,IAAI,cAAc,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,IAAY,EAAU,EAAE,CAClD,6CAA6C,MAAM,gBAAgB,IAAI,eAAe,CAAC;IAC3F,OAAO;;;;;kBAKO,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,0FAA0F,CAAC;kBAC5G,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,0SAA0S,CAAC;kBAC1T,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,iEAAiE,CAAC;kBACpF,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,oEAAoE,CAAC;kBACtF,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,ujBAAujB,CAAC;kBAC1kB,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,+DAA+D,CAAC;;;;kBAI9E,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,6FAA6F,CAAC;kBAC7G,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,yIAAyI,CAAC;kBAC1J,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,sDAAsD,CAAC;;;;;;;;;;;;;WAajF,CAAC;AACZ,CAAC","sourcesContent":["/**\n * The runtime graph's VISUAL VOCABULARY, and the legend that explains it.\n *\n * Every colour, shape and fill the drawing uses lives here, next to the legend swatches that teach\n * the reader what each one means. They are one unit on purpose: the legend's job is to be a true\n * account of the picture, and the fastest way for a legend to start lying is for it to sit in a\n * different file from the constants it describes and drift as those change.\n *\n * Imported by runtime-visualizer.ts, which owns the DRAWING (which nodes and edges to emit). This\n * module knows nothing about the graph model, which is what keeps the dependency one-way and\n * cycle-free.\n */\n\nexport const LEVEL_COLORS: Record<number, string> = {\n 0: '#E8F5E9',\n 1: '#E3F2FD',\n 2: '#FFF3E0',\n 3: '#FCE4EC',\n};\n\nexport const QUEUE_FILL = '#FFF3E0';\n\n/**\n * The queue node is an `Mrecord` whose FIRST field is empty, which draws a rounded outline with a\n * vertical cap line near one end — a cylinder lying on its side, distinguishing a queue from the\n * upright cylinder that now means a database.\n *\n * Two things here are load-bearing and easy to break:\n *\n * 1. NO surrounding `{}`. Record fields lay out along the rank direction, and this graph is\n * `rankdir=TB` (see {@link generateRuntimeDot}), where the default is horizontal — which is what\n * we want. Adding braces TOGGLES that, turning the cap line into a band across the top.\n * 2. The leading space is the empty field. The record parser trims it to nothing, which is the\n * point; it must survive as its own field, so the `|` cannot be dropped.\n *\n * Graphviz has no sideways cylinder and never has: `orientation=` is documented as rotating POLYGON\n * shapes, and `cylinder` is drawn with beziers, so it silently ignores the attribute (graphviz issue\n * #2244, open since 2022 and still reproducible on 13.0.0). This is the closest native shape.\n */\nexport const QUEUE_SHAPE = 'Mrecord';\nexport const QUEUE_LABEL_PREFIX = ' |';\n\n/**\n * Marker class stamped on every queue node. Graphviz copies `class` straight into the rendered\n * `<g class=\"node wp_queue\">`, which is how runtime-visualizer.client.js 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/** Shape per external-system kind. Anything unrecognised falls back to the generic dashed box. */\nexport const EXTERNAL_SHAPES: Record<string, string> = {\n database: 'cylinder',\n cache: 'cylinder',\n queue: 'Mrecord',\n storage: 'folder',\n};\n\n/** Fill per external-system kind, paired with {@link EXTERNAL_SHAPES}. */\nexport const EXTERNAL_FILLS: Record<string, string> = {\n database: DATABASE_FILL,\n cache: DATABASE_FILL,\n queue: QUEUE_FILL,\n storage: '#F3E5F5',\n};\n\n/** Fill + border for the dashed terminal node standing for a system outside this repo. */\nexport const EXTERNAL_FILL = '#FAFAFA';\nexport const EXTERNAL_BORDER = '#9E9E9E';\n\n/** Fill + border for the clock node standing for a scheduler-driven endpoint. */\nexport const CRON_FILL = '#FFF9C4';\nexport const CRON_BORDER = '#F9A825';\n\nclass LegendSwatches {\n readonly service =\n '<svg width=\"46\" height=\"26\"><rect x=\"1\" y=\"3\" width=\"44\" height=\"20\" rx=\"7\" fill=\"#E8F5E9\" stroke=\"#333\"/></svg>';\n /**\n * A cylinder on its side — the SAME geometry runtime-visualizer.client.js 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"]}
|