@diagrammo/dgmo 0.16.0 → 0.17.0
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/dist/advanced.cjs +133 -280
- package/dist/advanced.d.cts +9 -2
- package/dist/advanced.d.ts +9 -2
- package/dist/advanced.js +133 -280
- package/dist/auto.cjs +135 -269
- package/dist/auto.js +96 -96
- package/dist/auto.mjs +135 -269
- package/dist/cli.cjs +125 -125
- package/dist/index.cjs +132 -266
- package/dist/index.js +132 -266
- package/dist/internal.cjs +133 -280
- package/dist/internal.d.cts +9 -2
- package/dist/internal.d.ts +9 -2
- package/dist/internal.js +133 -280
- package/docs/language-reference.md +14 -18
- package/docs/migration-sequence-color-to-tags.md +1 -1
- package/gallery/fixtures/sequence-tags-protocols.dgmo +3 -3
- package/gallery/fixtures/sequence-tags.dgmo +3 -3
- package/gallery/fixtures/sequence.dgmo +4 -4
- package/package.json +7 -3
- package/src/auto/index.ts +2 -2
- package/src/boxes-and-lines/layout.ts +1 -2
- package/src/c4/parser.ts +1 -1
- package/src/class/parser.ts +1 -1
- package/src/cli.ts +2 -2
- package/src/completion.ts +1 -14
- package/src/cycle/parser.ts +1 -1
- package/src/d3.ts +2 -3
- package/src/diagnostics.ts +20 -0
- package/src/echarts.ts +2 -2
- package/src/editor/dgmo.grammar.d.ts +1 -1
- package/src/er/parser.ts +1 -1
- package/src/graph/flowchart-renderer.ts +3 -0
- package/src/infra/renderer.ts +1 -2
- package/src/journey-map/parser.ts +1 -1
- package/src/kanban/parser.ts +1 -1
- package/src/mindmap/parser.ts +2 -3
- package/src/org/parser.ts +1 -1
- package/src/pert/analyzer.ts +10 -10
- package/src/pert/layout.ts +1 -1
- package/src/pert/parser.ts +1 -1
- package/src/pyramid/parser.ts +1 -1
- package/src/raci/parser.ts +2 -2
- package/src/ring/parser.ts +1 -1
- package/src/sequence/parser.ts +66 -14
- package/src/sequence/participant-inference.ts +18 -181
- package/src/sequence/renderer.ts +47 -136
- package/src/sitemap/parser.ts +1 -1
- package/src/tech-radar/parser.ts +2 -2
- package/src/utils/extract-alias.ts +1 -1
- package/src/utils/inline-markdown.ts +1 -1
- package/src/utils/time-ticks.ts +1 -1
- package/src/wireframe/parser.ts +1 -1
package/src/sequence/parser.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
NAME_DIAGNOSTIC_CODES,
|
|
12
12
|
nameMergedMessage,
|
|
13
13
|
akaRemovedMessage,
|
|
14
|
+
participantTypeRemovedMessage,
|
|
14
15
|
} from '../diagnostics';
|
|
15
16
|
import { normalizeName, displayName } from '../utils/name-normalize';
|
|
16
17
|
import { parseArrow, parseInArrowLabel } from '../utils/arrows';
|
|
@@ -60,29 +61,40 @@ const KNOWN_SEQ_BOOLEANS = new Set(['activations', 'solid-fill', 'no-title']);
|
|
|
60
61
|
|
|
61
62
|
/**
|
|
62
63
|
* Participant types that can be declared via "Name is a type" syntax.
|
|
64
|
+
*
|
|
65
|
+
* The 0.16.0 trim retained only the types whose shapes carry semantic
|
|
66
|
+
* weight at a glance: stick figure (actor), cylinder (database),
|
|
67
|
+
* dashed cylinder (cache), horizontal pipe (queue), plus the default
|
|
68
|
+
* rectangle. The legacy `service`/`frontend`/`networking`/`gateway`/
|
|
69
|
+
* `external` keywords are rejected at parse time via
|
|
70
|
+
* `E_PARTICIPANT_TYPE_REMOVED`.
|
|
63
71
|
*/
|
|
64
72
|
export type ParticipantType =
|
|
65
73
|
| 'default'
|
|
66
|
-
| 'service'
|
|
67
74
|
| 'database'
|
|
68
75
|
| 'actor'
|
|
69
76
|
| 'queue'
|
|
70
|
-
| 'cache'
|
|
71
|
-
| 'gateway'
|
|
72
|
-
| 'external'
|
|
73
|
-
| 'networking'
|
|
74
|
-
| 'frontend';
|
|
77
|
+
| 'cache';
|
|
75
78
|
|
|
76
79
|
const VALID_PARTICIPANT_TYPES: ReadonlySet<string> = new Set([
|
|
77
|
-
'service',
|
|
78
80
|
'database',
|
|
79
81
|
'actor',
|
|
80
82
|
'queue',
|
|
81
83
|
'cache',
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Participant-type keywords that were removed in 0.16.0. Used to
|
|
88
|
+
* gate `is a X` declarations with a hard parse error rather than
|
|
89
|
+
* silent fall-through to default — aligns with the pre-1.0
|
|
90
|
+
* break-and-bump policy (see `E_AKA_REMOVED` precedent).
|
|
91
|
+
*/
|
|
92
|
+
const REMOVED_PARTICIPANT_TYPES: ReadonlySet<string> = new Set([
|
|
93
|
+
'service',
|
|
94
|
+
'frontend',
|
|
95
|
+
'networking',
|
|
82
96
|
'gateway',
|
|
83
97
|
'external',
|
|
84
|
-
'networking',
|
|
85
|
-
'frontend',
|
|
86
98
|
]);
|
|
87
99
|
|
|
88
100
|
/**
|
|
@@ -205,9 +217,9 @@ export interface ParsedSequenceDgmo {
|
|
|
205
217
|
error: string | null;
|
|
206
218
|
}
|
|
207
219
|
|
|
208
|
-
// "Name is a type" pattern — e.g. "Auth Server is a
|
|
220
|
+
// "Name is a type" pattern — e.g. "Auth Server is a database"
|
|
209
221
|
// Participant names may contain spaces; [^:]+? stops at colons so that
|
|
210
|
-
// note lines like "note right of A: this is
|
|
222
|
+
// note lines like "note right of A: this is an actor" are not falsely matched.
|
|
211
223
|
// Remainder after type is parsed separately for `position N` modifier.
|
|
212
224
|
const IS_A_PATTERN = /^([^:]+?)\s+is\s+an?\s+(\w+)(?:\s+(.+))?$/i;
|
|
213
225
|
|
|
@@ -480,7 +492,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
480
492
|
result.diagnostics.push(makeDgmoError(line, message, 'warning'));
|
|
481
493
|
};
|
|
482
494
|
|
|
483
|
-
if (!content
|
|
495
|
+
if (!content?.trim()) {
|
|
484
496
|
return fail(0, 'Empty content');
|
|
485
497
|
}
|
|
486
498
|
|
|
@@ -494,7 +506,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
494
506
|
const fl = lines[fi].trim();
|
|
495
507
|
if (!fl || fl.startsWith('//')) continue;
|
|
496
508
|
const parsed = parseFirstLine(fl);
|
|
497
|
-
if (parsed
|
|
509
|
+
if (parsed?.chartType === 'sequence') {
|
|
498
510
|
hasExplicitChart = true;
|
|
499
511
|
firstLineIndex = fi;
|
|
500
512
|
if (parsed.title) {
|
|
@@ -936,6 +948,23 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
936
948
|
const typeStr = isAMatch[2].toLowerCase();
|
|
937
949
|
let remainder = isAMatch[3]?.trim() || '';
|
|
938
950
|
|
|
951
|
+
// Reject the 5 removed-type keywords with a hard parse error
|
|
952
|
+
// (per Decision #2 / break-and-bump policy). Skip participant
|
|
953
|
+
// registration for the bad line — downstream message references
|
|
954
|
+
// will surface a "participant not declared" diagnostic, which
|
|
955
|
+
// is the intended behavior.
|
|
956
|
+
if (REMOVED_PARTICIPANT_TYPES.has(typeStr)) {
|
|
957
|
+
result.diagnostics.push(
|
|
958
|
+
makeDgmoError(
|
|
959
|
+
lineNumber,
|
|
960
|
+
participantTypeRemovedMessage(typeStr),
|
|
961
|
+
'error',
|
|
962
|
+
NAME_DIAGNOSTIC_CODES.PARTICIPANT_TYPE_REMOVED
|
|
963
|
+
)
|
|
964
|
+
);
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
|
|
939
968
|
const participantType: ParticipantType = VALID_PARTICIPANT_TYPES.has(
|
|
940
969
|
typeStr
|
|
941
970
|
)
|
|
@@ -957,7 +986,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
957
986
|
// register it. Order on the line is `Name is a TYPE [position N] as <alias>`.
|
|
958
987
|
// The leading `(.*?)\s*\b` allows the remainder to be just
|
|
959
988
|
// `as <alias>` (empty prefix) — the canonical example writes
|
|
960
|
-
// `Alice is
|
|
989
|
+
// `Alice is an actor as a` where the entire remainder is `as a`.
|
|
961
990
|
const asInRemainder = remainder.match(
|
|
962
991
|
/^(.*?)\s*\bas\s+([A-Za-z][A-Za-z0-9_]{0,11})\s*$/
|
|
963
992
|
);
|
|
@@ -1316,6 +1345,29 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
1316
1345
|
continue;
|
|
1317
1346
|
}
|
|
1318
1347
|
|
|
1348
|
+
// 'elif <label>' → suggest 'else if <label>'
|
|
1349
|
+
const elifMatch = trimmed.match(/^elif\b\s*(.*)$/i);
|
|
1350
|
+
if (elifMatch) {
|
|
1351
|
+
const tailRaw = elifMatch[1].trim();
|
|
1352
|
+
const tail = tailRaw ? ' ' + tailRaw : '';
|
|
1353
|
+
pushError(
|
|
1354
|
+
lineNumber,
|
|
1355
|
+
`'elif' is not a keyword. Did you mean 'else if${tail}'?`
|
|
1356
|
+
);
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// 'else <text>' (where text isn't 'if …') → suggest 'else if <text>'
|
|
1361
|
+
const elseLabelMatch = trimmed.match(/^else\s+(.+)$/i);
|
|
1362
|
+
if (elseLabelMatch) {
|
|
1363
|
+
const tail = elseLabelMatch[1].trim();
|
|
1364
|
+
pushError(
|
|
1365
|
+
lineNumber,
|
|
1366
|
+
`'else' does not take a label. Did you mean 'else if ${tail}'?`
|
|
1367
|
+
);
|
|
1368
|
+
continue;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1319
1371
|
// ---- Note parsing (space-separated only) ----
|
|
1320
1372
|
// Strategy:
|
|
1321
1373
|
// 1. Try bare note: `note text` — position defaults, text is everything after `note`
|
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
// ============================================================
|
|
4
4
|
//
|
|
5
5
|
// Data-driven rules table that infers participant type from name.
|
|
6
|
-
// First match wins.
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// First match wins. Conflict overrides come before suffix rules
|
|
7
|
+
// to prevent false positives within the surviving 4-type taxonomy.
|
|
8
|
+
//
|
|
9
|
+
// Surviving types: actor, database, cache, queue (plus default).
|
|
10
|
+
// Removed in 0.16.0: service, frontend, networking, gateway, external.
|
|
11
|
+
// Names that previously inferred to a removed type fall through to
|
|
12
|
+
// default (sharp rectangle). Inference is intentionally conservative
|
|
13
|
+
// — narrow product/name lists, no broad suffix catch-alls.
|
|
9
14
|
// ============================================================
|
|
10
15
|
|
|
11
16
|
import type { ParticipantType } from './parser';
|
|
@@ -23,95 +28,17 @@ interface InferenceRule {
|
|
|
23
28
|
*
|
|
24
29
|
* Priority order:
|
|
25
30
|
* 0. Conflict overrides (prevent misclassification by general patterns)
|
|
26
|
-
* 1.
|
|
27
|
-
* 2.
|
|
28
|
-
* 3.
|
|
29
|
-
* 4.
|
|
30
|
-
* 5. Queue/Messaging patterns
|
|
31
|
-
* 6. Actor patterns (suffix + exact)
|
|
32
|
-
* 7. Frontend patterns
|
|
33
|
-
* 8. Service patterns
|
|
34
|
-
* 9. External patterns
|
|
31
|
+
* 1. Database patterns
|
|
32
|
+
* 2. Cache patterns
|
|
33
|
+
* 3. Queue/Messaging patterns
|
|
34
|
+
* 4. Actor patterns (suffix + exact)
|
|
35
35
|
*/
|
|
36
36
|
const PARTICIPANT_RULES: readonly InferenceRule[] = [
|
|
37
37
|
// ── 0. Conflict overrides ────────────────────────────────
|
|
38
38
|
// Names that would incorrectly match general patterns in later groups
|
|
39
39
|
{ pattern: /^KeyDB$/i, type: 'cache' }, // not database (DB$ suffix)
|
|
40
|
-
{ pattern: /Webhook/i, type: 'external' }, // not frontend (Web contains)
|
|
41
|
-
{ pattern: /^Upstream$/i, type: 'external' }, // not queue (Stream$ suffix)
|
|
42
|
-
{ pattern: /^Downstream$/i, type: 'external' }, // not queue (Stream$ suffix)
|
|
43
|
-
|
|
44
|
-
// ── 1. Infrastructure overrides ─────────────────────────
|
|
45
|
-
// These names end in -er/-or but are NOT actors
|
|
46
|
-
{ pattern: /^.*Router$/i, type: 'networking' },
|
|
47
|
-
{ pattern: /^.*Scheduler$/i, type: 'service' },
|
|
48
|
-
{ pattern: /^.*Dispatcher$/i, type: 'service' },
|
|
49
|
-
{ pattern: /^.*Balancer$/i, type: 'networking' },
|
|
50
|
-
{ pattern: /^.*Controller$/i, type: 'service' },
|
|
51
|
-
{ pattern: /^.*Handler$/i, type: 'service' },
|
|
52
|
-
{ pattern: /^.*Processor$/i, type: 'service' },
|
|
53
|
-
{ pattern: /^.*Connector$/i, type: 'service' },
|
|
54
|
-
{ pattern: /^.*Adapter$/i, type: 'service' },
|
|
55
|
-
{ pattern: /^.*Provider$/i, type: 'service' },
|
|
56
|
-
{ pattern: /^.*Manager$/i, type: 'service' },
|
|
57
|
-
{ pattern: /^.*Orchestrator$/i, type: 'service' },
|
|
58
|
-
{ pattern: /^.*Monitor$/i, type: 'service' },
|
|
59
|
-
{ pattern: /^.*Resolver$/i, type: 'service' },
|
|
60
|
-
{ pattern: /^.*Logger$/i, type: 'service' },
|
|
61
|
-
{ pattern: /^.*Server$/i, type: 'service' },
|
|
62
|
-
{ pattern: /^.*Broker$/i, type: 'queue' },
|
|
63
|
-
{ pattern: /^.*Worker$/i, type: 'service' },
|
|
64
|
-
{ pattern: /^.*Consumer$/i, type: 'service' },
|
|
65
|
-
{ pattern: /^.*Producer$/i, type: 'service' },
|
|
66
|
-
{ pattern: /^.*Publisher$/i, type: 'service' },
|
|
67
|
-
{ pattern: /^.*Subscriber$/i, type: 'service' },
|
|
68
|
-
{ pattern: /^.*Listener$/i, type: 'service' },
|
|
69
|
-
// New -er/-or suffixes that are services, not actors
|
|
70
|
-
{ pattern: /^.*Watcher$/i, type: 'service' },
|
|
71
|
-
{ pattern: /^.*Executor$/i, type: 'service' },
|
|
72
|
-
{ pattern: /^.*Aggregator$/i, type: 'service' },
|
|
73
|
-
{ pattern: /^.*Collector$/i, type: 'service' },
|
|
74
|
-
{ pattern: /^.*Transformer$/i, type: 'service' },
|
|
75
|
-
{ pattern: /^.*Validator$/i, type: 'service' },
|
|
76
|
-
{ pattern: /^.*Generator$/i, type: 'service' },
|
|
77
|
-
{ pattern: /^.*Indexer$/i, type: 'service' },
|
|
78
|
-
{ pattern: /^.*Crawler$/i, type: 'service' },
|
|
79
|
-
{ pattern: /^.*Scanner$/i, type: 'service' },
|
|
80
|
-
{ pattern: /^.*Parser$/i, type: 'service' },
|
|
81
|
-
{ pattern: /^.*Emitter$/i, type: 'service' },
|
|
82
|
-
{ pattern: /^.*Exporter$/i, type: 'service' },
|
|
83
|
-
{ pattern: /^.*Importer$/i, type: 'service' },
|
|
84
|
-
{ pattern: /^.*Loader$/i, type: 'service' },
|
|
85
|
-
{ pattern: /^.*Renderer$/i, type: 'service' },
|
|
86
|
-
{ pattern: /^.*Checker$/i, type: 'service' },
|
|
87
|
-
{ pattern: /^.*Inspector$/i, type: 'service' },
|
|
88
|
-
{ pattern: /^.*Encoder$/i, type: 'service' },
|
|
89
|
-
{ pattern: /^.*Decoder$/i, type: 'service' },
|
|
90
|
-
{ pattern: /^.*Notifier$/i, type: 'service' },
|
|
91
|
-
|
|
92
|
-
// ── 2. Networking patterns ──────────────────────────────
|
|
93
|
-
{ pattern: /Gateway/i, type: 'networking' },
|
|
94
|
-
{ pattern: /GW$/i, type: 'networking' },
|
|
95
|
-
{ pattern: /Proxy/i, type: 'networking' },
|
|
96
|
-
{ pattern: /LB$/i, type: 'networking' },
|
|
97
|
-
{ pattern: /LoadBalancer/i, type: 'networking' },
|
|
98
|
-
{ pattern: /CDN/i, type: 'networking' },
|
|
99
|
-
{ pattern: /Firewall/i, type: 'networking' },
|
|
100
|
-
{ pattern: /WAF$/i, type: 'networking' },
|
|
101
|
-
{ pattern: /DNS/i, type: 'networking' },
|
|
102
|
-
{ pattern: /Ingress/i, type: 'networking' },
|
|
103
|
-
// Named products & patterns
|
|
104
|
-
{ pattern: /Nginx/i, type: 'networking' },
|
|
105
|
-
{ pattern: /Traefik/i, type: 'networking' },
|
|
106
|
-
{ pattern: /Envoy/i, type: 'networking' },
|
|
107
|
-
{ pattern: /Istio/i, type: 'networking' },
|
|
108
|
-
{ pattern: /Kong/i, type: 'networking' },
|
|
109
|
-
{ pattern: /Akamai/i, type: 'networking' },
|
|
110
|
-
{ pattern: /Cloudflare/i, type: 'networking' },
|
|
111
|
-
{ pattern: /Mesh$/i, type: 'networking' },
|
|
112
|
-
{ pattern: /ServiceMesh/i, type: 'networking' },
|
|
113
40
|
|
|
114
|
-
// ──
|
|
41
|
+
// ── 1. Database patterns ────────────────────────────────
|
|
115
42
|
{ pattern: /DB$/i, type: 'database' },
|
|
116
43
|
{ pattern: /Database/i, type: 'database' },
|
|
117
44
|
{ pattern: /Datastore/i, type: 'database' },
|
|
@@ -146,17 +73,16 @@ const PARTICIPANT_RULES: readonly InferenceRule[] = [
|
|
|
146
73
|
{ pattern: /Presto/i, type: 'database' },
|
|
147
74
|
{ pattern: /Table$/i, type: 'database' },
|
|
148
75
|
|
|
149
|
-
// ──
|
|
76
|
+
// ── 2. Cache patterns ──────────────────────────────────
|
|
150
77
|
{ pattern: /Cache/i, type: 'cache' },
|
|
151
78
|
{ pattern: /Redis/i, type: 'cache' },
|
|
152
79
|
{ pattern: /Memcache/i, type: 'cache' },
|
|
153
|
-
// CDN already matched by networking above
|
|
154
80
|
// Named products
|
|
155
81
|
{ pattern: /Dragonfly/i, type: 'cache' },
|
|
156
82
|
{ pattern: /Hazelcast/i, type: 'cache' },
|
|
157
83
|
{ pattern: /Valkey/i, type: 'cache' },
|
|
158
84
|
|
|
159
|
-
// ──
|
|
85
|
+
// ── 3. Queue/Messaging patterns ─────────────────────────
|
|
160
86
|
{ pattern: /Queue/i, type: 'queue' },
|
|
161
87
|
{ pattern: /MQ$/i, type: 'queue' },
|
|
162
88
|
{ pattern: /SQS/i, type: 'queue' },
|
|
@@ -169,6 +95,7 @@ const PARTICIPANT_RULES: readonly InferenceRule[] = [
|
|
|
169
95
|
{ pattern: /Stream$/i, type: 'queue' },
|
|
170
96
|
{ pattern: /SNS/i, type: 'queue' },
|
|
171
97
|
{ pattern: /PubSub/i, type: 'queue' },
|
|
98
|
+
{ pattern: /Broker$/i, type: 'queue' },
|
|
172
99
|
// Named products & patterns
|
|
173
100
|
{ pattern: /NATS/i, type: 'queue' },
|
|
174
101
|
{ pattern: /Pulsar/i, type: 'queue' },
|
|
@@ -180,7 +107,7 @@ const PARTICIPANT_RULES: readonly InferenceRule[] = [
|
|
|
180
107
|
{ pattern: /EventHub/i, type: 'queue' },
|
|
181
108
|
{ pattern: /Channel$/i, type: 'queue' },
|
|
182
109
|
|
|
183
|
-
// ──
|
|
110
|
+
// ── 4. Actor patterns ──────────────────────────────────
|
|
184
111
|
// Exact matches first
|
|
185
112
|
{ pattern: /^Admin$/i, type: 'actor' },
|
|
186
113
|
{ pattern: /^User$/i, type: 'actor' },
|
|
@@ -199,101 +126,11 @@ const PARTICIPANT_RULES: readonly InferenceRule[] = [
|
|
|
199
126
|
{ pattern: /^Fan$/i, type: 'actor' },
|
|
200
127
|
{ pattern: /^Purchaser$/i, type: 'actor' },
|
|
201
128
|
{ pattern: /^Reviewer$/i, type: 'actor' },
|
|
202
|
-
// Suffix rules
|
|
129
|
+
// Suffix rules
|
|
203
130
|
{ pattern: /User$/i, type: 'actor' },
|
|
204
131
|
{ pattern: /Actor$/i, type: 'actor' },
|
|
205
132
|
{ pattern: /Analyst$/i, type: 'actor' },
|
|
206
133
|
{ pattern: /Staff$/i, type: 'actor' },
|
|
207
|
-
|
|
208
|
-
// ── 7. Frontend patterns ────────────────────────────────
|
|
209
|
-
{ pattern: /App$/i, type: 'frontend' },
|
|
210
|
-
{ pattern: /Application/i, type: 'frontend' },
|
|
211
|
-
{ pattern: /Mobile/i, type: 'frontend' },
|
|
212
|
-
{ pattern: /iOS/i, type: 'frontend' },
|
|
213
|
-
{ pattern: /Android/i, type: 'frontend' },
|
|
214
|
-
{ pattern: /Web/i, type: 'frontend' },
|
|
215
|
-
{ pattern: /Browser/i, type: 'frontend' },
|
|
216
|
-
{ pattern: /Frontend/i, type: 'frontend' },
|
|
217
|
-
{ pattern: /UI$/i, type: 'frontend' },
|
|
218
|
-
{ pattern: /Dashboard/i, type: 'frontend' },
|
|
219
|
-
{ pattern: /CLI$/i, type: 'frontend' },
|
|
220
|
-
{ pattern: /Terminal/i, type: 'frontend' },
|
|
221
|
-
// Frameworks & patterns
|
|
222
|
-
{ pattern: /React/i, type: 'frontend' },
|
|
223
|
-
{ pattern: /^Vue$/i, type: 'frontend' },
|
|
224
|
-
{ pattern: /Angular/i, type: 'frontend' },
|
|
225
|
-
{ pattern: /Svelte/i, type: 'frontend' },
|
|
226
|
-
{ pattern: /NextJS/i, type: 'frontend' },
|
|
227
|
-
{ pattern: /Nuxt/i, type: 'frontend' },
|
|
228
|
-
{ pattern: /Remix/i, type: 'frontend' },
|
|
229
|
-
{ pattern: /Electron/i, type: 'frontend' },
|
|
230
|
-
{ pattern: /Tauri/i, type: 'frontend' },
|
|
231
|
-
{ pattern: /Widget$/i, type: 'frontend' },
|
|
232
|
-
{ pattern: /Portal/i, type: 'frontend' },
|
|
233
|
-
{ pattern: /Console$/i, type: 'frontend' },
|
|
234
|
-
{ pattern: /^SPA$/i, type: 'frontend' },
|
|
235
|
-
{ pattern: /^PWA$/i, type: 'frontend' },
|
|
236
|
-
|
|
237
|
-
// ── 8. Service patterns ─────────────────────────────────
|
|
238
|
-
{ pattern: /Service/i, type: 'service' },
|
|
239
|
-
{ pattern: /Svc$/i, type: 'service' },
|
|
240
|
-
{ pattern: /API$/i, type: 'service' },
|
|
241
|
-
{ pattern: /Lambda/i, type: 'service' },
|
|
242
|
-
{ pattern: /Function$/i, type: 'service' },
|
|
243
|
-
{ pattern: /Fn$/i, type: 'service' },
|
|
244
|
-
{ pattern: /Job$/i, type: 'service' },
|
|
245
|
-
{ pattern: /Cron/i, type: 'service' },
|
|
246
|
-
{ pattern: /Microservice/i, type: 'service' },
|
|
247
|
-
// Auth
|
|
248
|
-
{ pattern: /^Auth$/i, type: 'service' },
|
|
249
|
-
{ pattern: /^AuthN$/i, type: 'service' },
|
|
250
|
-
{ pattern: /^AuthZ$/i, type: 'service' },
|
|
251
|
-
{ pattern: /^SSO$/i, type: 'service' },
|
|
252
|
-
{ pattern: /OAuth/i, type: 'service' },
|
|
253
|
-
{ pattern: /^OIDC$/i, type: 'service' },
|
|
254
|
-
// SaaS
|
|
255
|
-
{ pattern: /Stripe/i, type: 'service' },
|
|
256
|
-
{ pattern: /Twilio/i, type: 'service' },
|
|
257
|
-
{ pattern: /SendGrid/i, type: 'service' },
|
|
258
|
-
{ pattern: /Mailgun/i, type: 'service' },
|
|
259
|
-
// Cloud/infra
|
|
260
|
-
{ pattern: /^S3$/i, type: 'service' },
|
|
261
|
-
{ pattern: /^Blob$/i, type: 'service' },
|
|
262
|
-
{ pattern: /Vercel/i, type: 'service' },
|
|
263
|
-
{ pattern: /Netlify/i, type: 'service' },
|
|
264
|
-
{ pattern: /Heroku/i, type: 'service' },
|
|
265
|
-
{ pattern: /Docker/i, type: 'service' },
|
|
266
|
-
{ pattern: /Kubernetes/i, type: 'service' },
|
|
267
|
-
{ pattern: /K8s/i, type: 'service' },
|
|
268
|
-
{ pattern: /Terraform/i, type: 'service' },
|
|
269
|
-
// Security
|
|
270
|
-
{ pattern: /Vault/i, type: 'service' },
|
|
271
|
-
{ pattern: /^HSM$/i, type: 'service' },
|
|
272
|
-
{ pattern: /KMS/i, type: 'service' },
|
|
273
|
-
{ pattern: /^IAM$/i, type: 'service' },
|
|
274
|
-
// AI/ML
|
|
275
|
-
{ pattern: /^LLM$/i, type: 'service' },
|
|
276
|
-
{ pattern: /GPT/i, type: 'service' },
|
|
277
|
-
{ pattern: /^Claude$/i, type: 'service' },
|
|
278
|
-
{ pattern: /Embedding/i, type: 'service' },
|
|
279
|
-
{ pattern: /Inference/i, type: 'service' },
|
|
280
|
-
// Suffixes & patterns
|
|
281
|
-
{ pattern: /Pipeline$/i, type: 'service' },
|
|
282
|
-
{ pattern: /Registry/i, type: 'service' },
|
|
283
|
-
{ pattern: /Engine$/i, type: 'service' },
|
|
284
|
-
{ pattern: /Daemon/i, type: 'service' },
|
|
285
|
-
|
|
286
|
-
// ── 9. External patterns ────────────────────────────────
|
|
287
|
-
{ pattern: /External/i, type: 'external' },
|
|
288
|
-
{ pattern: /Ext$/i, type: 'external' },
|
|
289
|
-
{ pattern: /ThirdParty/i, type: 'external' },
|
|
290
|
-
{ pattern: /3P$/i, type: 'external' },
|
|
291
|
-
{ pattern: /Vendor/i, type: 'external' },
|
|
292
|
-
// Named products & patterns
|
|
293
|
-
{ pattern: /Callback/i, type: 'external' },
|
|
294
|
-
{ pattern: /^AWS$/i, type: 'external' },
|
|
295
|
-
{ pattern: /^GCP$/i, type: 'external' },
|
|
296
|
-
{ pattern: /Azure/i, type: 'external' },
|
|
297
134
|
];
|
|
298
135
|
|
|
299
136
|
/**
|
package/src/sequence/renderer.ts
CHANGED
|
@@ -49,7 +49,6 @@ const PARTICIPANT_BOX_HEIGHT = 50;
|
|
|
49
49
|
const TOP_MARGIN = 20;
|
|
50
50
|
const TITLE_HEIGHT = 30;
|
|
51
51
|
const PARTICIPANT_Y_OFFSET = 10;
|
|
52
|
-
const SERVICE_BORDER_RADIUS = 10;
|
|
53
52
|
const MESSAGE_START_OFFSET = 30;
|
|
54
53
|
const LIFELINE_TAIL = 30;
|
|
55
54
|
const ARROWHEAD_SIZE = 8;
|
|
@@ -180,25 +179,6 @@ function renderRectParticipant(
|
|
|
180
179
|
.attr('stroke-width', SW);
|
|
181
180
|
}
|
|
182
181
|
|
|
183
|
-
function renderServiceParticipant(
|
|
184
|
-
g: d3Selection.Selection<SVGGElement, unknown, null, undefined>,
|
|
185
|
-
palette: PaletteColors,
|
|
186
|
-
isDark: boolean,
|
|
187
|
-
color?: string,
|
|
188
|
-
solid?: boolean
|
|
189
|
-
): void {
|
|
190
|
-
g.append('rect')
|
|
191
|
-
.attr('x', -W / 2)
|
|
192
|
-
.attr('y', 0)
|
|
193
|
-
.attr('width', W)
|
|
194
|
-
.attr('height', H)
|
|
195
|
-
.attr('rx', SERVICE_BORDER_RADIUS)
|
|
196
|
-
.attr('ry', SERVICE_BORDER_RADIUS)
|
|
197
|
-
.attr('fill', fill(palette, isDark, color, solid))
|
|
198
|
-
.attr('stroke', stroke(palette, color))
|
|
199
|
-
.attr('stroke-width', SW);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
182
|
function renderActorParticipant(
|
|
203
183
|
g: d3Selection.Selection<SVGGElement, unknown, null, undefined>,
|
|
204
184
|
palette: PaletteColors,
|
|
@@ -443,99 +423,6 @@ function renderCacheParticipant(
|
|
|
443
423
|
.attr('stroke-dasharray', dash);
|
|
444
424
|
}
|
|
445
425
|
|
|
446
|
-
function renderNetworkingParticipant(
|
|
447
|
-
g: d3Selection.Selection<SVGGElement, unknown, null, undefined>,
|
|
448
|
-
palette: PaletteColors,
|
|
449
|
-
isDark: boolean,
|
|
450
|
-
color?: string,
|
|
451
|
-
solid?: boolean
|
|
452
|
-
): void {
|
|
453
|
-
// Hexagon fitting within W x H
|
|
454
|
-
const inset = 16;
|
|
455
|
-
const points = [
|
|
456
|
-
`${-W / 2 + inset},0`,
|
|
457
|
-
`${W / 2 - inset},0`,
|
|
458
|
-
`${W / 2},${H / 2}`,
|
|
459
|
-
`${W / 2 - inset},${H}`,
|
|
460
|
-
`${-W / 2 + inset},${H}`,
|
|
461
|
-
`${-W / 2},${H / 2}`,
|
|
462
|
-
].join(' ');
|
|
463
|
-
g.append('polygon')
|
|
464
|
-
.attr('points', points)
|
|
465
|
-
.attr('fill', fill(palette, isDark, color, solid))
|
|
466
|
-
.attr('stroke', stroke(palette, color))
|
|
467
|
-
.attr('stroke-width', SW);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
function renderFrontendParticipant(
|
|
471
|
-
g: d3Selection.Selection<SVGGElement, unknown, null, undefined>,
|
|
472
|
-
palette: PaletteColors,
|
|
473
|
-
isDark: boolean,
|
|
474
|
-
color?: string,
|
|
475
|
-
solid?: boolean
|
|
476
|
-
): void {
|
|
477
|
-
// Monitor shape fitting within W x H
|
|
478
|
-
const screenH = H - 10;
|
|
479
|
-
const s = stroke(palette, color);
|
|
480
|
-
g.append('rect')
|
|
481
|
-
.attr('x', -W / 2)
|
|
482
|
-
.attr('y', 0)
|
|
483
|
-
.attr('width', W)
|
|
484
|
-
.attr('height', screenH)
|
|
485
|
-
.attr('rx', 3)
|
|
486
|
-
.attr('ry', 3)
|
|
487
|
-
.attr('fill', fill(palette, isDark, color, solid))
|
|
488
|
-
.attr('stroke', s)
|
|
489
|
-
.attr('stroke-width', SW);
|
|
490
|
-
// Stand
|
|
491
|
-
g.append('line')
|
|
492
|
-
.attr('x1', 0)
|
|
493
|
-
.attr('y1', screenH)
|
|
494
|
-
.attr('x2', 0)
|
|
495
|
-
.attr('y2', H - 2)
|
|
496
|
-
.attr('stroke', s)
|
|
497
|
-
.attr('stroke-width', SW);
|
|
498
|
-
// Base
|
|
499
|
-
g.append('line')
|
|
500
|
-
.attr('x1', -14)
|
|
501
|
-
.attr('y1', H - 2)
|
|
502
|
-
.attr('x2', 14)
|
|
503
|
-
.attr('y2', H - 2)
|
|
504
|
-
.attr('stroke', s)
|
|
505
|
-
.attr('stroke-width', SW);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function renderExternalParticipant(
|
|
509
|
-
g: d3Selection.Selection<SVGGElement, unknown, null, undefined>,
|
|
510
|
-
palette: PaletteColors,
|
|
511
|
-
isDark: boolean,
|
|
512
|
-
color?: string,
|
|
513
|
-
solid?: boolean
|
|
514
|
-
): void {
|
|
515
|
-
// Dashed border rectangle
|
|
516
|
-
g.append('rect')
|
|
517
|
-
.attr('x', -W / 2)
|
|
518
|
-
.attr('y', 0)
|
|
519
|
-
.attr('width', W)
|
|
520
|
-
.attr('height', H)
|
|
521
|
-
.attr('rx', 2)
|
|
522
|
-
.attr('ry', 2)
|
|
523
|
-
.attr('fill', fill(palette, isDark, color, solid))
|
|
524
|
-
.attr('stroke', stroke(palette, color))
|
|
525
|
-
.attr('stroke-width', SW)
|
|
526
|
-
.attr('stroke-dasharray', '6 3');
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
function renderGatewayParticipant(
|
|
530
|
-
g: d3Selection.Selection<SVGGElement, unknown, null, undefined>,
|
|
531
|
-
palette: PaletteColors,
|
|
532
|
-
isDark: boolean,
|
|
533
|
-
color?: string,
|
|
534
|
-
_solid?: boolean
|
|
535
|
-
): void {
|
|
536
|
-
renderRectParticipant(g, palette, isDark, color);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
426
|
// ============================================================
|
|
540
427
|
// Collapsible Section Support
|
|
541
428
|
// ============================================================
|
|
@@ -1528,7 +1415,7 @@ export function renderSequenceDiagram(
|
|
|
1528
1415
|
// Ensure contentBottomY accounts for all note extents
|
|
1529
1416
|
const lastStep = renderSteps[renderSteps.length - 1];
|
|
1530
1417
|
const lastIsSelfCall =
|
|
1531
|
-
lastStep
|
|
1418
|
+
lastStep?.type === 'call' && lastStep.from === lastStep.to;
|
|
1532
1419
|
const lastStepTrailing = lastIsSelfCall ? SELF_CALL_HEIGHT + 25 : stepSpacing;
|
|
1533
1420
|
let contentBottomY =
|
|
1534
1421
|
renderSteps.length > 0
|
|
@@ -1968,6 +1855,13 @@ export function renderSequenceDiagram(
|
|
|
1968
1855
|
// FRAME_PADDING_TOP declared earlier (near BLOCK_HEADER_SPACE)
|
|
1969
1856
|
const FRAME_PADDING_BOTTOM = 15;
|
|
1970
1857
|
const FRAME_LABEL_HEIGHT = 18;
|
|
1858
|
+
// Self-loop projects ACTIVATION_WIDTH/2 + SELF_CALL_WIDTH (=35) past the
|
|
1859
|
+
// lifeline; FRAME_PADDING_X (=30) leaves no breathing room. When a block
|
|
1860
|
+
// contains a self-arrow, extend the frame on the loop's side so the loop
|
|
1861
|
+
// sits comfortably inside.
|
|
1862
|
+
const SELF_ARROW_PROJECTION = ACTIVATION_WIDTH / 2 + SELF_CALL_WIDTH;
|
|
1863
|
+
const SELF_ARROW_FRAME_PAD = 10;
|
|
1864
|
+
const frameRightmostX = Math.max(...Array.from(participantX.values()));
|
|
1971
1865
|
|
|
1972
1866
|
// Collect message indices from an element subtree
|
|
1973
1867
|
const collectMsgIndices = (els: SequenceElement[]): number[] => {
|
|
@@ -2063,14 +1957,46 @@ export function renderSequenceDiagram(
|
|
|
2063
1957
|
}
|
|
2064
1958
|
}
|
|
2065
1959
|
|
|
2066
|
-
|
|
1960
|
+
// Self-arrow geometry: extend frame on the loop's side so loops sit
|
|
1961
|
+
// comfortably inside, and extend vertically if the block's last step
|
|
1962
|
+
// is a self-call (whose loop drops SELF_CALL_HEIGHT below stepY).
|
|
1963
|
+
let extraLeft = 0;
|
|
1964
|
+
let extraRight = 0;
|
|
1965
|
+
let maxStepIsSelfCall = false;
|
|
1966
|
+
for (const mi of allIndices) {
|
|
1967
|
+
const m = messages[mi];
|
|
1968
|
+
if (m.from === m.to) {
|
|
1969
|
+
const px = participantX.get(m.from);
|
|
1970
|
+
if (px !== undefined) {
|
|
1971
|
+
const flipLeft = px === frameRightmostX;
|
|
1972
|
+
if (flipLeft) {
|
|
1973
|
+
const loopMin = px - SELF_ARROW_PROJECTION;
|
|
1974
|
+
const need =
|
|
1975
|
+
minPX - FRAME_PADDING_X - loopMin + SELF_ARROW_FRAME_PAD;
|
|
1976
|
+
if (need > 0) extraLeft = Math.max(extraLeft, need);
|
|
1977
|
+
} else {
|
|
1978
|
+
const loopMax = px + SELF_ARROW_PROJECTION;
|
|
1979
|
+
const need =
|
|
1980
|
+
loopMax - (maxPX + FRAME_PADDING_X) + SELF_ARROW_FRAME_PAD;
|
|
1981
|
+
if (need > 0) extraRight = Math.max(extraRight, need);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
if (msgToLastStep.get(mi) === maxStep) {
|
|
1985
|
+
maxStepIsSelfCall = true;
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
const frameX = minPX - FRAME_PADDING_X - extraLeft;
|
|
2067
1991
|
const frameY = stepY(minStep) - FRAME_PADDING_TOP;
|
|
2068
|
-
const frameW =
|
|
1992
|
+
const frameW =
|
|
1993
|
+
maxPX - minPX + FRAME_PADDING_X * 2 + extraLeft + extraRight;
|
|
2069
1994
|
const frameH =
|
|
2070
1995
|
stepY(maxStep) -
|
|
2071
1996
|
stepY(minStep) +
|
|
2072
1997
|
FRAME_PADDING_TOP +
|
|
2073
|
-
FRAME_PADDING_BOTTOM
|
|
1998
|
+
FRAME_PADDING_BOTTOM +
|
|
1999
|
+
(maxStepIsSelfCall ? SELF_CALL_HEIGHT : 0);
|
|
2074
2000
|
|
|
2075
2001
|
// Frame border
|
|
2076
2002
|
svg
|
|
@@ -2093,7 +2019,7 @@ export function renderSequenceDiagram(
|
|
|
2093
2019
|
x: frameX + 6,
|
|
2094
2020
|
y: frameY + FRAME_LABEL_HEIGHT - 4,
|
|
2095
2021
|
text: `${el.type} ${el.label}`,
|
|
2096
|
-
bold:
|
|
2022
|
+
bold: false,
|
|
2097
2023
|
italic: false,
|
|
2098
2024
|
blockLine: el.lineNumber,
|
|
2099
2025
|
});
|
|
@@ -2121,7 +2047,7 @@ export function renderSequenceDiagram(
|
|
|
2121
2047
|
y: dividerY + 14,
|
|
2122
2048
|
text: `else if ${branchData.label}`,
|
|
2123
2049
|
bold: false,
|
|
2124
|
-
italic:
|
|
2050
|
+
italic: false,
|
|
2125
2051
|
blockLine: branchData.lineNumber,
|
|
2126
2052
|
});
|
|
2127
2053
|
}
|
|
@@ -2150,7 +2076,7 @@ export function renderSequenceDiagram(
|
|
|
2150
2076
|
y: dividerY + 14,
|
|
2151
2077
|
text: 'else',
|
|
2152
2078
|
bold: false,
|
|
2153
|
-
italic:
|
|
2079
|
+
italic: false,
|
|
2154
2080
|
blockLine: el.elseLineNumber,
|
|
2155
2081
|
});
|
|
2156
2082
|
}
|
|
@@ -2292,7 +2218,7 @@ export function renderSequenceDiagram(
|
|
|
2292
2218
|
|
|
2293
2219
|
// Render section dividers
|
|
2294
2220
|
const leftmostX = Math.min(...Array.from(participantX.values()));
|
|
2295
|
-
const rightmostX =
|
|
2221
|
+
const rightmostX = frameRightmostX;
|
|
2296
2222
|
const sectionLineX1 = leftmostX - PARTICIPANT_BOX_WIDTH / 2 - 10;
|
|
2297
2223
|
const sectionLineX2 = rightmostX + PARTICIPANT_BOX_WIDTH / 2 + 10;
|
|
2298
2224
|
|
|
@@ -2853,27 +2779,12 @@ function renderParticipant(
|
|
|
2853
2779
|
case 'database':
|
|
2854
2780
|
renderDatabaseParticipant(g, palette, isDark, color, solid);
|
|
2855
2781
|
break;
|
|
2856
|
-
case 'service':
|
|
2857
|
-
renderServiceParticipant(g, palette, isDark, color, solid);
|
|
2858
|
-
break;
|
|
2859
2782
|
case 'queue':
|
|
2860
2783
|
renderQueueParticipant(g, palette, isDark, color, solid);
|
|
2861
2784
|
break;
|
|
2862
2785
|
case 'cache':
|
|
2863
2786
|
renderCacheParticipant(g, palette, isDark, color, solid);
|
|
2864
2787
|
break;
|
|
2865
|
-
case 'networking':
|
|
2866
|
-
renderNetworkingParticipant(g, palette, isDark, color, solid);
|
|
2867
|
-
break;
|
|
2868
|
-
case 'frontend':
|
|
2869
|
-
renderFrontendParticipant(g, palette, isDark, color, solid);
|
|
2870
|
-
break;
|
|
2871
|
-
case 'external':
|
|
2872
|
-
renderExternalParticipant(g, palette, isDark, color, solid);
|
|
2873
|
-
break;
|
|
2874
|
-
case 'gateway':
|
|
2875
|
-
renderGatewayParticipant(g, palette, isDark, color, solid);
|
|
2876
|
-
break;
|
|
2877
2788
|
default:
|
|
2878
2789
|
renderRectParticipant(g, palette, isDark, color, solid);
|
|
2879
2790
|
break;
|
package/src/sitemap/parser.ts
CHANGED