@diagrammo/dgmo 0.46.0 → 0.48.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.d.ts +3 -855
- package/dist/advanced.js +541 -70107
- package/dist/analyzer-UYKBXAKR.js +15 -0
- package/dist/auto.js +1 -1
- package/dist/auto.mjs +1 -1
- package/dist/block.js +83 -69417
- package/dist/calculator-LDYQCDDY.js +11 -0
- package/dist/charts-d3-QOZHKTCT.js +25 -0
- package/dist/chunk-2DXMZRWJ.js +800 -0
- package/dist/chunk-2LACINWN.js +786 -0
- package/dist/chunk-33LCUH4H.js +893 -0
- package/dist/chunk-3CIOJ5YJ.js +35 -0
- package/dist/chunk-3HDBAXF5.js +86 -0
- package/dist/chunk-3JLKYGBQ.js +290 -0
- package/dist/chunk-3MQQNXPD.js +415 -0
- package/dist/chunk-3R5POCTZ.js +967 -0
- package/dist/chunk-3SMGIJDD.js +112 -0
- package/dist/chunk-44CM72SQ.js +409 -0
- package/dist/chunk-4GRI7MEB.js +1604 -0
- package/dist/chunk-4OHACBGX.js +606 -0
- package/dist/chunk-5DH3KYFH.js +21 -0
- package/dist/chunk-5MLYVTYM.js +76 -0
- package/dist/chunk-5QXL4NYS.js +553 -0
- package/dist/chunk-5WRI5ZAA.js +31 -0
- package/dist/chunk-6EK55ART.js +117 -0
- package/dist/chunk-6FZYCKXB.js +2065 -0
- package/dist/chunk-6HO5ZTXK.js +277 -0
- package/dist/chunk-6KFWC4CO.js +7 -0
- package/dist/chunk-6MTWIQVO.js +121 -0
- package/dist/chunk-6NYWYNBI.js +329 -0
- package/dist/chunk-6SM5NJQH.js +620 -0
- package/dist/chunk-725LDASD.js +422 -0
- package/dist/chunk-76OWZ3BA.js +130 -0
- package/{src/block/parser.ts → dist/chunk-7JEGTRLD.js} +100 -189
- package/dist/chunk-A44USUV7.js +923 -0
- package/dist/chunk-A4GOY3PE.js +25 -0
- package/dist/chunk-A4VFQH5G.js +251 -0
- package/dist/chunk-AELOBFW5.js +375 -0
- package/dist/chunk-AN2MOO5T.js +522 -0
- package/dist/chunk-BMR4CIEO.js +41 -0
- package/dist/chunk-BS7SV7C3.js +51 -0
- package/dist/chunk-BUPDIHML.js +613 -0
- package/dist/chunk-BUVJFRPY.js +887 -0
- package/dist/chunk-C3Z4DX3V.js +112 -0
- package/dist/chunk-CI2G3OEB.js +1818 -0
- package/dist/chunk-CKNVO24D.js +54 -0
- package/dist/chunk-CUVHQHZ7.js +391 -0
- package/dist/chunk-CXK4DZH3.js +114 -0
- package/dist/{editor.cjs → chunk-DKEPS5LH.js} +14 -225
- package/dist/chunk-DU7W62N5.js +487 -0
- package/dist/chunk-DZAQBW35.js +751 -0
- package/dist/chunk-EB6FPQUP.js +316 -0
- package/dist/chunk-EMS2SK26.js +376 -0
- package/dist/chunk-ERLBPDGU.js +510 -0
- package/dist/chunk-EVCVDTXE.js +732 -0
- package/dist/chunk-FCIIHT24.js +384 -0
- package/dist/chunk-FFUN5D66.js +97 -0
- package/dist/chunk-FITOLXSX.js +714 -0
- package/dist/chunk-FMDSF3OV.js +68 -0
- package/dist/chunk-FNB3PNGF.js +410 -0
- package/dist/chunk-G2H74XJZ.js +2104 -0
- package/dist/chunk-GMR5MFHJ.js +339 -0
- package/dist/chunk-H6PI3KJT.js +305 -0
- package/dist/chunk-I46X5VUA.js +59 -0
- package/dist/chunk-I5IGB56C.js +255 -0
- package/dist/chunk-IH6ZMVQ5.js +3950 -0
- package/dist/chunk-IJZFN3HM.js +463 -0
- package/dist/chunk-IPWPWM6R.js +3079 -0
- package/dist/chunk-IYULMRBE.js +23 -0
- package/dist/chunk-IZMRON6C.js +167 -0
- package/dist/chunk-J5FIXQRT.js +813 -0
- package/dist/chunk-JED6ZDEE.js +73 -0
- package/dist/chunk-JEUJOX5X.js +136 -0
- package/dist/chunk-JM4LEPY3.js +436 -0
- package/dist/chunk-L3UHP6TD.js +101 -0
- package/dist/chunk-L66GEFB6.js +524 -0
- package/dist/chunk-LIOHW36Z.js +469 -0
- package/dist/chunk-LP7EC4MI.js +16 -0
- package/dist/chunk-MITI6NOL.js +548 -0
- package/dist/chunk-MO5YHHMQ.js +168 -0
- package/dist/chunk-MZSBFAPJ.js +885 -0
- package/dist/chunk-NNAAJVBU.js +9 -0
- package/dist/chunk-NOTA6TEI.js +47 -0
- package/dist/chunk-NURGB5Z4.js +335 -0
- package/dist/chunk-NX4EQS3U.js +1062 -0
- package/dist/chunk-NXMESY4J.js +1 -0
- package/dist/chunk-OJR3MQYV.js +1458 -0
- package/dist/chunk-OURHMODR.js +454 -0
- package/dist/chunk-P235SVAH.js +1057 -0
- package/dist/chunk-P4RH4OSP.js +1038 -0
- package/dist/chunk-P6T66CCZ.js +226 -0
- package/dist/chunk-PBAHEPLQ.js +223 -0
- package/dist/chunk-PBHE5UG6.js +1530 -0
- package/dist/chunk-PDKUANDZ.js +35 -0
- package/dist/chunk-PWIHZ6NJ.js +296 -0
- package/{src/c4/layout.ts → dist/chunk-QEBUY5NY.js} +384 -1140
- package/dist/chunk-QLCCJEJI.js +253 -0
- package/dist/chunk-QOOUC2J5.js +52 -0
- package/dist/chunk-QYTPPDAO.js +402 -0
- package/dist/chunk-R3XI2VC7.js +801 -0
- package/dist/chunk-RCERWOJR.js +154 -0
- package/dist/chunk-RJE5JTVK.js +362 -0
- package/dist/chunk-RTLBSQ3G.js +1155 -0
- package/dist/chunk-RUNNI6VE.js +448 -0
- package/dist/chunk-RVMRZAPB.js +354 -0
- package/dist/chunk-RYEGFMUO.js +955 -0
- package/dist/chunk-RZCF2VVM.js +528 -0
- package/dist/chunk-SML67VPV.js +1025 -0
- package/dist/chunk-SQ7IY53Q.js +44 -0
- package/dist/chunk-SQAG3UXH.js +161 -0
- package/dist/chunk-SRTU6HMQ.js +1851 -0
- package/dist/chunk-T2OE3YQB.js +550 -0
- package/dist/chunk-T3UBLACL.js +299 -0
- package/dist/chunk-TLVIHFRO.js +296 -0
- package/dist/chunk-TYS6CSS5.js +52 -0
- package/dist/chunk-U6EQI3U6.js +130 -0
- package/dist/chunk-UJ4FJLBF.js +86 -0
- package/dist/chunk-ULOEATBZ.js +347 -0
- package/dist/chunk-UNCCZ3NU.js +127 -0
- package/dist/chunk-USZW2HHU.js +197 -0
- package/dist/chunk-UWN33VNX.js +1610 -0
- package/dist/chunk-UZ7EZW3B.js +40 -0
- package/dist/chunk-VOKGH627.js +37 -0
- package/dist/chunk-WBRZJOEC.js +204 -0
- package/dist/chunk-WCSOJFU3.js +380 -0
- package/dist/chunk-WCT3NYCV.js +160 -0
- package/dist/chunk-WVAJFAIA.js +529 -0
- package/dist/chunk-X5LCYCL5.js +394 -0
- package/dist/chunk-XAKEUMEJ.js +1256 -0
- package/dist/chunk-Y2ZE3MSG.js +590 -0
- package/dist/chunk-Y3J7VR52.js +646 -0
- package/dist/chunk-Y5QGVTVC.js +33 -0
- package/dist/chunk-YAA6IID3.js +548 -0
- package/dist/chunk-YGJTDXVR.js +692 -0
- package/{src/utils/time-ticks.ts → dist/chunk-YRYKTYMQ.js} +45 -112
- package/dist/chunk-YYVTBZZC.js +61 -0
- package/dist/chunk-Z5CGSFJB.js +647 -0
- package/{src/gantt/calculator.ts → dist/chunk-Z72XRXD4.js} +236 -428
- package/dist/chunk-ZGR7ULFJ.js +978 -0
- package/dist/chunk-ZJEL4H2R.js +851 -0
- package/dist/chunk-ZO27FGJS.js +554 -0
- package/dist/collapse-CLPJD2RL.js +11 -0
- package/dist/collapse-SLINJQW6.js +9 -0
- package/dist/collapse-ZWUFFCK2.js +9 -0
- package/dist/compute-G55RG53N.js +8 -0
- package/dist/dgmo-router-DZCIDUO5.js +73 -0
- package/dist/dimensions-J3FHIMJ6.js +22 -0
- package/dist/element.js +1 -1
- package/dist/element.mjs +1 -1
- package/dist/flowchart-parser-ZNSKIBVR.js +21 -0
- package/dist/flowchart-renderer-ZAEOL4DX.js +35 -0
- package/dist/index.d.ts +3 -469
- package/dist/index.js +137 -69685
- package/dist/layout-46N4JJYK.js +17 -0
- package/dist/layout-5RA6QWR6.js +26 -0
- package/dist/layout-5T6RH5M6.js +14 -0
- package/dist/layout-7QXCNOVS.js +13 -0
- package/dist/layout-EE6GE46Y.js +17 -0
- package/dist/layout-G23F7LDH.js +9 -0
- package/dist/layout-HRBZPFCN.js +23 -0
- package/dist/layout-HWDSRX5E.js +20 -0
- package/dist/layout-HYZPWAK5.js +18 -0
- package/dist/layout-N76NR4ZO.js +19 -0
- package/dist/layout-OB3RI63S.js +14 -0
- package/dist/layout-S2QBYSK2.js +15 -0
- package/dist/layout-UM5F4DJB.js +15 -0
- package/dist/layout-search-OYV77HHS.js +1655 -0
- package/dist/load-data-EHDDZ5ZS.js +8 -0
- package/dist/palettes-PRJ2RI6D.js +56 -0
- package/dist/parser-2MFP2H2E.js +11 -0
- package/dist/parser-3STE62GB.js +21 -0
- package/dist/parser-4FL2CMDG.js +18 -0
- package/dist/parser-4OGOBJJ4.js +13 -0
- package/dist/parser-6BTRLKRZ.js +15 -0
- package/dist/parser-AQETEVRW.js +21 -0
- package/dist/parser-CFZD5IBL.js +12 -0
- package/dist/parser-FUSKFGZN.js +16 -0
- package/dist/parser-I6KXEYFK.js +13 -0
- package/dist/parser-IXSZZQHY.js +18 -0
- package/dist/parser-JAL2HD4I.js +13 -0
- package/dist/parser-KNSQSKQO.js +20 -0
- package/dist/parser-N5O4IHUN.js +13 -0
- package/dist/parser-NGU3ETO6.js +16 -0
- package/dist/parser-NRAI6O25.js +19 -0
- package/dist/parser-O5J4VBII.js +11 -0
- package/dist/parser-OV7NOBW6.js +21 -0
- package/dist/parser-PQ3POX45.js +23 -0
- package/dist/parser-TFZFBVWN.js +11 -0
- package/dist/parser-TRTHDFPP.js +18 -0
- package/dist/parser-VQP2UJ6W.js +11 -0
- package/dist/parser-XDVELHRK.js +13 -0
- package/dist/parser-XKRP7QKP.js +11 -0
- package/dist/parser-Y6WTBU76.js +14 -0
- package/dist/parser-ZSDPZYQP.js +12 -0
- package/dist/renderer-2XQQQ6QR.js +28 -0
- package/dist/renderer-5NDVNDAZ.js +26 -0
- package/dist/renderer-6EEZHMH4.js +26 -0
- package/dist/renderer-6W5462RB.js +43 -0
- package/dist/renderer-7MVLWWQD.js +22 -0
- package/dist/renderer-AWWAF6XD.js +33 -0
- package/dist/renderer-BYCNMBF7.js +34 -0
- package/dist/renderer-CLJHQPBJ.js +29 -0
- package/dist/renderer-GHPZMFU2.js +15 -0
- package/dist/renderer-JGBKUCZD.js +16 -0
- package/dist/renderer-KQB7UGDS.js +19 -0
- package/dist/renderer-PGX2TKVS.js +24 -0
- package/dist/renderer-QIWVHWZO.js +15 -0
- package/dist/renderer-RPOA5YPZ.js +35 -0
- package/dist/renderer-SMIBCRVB.js +24 -0
- package/dist/renderer-T7YKC5VP.js +28 -0
- package/dist/renderer-TOCOVLHS.js +37 -0
- package/dist/renderer-UWA5XS74.js +39 -0
- package/dist/renderer-VACVQYML.js +25 -0
- package/dist/renderer-W4M6QLIJ.js +37 -0
- package/dist/renderer-WZRREQ2P.js +23 -0
- package/dist/renderer-YY2OSF6E.js +27 -0
- package/dist/renderer-Z7I3W2PP.js +24 -0
- package/dist/renderer-ZGNTUWC4.js +22 -0
- package/dist/renderer-ZTR3DER3.js +19 -0
- package/dist/renderer-radial-C32GPJQ7.js +21 -0
- package/dist/resolver-NOAA7ID7.js +13 -0
- package/dist/state-collapse-HCGD6IVJ.js +8 -0
- package/dist/state-parser-Z2V23ATR.js +18 -0
- package/dist/state-renderer-RFYHSP4K.js +34 -0
- package/dist/themes-1-CuKpeH.d.ts +908 -0
- package/package.json +19 -70
- package/dist/advanced.cjs +0 -71648
- package/dist/advanced.d.cts +0 -6571
- package/dist/auto.cjs +0 -70648
- package/dist/auto.d.cts +0 -54
- package/dist/block.cjs +0 -70208
- package/dist/block.d.cts +0 -115
- package/dist/editor.d.cts +0 -26
- package/dist/element.cjs +0 -70530
- package/dist/element.d.cts +0 -62
- package/dist/highlight.cjs +0 -852
- package/dist/highlight.d.cts +0 -49
- package/dist/index.cjs +0 -70491
- package/dist/index.d.cts +0 -626
- package/dist/pert.cjs +0 -325
- package/dist/pert.d.cts +0 -559
- package/src/advanced.ts +0 -873
- package/src/arc/parser.ts +0 -15
- package/src/arc/renderer.ts +0 -633
- package/src/auto/index.ts +0 -621
- package/src/auto/shared.ts +0 -370
- package/src/auto/styles.ts +0 -73
- package/src/block/layout.ts +0 -198
- package/src/block/renderer.ts +0 -484
- package/src/block/types.ts +0 -67
- package/src/boxes-and-lines/collapse.ts +0 -96
- package/src/boxes-and-lines/focus.ts +0 -257
- package/src/boxes-and-lines/label-placement.ts +0 -339
- package/src/boxes-and-lines/layout-grouped.ts +0 -912
- package/src/boxes-and-lines/layout-layered.ts +0 -722
- package/src/boxes-and-lines/layout-search.ts +0 -1678
- package/src/boxes-and-lines/layout.ts +0 -398
- package/src/boxes-and-lines/parser.ts +0 -1181
- package/src/boxes-and-lines/renderer.ts +0 -1465
- package/src/boxes-and-lines/types.ts +0 -66
- package/src/c4/parser.ts +0 -1158
- package/src/c4/renderer.ts +0 -2008
- package/src/c4/types.ts +0 -89
- package/src/chart-type-registry.ts +0 -529
- package/src/chart-types.ts +0 -225
- package/src/chart.ts +0 -685
- package/src/charts-d3/bar.ts +0 -338
- package/src/charts-d3/chord.ts +0 -140
- package/src/charts-d3/function.ts +0 -147
- package/src/charts-d3/funnel.ts +0 -104
- package/src/charts-d3/heatmap.ts +0 -145
- package/src/charts-d3/index.ts +0 -337
- package/src/charts-d3/interactions.ts +0 -477
- package/src/charts-d3/line.ts +0 -344
- package/src/charts-d3/mount.ts +0 -151
- package/src/charts-d3/pie.ts +0 -117
- package/src/charts-d3/polar.ts +0 -89
- package/src/charts-d3/radar.ts +0 -0
- package/src/charts-d3/sankey.ts +0 -0
- package/src/charts-d3/scatter.ts +0 -185
- package/src/charts-d3/shared.ts +0 -208
- package/src/class/layout.ts +0 -326
- package/src/class/parser.ts +0 -635
- package/src/class/renderer.ts +0 -793
- package/src/class/types.ts +0 -58
- package/src/cli-banner.ts +0 -106
- package/src/cli.ts +0 -1080
- package/src/colors-diagnostics.ts +0 -35
- package/src/colors.ts +0 -379
- package/src/completion-types.ts +0 -27
- package/src/completion.ts +0 -2668
- package/src/cycle/layout.ts +0 -1026
- package/src/cycle/parser.ts +0 -387
- package/src/cycle/renderer.ts +0 -653
- package/src/cycle/types.ts +0 -101
- package/src/d3.ts +0 -1434
- package/src/data-chart-parser.ts +0 -1007
- package/src/dgmo-router.ts +0 -296
- package/src/diagnostics-registry.ts +0 -108
- package/src/diagnostics.ts +0 -0
- package/src/dimensions.ts +0 -20
- package/src/directives-registry.ts +0 -396
- package/src/editor/dgmo.grammar +0 -76
- package/src/editor/dgmo.grammar.d.ts +0 -2
- package/src/editor/dgmo.grammar.js +0 -18
- package/src/editor/dgmo.grammar.terms.d.ts +0 -5
- package/src/editor/dgmo.grammar.terms.js +0 -35
- package/src/editor/highlight-api.ts +0 -596
- package/src/editor/highlight.ts +0 -36
- package/src/editor/index.ts +0 -27
- package/src/editor/keywords.ts +0 -128
- package/src/editor/tokens.ts +0 -30
- package/src/element/index.ts +0 -432
- package/src/embed/css.ts +0 -312
- package/src/embed/escape.ts +0 -15
- package/src/embed/index.ts +0 -280
- package/src/er/classify.ts +0 -228
- package/src/er/layout.ts +0 -530
- package/src/er/parser.ts +0 -750
- package/src/er/renderer.ts +0 -725
- package/src/er/types.ts +0 -52
- package/src/error-card.ts +0 -269
- package/src/event-line/diagnostics.ts +0 -117
- package/src/event-line/parser.ts +0 -643
- package/src/event-line/renderer.ts +0 -1742
- package/src/event-line/types.ts +0 -74
- package/src/fonts.ts +0 -3
- package/src/gantt/parser.ts +0 -1641
- package/src/gantt/renderer.ts +0 -3787
- package/src/gantt/resolver.ts +0 -172
- package/src/gantt/types.ts +0 -206
- package/src/graph/diagnostics.ts +0 -64
- package/src/graph/edge-spline.ts +0 -29
- package/src/graph/flowchart-parser.ts +0 -759
- package/src/graph/flowchart-renderer.ts +0 -817
- package/src/graph/layout.ts +0 -472
- package/src/graph/notes.ts +0 -21
- package/src/graph/state-collapse.ts +0 -78
- package/src/graph/state-parser.ts +0 -574
- package/src/graph/state-renderer.ts +0 -653
- package/src/graph/types.ts +0 -65
- package/src/index.ts +0 -261
- package/src/infra/compute.ts +0 -1385
- package/src/infra/layout.ts +0 -888
- package/src/infra/parser.ts +0 -1234
- package/src/infra/renderer.ts +0 -2534
- package/src/infra/roles.ts +0 -75
- package/src/infra/types.ts +0 -216
- package/src/infra/validation.ts +0 -201
- package/src/journey-map/layout.ts +0 -490
- package/src/journey-map/parser.ts +0 -557
- package/src/journey-map/renderer.ts +0 -1580
- package/src/journey-map/types.ts +0 -47
- package/src/kanban/mutations.ts +0 -175
- package/src/kanban/parser.ts +0 -509
- package/src/kanban/renderer.ts +0 -1449
- package/src/kanban/types.ts +0 -39
- package/src/label-layout.ts +0 -368
- package/src/map/colorize.ts +0 -54
- package/src/map/completion.ts +0 -316
- package/src/map/context-labels.ts +0 -609
- package/src/map/data/PROVENANCE.json +0 -1
- package/src/map/data/README.md +0 -40
- package/src/map/data/airport-collisions.json +0 -1
- package/src/map/data/airports.json +0 -1
- package/src/map/data/gazetteer.json +0 -1
- package/src/map/data/lakes.json +0 -1
- package/src/map/data/mountain-ranges.json +0 -1
- package/src/map/data/na-lakes.json +0 -1
- package/src/map/data/na-land.json +0 -1
- package/src/map/data/region-names.json +0 -1
- package/src/map/data/rivers.json +0 -1
- package/src/map/data/types.ts +0 -121
- package/src/map/data/us-states.json +0 -1
- package/src/map/data/water-bodies.json +0 -1
- package/src/map/data/world-coarse.json +0 -1
- package/src/map/data/world-detail.json +0 -1
- package/src/map/diagnostics.ts +0 -134
- package/src/map/dimensions.ts +0 -150
- package/src/map/geo-query.ts +0 -295
- package/src/map/geo.ts +0 -438
- package/src/map/invert.ts +0 -111
- package/src/map/layout.ts +0 -5069
- package/src/map/legend-band.ts +0 -127
- package/src/map/load-data.ts +0 -186
- package/src/map/parser.ts +0 -783
- package/src/map/renderer.ts +0 -1445
- package/src/map/resolved-types.ts +0 -160
- package/src/map/resolver.ts +0 -1146
- package/src/map/types.ts +0 -194
- package/src/mindmap/collapse.ts +0 -67
- package/src/mindmap/layout.ts +0 -649
- package/src/mindmap/parser.ts +0 -430
- package/src/mindmap/renderer.ts +0 -637
- package/src/mindmap/text-wrap.ts +0 -218
- package/src/mindmap/types.ts +0 -56
- package/src/org/collapse.ts +0 -151
- package/src/org/layout.ts +0 -1262
- package/src/org/parser.ts +0 -519
- package/src/org/renderer.ts +0 -866
- package/src/org/resolver.ts +0 -636
- package/src/palettes/atlas.ts +0 -77
- package/src/palettes/blueprint.ts +0 -73
- package/src/palettes/catppuccin.ts +0 -84
- package/src/palettes/color-utils.ts +0 -443
- package/src/palettes/index.ts +0 -67
- package/src/palettes/nord.ts +0 -67
- package/src/palettes/registry.ts +0 -114
- package/src/palettes/slate.ts +0 -73
- package/src/palettes/tidewater.ts +0 -73
- package/src/palettes/tokyo-night.ts +0 -86
- package/src/palettes/types.ts +0 -88
- package/src/pert/analyzer.ts +0 -1183
- package/src/pert/diagnostics.ts +0 -110
- package/src/pert/index.ts +0 -43
- package/src/pert/internal.ts +0 -252
- package/src/pert/layout.ts +0 -971
- package/src/pert/monte-carlo.ts +0 -446
- package/src/pert/parser.ts +0 -1606
- package/src/pert/renderer.ts +0 -4009
- package/src/pert/share-normalize.ts +0 -62
- package/src/pert/types.ts +0 -604
- package/src/pyramid/parser.ts +0 -185
- package/src/pyramid/renderer.ts +0 -768
- package/src/pyramid/types.ts +0 -28
- package/src/quadrant/parser.ts +0 -18
- package/src/quadrant/renderer.ts +0 -638
- package/src/raci/diagnostics.ts +0 -213
- package/src/raci/index.ts +0 -23
- package/src/raci/mutations.ts +0 -321
- package/src/raci/parser.ts +0 -699
- package/src/raci/renderer.ts +0 -1627
- package/src/raci/types.ts +0 -82
- package/src/raci/variants.ts +0 -320
- package/src/render.ts +0 -268
- package/src/ring/parser.ts +0 -212
- package/src/ring/renderer.ts +0 -490
- package/src/ring/types.ts +0 -27
- package/src/sequence/collapse.ts +0 -174
- package/src/sequence/parser.ts +0 -1596
- package/src/sequence/participant-inference.ts +0 -152
- package/src/sequence/renderer.ts +0 -3168
- package/src/sequence/tag-resolution.ts +0 -164
- package/src/sharing.ts +0 -211
- package/src/sitemap/collapse.ts +0 -100
- package/src/sitemap/layout.ts +0 -925
- package/src/sitemap/parser.ts +0 -755
- package/src/sitemap/renderer.ts +0 -828
- package/src/sitemap/types.ts +0 -42
- package/src/slope/parser.ts +0 -18
- package/src/slope/renderer.ts +0 -378
- package/src/swimlane/diagnostics.ts +0 -94
- package/src/swimlane/layout.ts +0 -597
- package/src/swimlane/parser.ts +0 -613
- package/src/swimlane/renderer.ts +0 -681
- package/src/swimlane/types.ts +0 -151
- package/src/tech-radar/interactive.ts +0 -1129
- package/src/tech-radar/layout.ts +0 -197
- package/src/tech-radar/parser.ts +0 -436
- package/src/tech-radar/renderer.ts +0 -1239
- package/src/tech-radar/shared.ts +0 -151
- package/src/tech-radar/types.ts +0 -86
- package/src/themes.ts +0 -22
- package/src/timeline/parser.ts +0 -472
- package/src/timeline/renderer.ts +0 -3024
- package/src/timeline/types.ts +0 -33
- package/src/timeline/viz-parser.ts +0 -21
- package/src/treemap/diagnostics.ts +0 -62
- package/src/treemap/layout-radial.ts +0 -233
- package/src/treemap/layout.ts +0 -189
- package/src/treemap/parser.ts +0 -463
- package/src/treemap/renderer-radial.ts +0 -438
- package/src/treemap/renderer.ts +0 -572
- package/src/treemap/treemap-shared.ts +0 -309
- package/src/treemap/types.ts +0 -60
- package/src/utils/arrow-markers.ts +0 -51
- package/src/utils/arrows-diagnostics.ts +0 -62
- package/src/utils/arrows.ts +0 -227
- package/src/utils/brand.ts +0 -61
- package/src/utils/card.ts +0 -183
- package/src/utils/collapse-engine/tree-edges.ts +0 -115
- package/src/utils/collapse-engine/tree.ts +0 -117
- package/src/utils/d3-helpers.ts +0 -219
- package/src/utils/d3-types.ts +0 -9
- package/src/utils/description-helpers.ts +0 -38
- package/src/utils/duration.ts +0 -312
- package/src/utils/export-container.ts +0 -41
- package/src/utils/expr-eval.ts +0 -27
- package/src/utils/fit-canvas.ts +0 -64
- package/src/utils/hover-styles.ts +0 -673
- package/src/utils/inline-markdown.ts +0 -96
- package/src/utils/legend-constants.ts +0 -48
- package/src/utils/legend-d3.ts +0 -618
- package/src/utils/legend-integration.ts +0 -107
- package/src/utils/legend-layout.ts +0 -776
- package/src/utils/legend-svg.ts +0 -214
- package/src/utils/legend-types.ts +0 -272
- package/src/utils/name-normalize.ts +0 -163
- package/src/utils/note-box/constants.ts +0 -25
- package/src/utils/note-box/index.ts +0 -11
- package/src/utils/note-box/metrics.ts +0 -90
- package/src/utils/note-box/svg.ts +0 -331
- package/src/utils/notes/bounds.ts +0 -30
- package/src/utils/notes/build.ts +0 -131
- package/src/utils/notes/index.ts +0 -18
- package/src/utils/notes/model.ts +0 -19
- package/src/utils/notes/parse.ts +0 -131
- package/src/utils/notes/place.ts +0 -177
- package/src/utils/notes/resolve.ts +0 -88
- package/src/utils/number-format.ts +0 -36
- package/src/utils/parsing.ts +0 -953
- package/src/utils/reserved-key-registry.ts +0 -239
- package/src/utils/safe-href.ts +0 -38
- package/src/utils/scaling.ts +0 -97
- package/src/utils/svg-embed.ts +0 -251
- package/src/utils/tag-groups.ts +0 -607
- package/src/utils/text-measure.ts +0 -122
- package/src/utils/title-constants.ts +0 -24
- package/src/utils/visual-conventions.ts +0 -61
- package/src/utils/wrapped-desc.ts +0 -87
- package/src/venn/parser.ts +0 -18
- package/src/venn/renderer.ts +0 -1047
- package/src/version-control/diagnostics.ts +0 -67
- package/src/version-control/parser.ts +0 -544
- package/src/version-control/renderer.ts +0 -724
- package/src/version-control/types.ts +0 -100
- package/src/visualizations/parse.ts +0 -1560
- package/src/visualizations/types.ts +0 -262
- package/src/wireframe/layout.ts +0 -479
- package/src/wireframe/parser.ts +0 -1114
- package/src/wireframe/renderer.ts +0 -1318
- package/src/wireframe/types.ts +0 -110
- package/src/wordcloud/parser.ts +0 -18
- package/src/wordcloud/renderer.ts +0 -357
package/src/infra/parser.ts
DELETED
|
@@ -1,1234 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Infra Chart Parser
|
|
3
|
-
// ============================================================
|
|
4
|
-
//
|
|
5
|
-
// Parses `infra [Title]` syntax into a structured InfraModel.
|
|
6
|
-
// Handles: chart metadata, component blocks with indented properties
|
|
7
|
-
// and connections, [Group] containers, tag groups, pipe metadata.
|
|
8
|
-
|
|
9
|
-
import { tagAttrKey } from '../utils/tag-groups';
|
|
10
|
-
import {
|
|
11
|
-
emit,
|
|
12
|
-
formatDgmoError,
|
|
13
|
-
makeDgmoError,
|
|
14
|
-
NAME_DIAGNOSTIC_CODES,
|
|
15
|
-
nameMergedMessage,
|
|
16
|
-
suggest,
|
|
17
|
-
} from '../diagnostics';
|
|
18
|
-
import { GRAPH_DX } from '../graph/diagnostics';
|
|
19
|
-
import { tryStripDescriptionKeyword } from '../utils/description-helpers';
|
|
20
|
-
import { parseInArrowLabel } from '../utils/arrows';
|
|
21
|
-
import {
|
|
22
|
-
measureIndent,
|
|
23
|
-
parseFirstLine,
|
|
24
|
-
OPTION_NOCOLON_RE,
|
|
25
|
-
tryParseSharedOption,
|
|
26
|
-
warnUnknownMetaKeys,
|
|
27
|
-
} from '../utils/parsing';
|
|
28
|
-
import { isRecognizedColorName } from '../colors';
|
|
29
|
-
import { normalizeName, displayName } from '../utils/name-normalize';
|
|
30
|
-
import {
|
|
31
|
-
matchTagBlockHeading,
|
|
32
|
-
stripDefaultModifier,
|
|
33
|
-
validateTagGroupNames,
|
|
34
|
-
} from '../utils/tag-groups';
|
|
35
|
-
import { INFRA_REGISTRY, withTagAliases } from '../utils/reserved-key-registry';
|
|
36
|
-
import type {
|
|
37
|
-
ParsedInfra,
|
|
38
|
-
InfraNode,
|
|
39
|
-
InfraGroup,
|
|
40
|
-
InfraTagGroup,
|
|
41
|
-
} from './types';
|
|
42
|
-
import { INFRA_BEHAVIOR_KEYS, EDGE_ONLY_KEYS } from './types';
|
|
43
|
-
import { INFRA_TOP_LEVEL_OPTION_SET } from '../directives-registry';
|
|
44
|
-
import type { Writable } from '../utils/brand';
|
|
45
|
-
|
|
46
|
-
// ============================================================
|
|
47
|
-
// Regex patterns
|
|
48
|
-
// ============================================================
|
|
49
|
-
|
|
50
|
-
// Connection: -label-> Target or -> Target (pipe metadata handled by extractPipeMetadata)
|
|
51
|
-
const CONNECTION_RE = /^-(?:([^-].*?))?->\s*(.+?)\s*$/;
|
|
52
|
-
|
|
53
|
-
// Simple connection shorthand: -> Target (no label, no dash prefix needed for edge)
|
|
54
|
-
const SIMPLE_CONNECTION_RE = /^->\s*(.+?)\s*$/;
|
|
55
|
-
|
|
56
|
-
// Async connection: ~label~> Target or ~> Target
|
|
57
|
-
const ASYNC_CONNECTION_RE = /^~(?:([^~].*?))?~>\s*(.+?)\s*$/;
|
|
58
|
-
|
|
59
|
-
// Async simple connection shorthand: ~> Target
|
|
60
|
-
const ASYNC_SIMPLE_CONNECTION_RE = /^~>\s*(.+?)\s*$/;
|
|
61
|
-
|
|
62
|
-
// Deprecated xN fanout suffix (e.g. "x5" at end of line)
|
|
63
|
-
const DEPRECATED_FANOUT_RE = /\bx(\d+)\s*$/;
|
|
64
|
-
|
|
65
|
-
// Group declaration: [Group Name] with optional `as <alias>` and optional
|
|
66
|
-
// metadata (legacy `| ...` or new same-line `key: value` per §1.4).
|
|
67
|
-
// Capture order: 1=label, 2=alias, 3=metadata tail.
|
|
68
|
-
const GROUP_RE =
|
|
69
|
-
/^\[([^\]]+)\]\s*(?:as\s+([A-Za-z][A-Za-z0-9_]{0,11})\s*)?((?:\|.*)|(?:[a-zA-Z_]\w*\s*:.*))?$/;
|
|
70
|
-
|
|
71
|
-
// Tag value: `Name` or `Name color` (trailing-token color form). Color is
|
|
72
|
-
// extracted via the shared `extractColor` helper at use-site (see
|
|
73
|
-
// `dgmo/src/utils/parsing.ts:extractColor`), not via this regex. This regex
|
|
74
|
-
// just confirms the line shape is a valid tag value (no reserved sigils).
|
|
75
|
-
const TAG_VALUE_RE = /^(\w[\w\s]+?)\s*$/;
|
|
76
|
-
|
|
77
|
-
// Component line. Accepts either a quoted name ("name with | : reserved chars")
|
|
78
|
-
// or a bare name (multi-word allowed; must start with letter/underscore so digit-
|
|
79
|
-
// only or sigil-led lines fall through to other branches). Metadata follows
|
|
80
|
-
// after a `|` separator (legacy) or as same-line `key: value` (§1.4).
|
|
81
|
-
//
|
|
82
|
-
// Named captures: qname (quoted content), uname (bare name), alias (`as <x>`
|
|
83
|
-
// postfix), meta (the metadata tail). qname/uname are mutually exclusive.
|
|
84
|
-
//
|
|
85
|
-
// A component declaration: a quoted display name OR a bare name, an OPTIONAL
|
|
86
|
-
// `as <alias>` postfix (alias mirrors peelAlias: letter-led, ≤12 chars), then
|
|
87
|
-
// optional same-line metadata. The `as` capture is what makes a quoted name +
|
|
88
|
-
// alias parse — `"Web App" as web` — which the bare-name path got for free by
|
|
89
|
-
// sweeping the alias into the lazy name group and peeling it later, but the
|
|
90
|
-
// quoted path could not reach past the closing quote.
|
|
91
|
-
const COMPONENT_RE =
|
|
92
|
-
/^(?:"(?<qname>[^"]+)"|(?<uname>[a-zA-Z_][\w -]*?))(?:\s+as\s+(?<alias>[A-Za-z][A-Za-z0-9_]{0,11}))?\s*(?<meta>(?:\|.*)|(?:[a-zA-Z_]\w*\s*:.*))?$/;
|
|
93
|
-
|
|
94
|
-
// Legacy pipe metadata: | key: value or | k1: v1, k2: v2 (comma-separated).
|
|
95
|
-
// Kept for transitional back-compat extraction; new same-line metadata
|
|
96
|
-
// (§1.4) is preferred and extracted via SAMELINE_META_RE below.
|
|
97
|
-
const PIPE_META_RE = /[|,]\s*(\w+)\s*:\s*([^|,]+)/g;
|
|
98
|
-
// Same-line metadata (post-0.18.0 §1.4): `k: v, k2: v2` after any name
|
|
99
|
-
// region — matches a colon-bearing key-value, comma-separated.
|
|
100
|
-
const SAMELINE_META_RE = /(?:^|[\s,])(\w+)\s*:\s*([^,]+?)(?=\s*,|\s*$)/g;
|
|
101
|
-
|
|
102
|
-
// Property: key: value (colon-separated)
|
|
103
|
-
const PROPERTY_RE = /^([\w-]+):\s*(.+)$/;
|
|
104
|
-
|
|
105
|
-
// Percentage value: 80% or 99.99%
|
|
106
|
-
const PERCENT_RE = /^([\d.]+)%$/;
|
|
107
|
-
|
|
108
|
-
// Range value: N-M (for instances)
|
|
109
|
-
const RANGE_RE = /^(\d+)-(\d+)$/;
|
|
110
|
-
|
|
111
|
-
// Node names that act as the traffic entry point (edge node)
|
|
112
|
-
const EDGE_NODE_NAMES = new Set(['edge', 'internet']);
|
|
113
|
-
|
|
114
|
-
// Known top-level option keys (space-separated, no colon). Derived from the
|
|
115
|
-
// single-source directives registry; the anti-drift guard
|
|
116
|
-
// (tests/highlight-coverage.test.ts) cross-checks it against the editor sets.
|
|
117
|
-
export const INFRA_TOP_LEVEL_OPTIONS: ReadonlySet<string> =
|
|
118
|
-
INFRA_TOP_LEVEL_OPTION_SET;
|
|
119
|
-
const TOP_LEVEL_OPTIONS = INFRA_TOP_LEVEL_OPTIONS;
|
|
120
|
-
|
|
121
|
-
// ============================================================
|
|
122
|
-
// Helpers
|
|
123
|
-
// ============================================================
|
|
124
|
-
|
|
125
|
-
function nodeId(name: string): string {
|
|
126
|
-
return normalizeName(name);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function groupId(name: string): string {
|
|
130
|
-
return `[${normalizeName(name)}]`;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function parsePropertyValue(raw: string): string | number {
|
|
134
|
-
const pct = raw.match(PERCENT_RE);
|
|
135
|
-
// Capture group 1 in-bounds after successful match.
|
|
136
|
-
if (pct) return parseFloat(pct[1]!);
|
|
137
|
-
|
|
138
|
-
const num = parseFloat(raw);
|
|
139
|
-
if (!isNaN(num) && String(num) === raw.trim()) return num;
|
|
140
|
-
|
|
141
|
-
return raw.trim();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function extractPipeMetadata(rest: string): {
|
|
145
|
-
tags: Record<string, string>;
|
|
146
|
-
clean: string;
|
|
147
|
-
hadPipe: boolean;
|
|
148
|
-
} {
|
|
149
|
-
const tags: Record<string, string> = {};
|
|
150
|
-
let clean = rest;
|
|
151
|
-
let match: RegExpExecArray | null;
|
|
152
|
-
const hadPipe = rest.includes('|');
|
|
153
|
-
if (hadPipe) {
|
|
154
|
-
PIPE_META_RE.lastIndex = 0;
|
|
155
|
-
while ((match = PIPE_META_RE.exec(rest)) !== null) {
|
|
156
|
-
tags[match[1]!.trim()] = match[2]!.trim();
|
|
157
|
-
clean = clean.replace(match[0], '');
|
|
158
|
-
}
|
|
159
|
-
return { tags, clean: clean.trim(), hadPipe: true };
|
|
160
|
-
}
|
|
161
|
-
// §1.4 same-line metadata: no pipe, but `key: value, key2: value2`
|
|
162
|
-
// may follow the name. Extract from right-to-left so the leftmost
|
|
163
|
-
// name region is preserved as `clean`.
|
|
164
|
-
SAMELINE_META_RE.lastIndex = 0;
|
|
165
|
-
let firstMatchIdx = -1;
|
|
166
|
-
while ((match = SAMELINE_META_RE.exec(rest)) !== null) {
|
|
167
|
-
tags[match[1]!.trim()] = match[2]!.trim();
|
|
168
|
-
if (firstMatchIdx === -1) firstMatchIdx = match.index;
|
|
169
|
-
}
|
|
170
|
-
if (firstMatchIdx >= 0) {
|
|
171
|
-
clean = rest.substring(0, firstMatchIdx).trim();
|
|
172
|
-
}
|
|
173
|
-
return { tags, clean: clean.trim(), hadPipe: false };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Detect unparsed pipe metadata left in a target name after extractPipeMetadata.
|
|
177
|
-
// Common case: `split 100%` without a colon isn't picked up by PIPE_META_RE.
|
|
178
|
-
const UNPARSED_SPLIT_RE = /\bsplit\s+(\d+)%/;
|
|
179
|
-
|
|
180
|
-
function warnUnparsedPipeMeta(
|
|
181
|
-
targetName: string,
|
|
182
|
-
lineNumber: number,
|
|
183
|
-
warnFn: (line: number, message: string) => void
|
|
184
|
-
): void {
|
|
185
|
-
if (!targetName.includes('|')) return;
|
|
186
|
-
const splitMatch = targetName.match(UNPARSED_SPLIT_RE);
|
|
187
|
-
if (splitMatch) {
|
|
188
|
-
warnFn(
|
|
189
|
-
lineNumber,
|
|
190
|
-
`'split ${splitMatch[1]}%' needs a colon — use 'split: ${splitMatch[1]}%'`
|
|
191
|
-
);
|
|
192
|
-
} else {
|
|
193
|
-
warnFn(
|
|
194
|
-
lineNumber,
|
|
195
|
-
`Unparsed pipe metadata in target — pipe values use 'key: value' syntax`
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// ============================================================
|
|
201
|
-
// Parser
|
|
202
|
-
// ============================================================
|
|
203
|
-
|
|
204
|
-
export function parseInfra(content: string): ParsedInfra {
|
|
205
|
-
const lines = content.split('\n');
|
|
206
|
-
const options: Record<string, string> = {};
|
|
207
|
-
const result: Writable<ParsedInfra> = {
|
|
208
|
-
type: 'infra',
|
|
209
|
-
title: null,
|
|
210
|
-
titleLineNumber: null,
|
|
211
|
-
direction: 'LR',
|
|
212
|
-
nodes: [],
|
|
213
|
-
edges: [],
|
|
214
|
-
groups: [],
|
|
215
|
-
tagGroups: [],
|
|
216
|
-
options,
|
|
217
|
-
diagnostics: [],
|
|
218
|
-
error: null,
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
const nodeMap = new Map<string, Writable<InfraNode>>();
|
|
222
|
-
// Sidecar mutable tag-records, keyed by node id. The same Record object is
|
|
223
|
-
// also referenced from `node.tags`; mutating it here updates both views.
|
|
224
|
-
const nodeMutableTags = new Map<string, Record<string, string>>();
|
|
225
|
-
|
|
226
|
-
// Push a description line onto a node. Manages the underlying mutable
|
|
227
|
-
// array and assigns it back so `node.description` stays in sync.
|
|
228
|
-
function pushDescription(node: Writable<InfraNode>, text: string): void {
|
|
229
|
-
const existing = (node.description as string[] | undefined) ?? [];
|
|
230
|
-
existing.push(text);
|
|
231
|
-
node.description = existing;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Tag-alias set grows as `tag Name as <x>` groups are parsed.
|
|
235
|
-
const tagAliasSet = new Set<string>();
|
|
236
|
-
const EDGE_PIPE_REGISTRY = {
|
|
237
|
-
keys: new Set(['split', 'fanout']),
|
|
238
|
-
tagAliases: new Set<string>(),
|
|
239
|
-
} as const;
|
|
240
|
-
|
|
241
|
-
// Per-parse alias literal → canonical node id (TD-18). Per C8.
|
|
242
|
-
const nameAliasMap = new Map<string, string>();
|
|
243
|
-
function peelAlias(label: string): { label: string; alias?: string } {
|
|
244
|
-
const trimmed = label.trim();
|
|
245
|
-
const m = trimmed.match(/^(.*?)\s+as\s+([A-Za-z][A-Za-z0-9_]{0,11})\s*$/);
|
|
246
|
-
if (!m) return { label: trimmed };
|
|
247
|
-
// Capture groups 1 & 2 in-bounds after successful match.
|
|
248
|
-
return { label: m[1]!.trim(), ...(m[2] !== undefined && { alias: m[2] }) };
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Infra nodes have no "is a <type>" declaration — capability comes from
|
|
252
|
-
// properties (cache-hit, buffer, drain-rate, …). Strip the suffix if a
|
|
253
|
-
// user wrote it (likely copying from sequence/c4 syntax) and warn.
|
|
254
|
-
const IS_A_SUFFIX = /^(.*?)\s+is\s+an?\s+[A-Za-z][\w-]*\s*$/i;
|
|
255
|
-
function peelInfraDecorations(
|
|
256
|
-
rawName: string,
|
|
257
|
-
lineNumber: number
|
|
258
|
-
): { label: string; alias?: string } {
|
|
259
|
-
const peeled = peelAlias(rawName);
|
|
260
|
-
const m = peeled.label.match(IS_A_SUFFIX);
|
|
261
|
-
if (!m) return peeled;
|
|
262
|
-
warn(
|
|
263
|
-
lineNumber,
|
|
264
|
-
`Infra nodes don't use 'is a <type>' — types are inferred from properties (cache-hit, buffer, drain-rate, …). Drop the 'is a' suffix.`
|
|
265
|
-
);
|
|
266
|
-
// Capture group 1 in-bounds after successful match.
|
|
267
|
-
return {
|
|
268
|
-
label: m[1]!.trim(),
|
|
269
|
-
...(peeled.alias !== undefined && { alias: peeled.alias }),
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Resolve a connection-target token. If the token exactly matches a
|
|
274
|
-
* declared alias, return the bound canonical id. Otherwise fall
|
|
275
|
-
* through to the normal `nodeId()` hash.
|
|
276
|
-
*/
|
|
277
|
-
function resolveTargetId(rawName: string): string {
|
|
278
|
-
const aliasResolved = nameAliasMap.get(rawName.trim());
|
|
279
|
-
if (aliasResolved !== undefined) return aliasResolved;
|
|
280
|
-
return nodeId(rawName);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Tracks first-seen display form for each normalized edge-target id.
|
|
284
|
-
// Used to give forward-reference stubs a readable label (e.g. 'CDN'
|
|
285
|
-
// instead of the lowercased internal 'cdn').
|
|
286
|
-
const targetDisplays = new Map<string, string>();
|
|
287
|
-
const rememberTargetDisplay = (key: string, display: string): void => {
|
|
288
|
-
if (!targetDisplays.has(key)) targetDisplays.set(key, display.trim());
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
const setError = (line: number, message: string) => {
|
|
292
|
-
const diag = makeDgmoError(line, message);
|
|
293
|
-
result.diagnostics.push(diag);
|
|
294
|
-
if (!result.error) result.error = formatDgmoError(diag);
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
const warn = (line: number, message: string) => {
|
|
298
|
-
result.diagnostics.push(makeDgmoError(line, message, 'warning'));
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
// Track parser state
|
|
302
|
-
let currentNode: Writable<InfraNode> | null = null;
|
|
303
|
-
let currentGroup: Writable<InfraGroup> | null = null;
|
|
304
|
-
let currentTagGroup: Writable<InfraTagGroup> | null = null;
|
|
305
|
-
let baseIndent = 0; // indent of the current component line
|
|
306
|
-
// The `infra [Title]` declaration is only legal on the first content line.
|
|
307
|
-
// parseFirstLine() must run ONCE, there — otherwise a later bare node line
|
|
308
|
-
// whose name is a chart-type keyword (e.g. an edge-block header `timeline`)
|
|
309
|
-
// is mis-read as a nested chart declaration and the parse aborts.
|
|
310
|
-
let firstLineConsumed = false;
|
|
311
|
-
|
|
312
|
-
function finishCurrentNode() {
|
|
313
|
-
if (!currentNode) return;
|
|
314
|
-
const key = currentNode.id;
|
|
315
|
-
const existing = nodeMap.get(key);
|
|
316
|
-
if (existing) {
|
|
317
|
-
const incomingDisplay = displayName(currentNode.label);
|
|
318
|
-
const existingDisplay = displayName(existing.label);
|
|
319
|
-
// A bare reference line that is a declared alias of `existing` (e.g. an
|
|
320
|
-
// edge-block header `web` for a node `"Web App" as web`) is an intentional
|
|
321
|
-
// pointer, not a near-duplicate name — don't flag it as a case/whitespace
|
|
322
|
-
// typo. Genuine collisions (no matching alias) still warn.
|
|
323
|
-
const isAliasRef = nameAliasMap.get(currentNode.label) === key;
|
|
324
|
-
if (incomingDisplay !== existingDisplay && !isAliasRef) {
|
|
325
|
-
result.diagnostics.push(
|
|
326
|
-
makeDgmoError(
|
|
327
|
-
currentNode.lineNumber,
|
|
328
|
-
nameMergedMessage({
|
|
329
|
-
incomingDisplay,
|
|
330
|
-
incomingLine: currentNode.lineNumber,
|
|
331
|
-
existingDisplay,
|
|
332
|
-
existingLine: existing.lineNumber,
|
|
333
|
-
}),
|
|
334
|
-
'warning',
|
|
335
|
-
NAME_DIAGNOSTIC_CODES.NAME_MERGED
|
|
336
|
-
)
|
|
337
|
-
);
|
|
338
|
-
}
|
|
339
|
-
currentNode = null;
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
// Validate mutual exclusion: concurrency vs instances/max-rps
|
|
343
|
-
const keys = new Set(currentNode.properties.map((p) => p.key));
|
|
344
|
-
if (
|
|
345
|
-
keys.has('concurrency') &&
|
|
346
|
-
(keys.has('instances') || keys.has('max-rps'))
|
|
347
|
-
) {
|
|
348
|
-
const conflicting = [
|
|
349
|
-
keys.has('instances') ? 'instances' : '',
|
|
350
|
-
keys.has('max-rps') ? 'max-rps' : '',
|
|
351
|
-
]
|
|
352
|
-
.filter(Boolean)
|
|
353
|
-
.join(', ');
|
|
354
|
-
warn(
|
|
355
|
-
currentNode.lineNumber,
|
|
356
|
-
`'concurrency' (serverless) is mutually exclusive with ${conflicting}. Serverless nodes scale via concurrency, not instances.`
|
|
357
|
-
);
|
|
358
|
-
}
|
|
359
|
-
// Validate mutual exclusion: buffer (queue) vs max-rps (service)
|
|
360
|
-
if (keys.has('buffer') && keys.has('max-rps')) {
|
|
361
|
-
warn(
|
|
362
|
-
currentNode.lineNumber,
|
|
363
|
-
`'buffer' (queue) and 'max-rps' (service) represent different capacity models. A queue buffers messages; a service processes them.`
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
nodeMap.set(key, currentNode);
|
|
367
|
-
result.nodes.push(currentNode);
|
|
368
|
-
currentNode = null;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function finishCurrentTagGroup() {
|
|
372
|
-
if (currentTagGroup) {
|
|
373
|
-
result.tagGroups.push(currentTagGroup);
|
|
374
|
-
}
|
|
375
|
-
currentTagGroup = null;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
for (let i = 0; i < lines.length; i++) {
|
|
379
|
-
// In-bounds by loop guard.
|
|
380
|
-
const raw = lines[i]!;
|
|
381
|
-
const lineNumber = i + 1;
|
|
382
|
-
const trimmed = raw.trim();
|
|
383
|
-
const indent = measureIndent(raw);
|
|
384
|
-
|
|
385
|
-
// Skip empty lines and comments
|
|
386
|
-
if (!trimmed || trimmed.startsWith('//')) continue;
|
|
387
|
-
|
|
388
|
-
// Skip markdown section headers
|
|
389
|
-
if (/^#{2,}\s+/.test(trimmed)) continue;
|
|
390
|
-
|
|
391
|
-
// ---- Top-level metadata (no indent) ----
|
|
392
|
-
if (indent === 0) {
|
|
393
|
-
// Close any open blocks
|
|
394
|
-
if (indent === 0 && currentNode && !trimmed.startsWith('-')) {
|
|
395
|
-
finishCurrentNode();
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// First line: `infra [Title]` or legacy `chart: infra` — only ever
|
|
399
|
-
// attempted on the first content line (see firstLineConsumed). A node or
|
|
400
|
-
// alias named like a chart type elsewhere is a normal node, not a redecl.
|
|
401
|
-
if (!firstLineConsumed) {
|
|
402
|
-
firstLineConsumed = true;
|
|
403
|
-
const firstLineResult = parseFirstLine(trimmed);
|
|
404
|
-
if (firstLineResult) {
|
|
405
|
-
if (firstLineResult.chartType !== 'infra') {
|
|
406
|
-
setError(
|
|
407
|
-
lineNumber,
|
|
408
|
-
`Expected chart type 'infra', got '${firstLineResult.chartType}'`
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
if (firstLineResult.title) {
|
|
412
|
-
result.title = firstLineResult.title;
|
|
413
|
-
result.titleLineNumber = lineNumber;
|
|
414
|
-
}
|
|
415
|
-
continue;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// direction-tb — bare boolean to switch to top-to-bottom (default is LR)
|
|
420
|
-
if (/^direction-tb$/i.test(trimmed)) {
|
|
421
|
-
result.direction = 'TB';
|
|
422
|
-
continue;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
if (trimmed === 'animate' || trimmed === 'no-animate') {
|
|
426
|
-
warn(
|
|
427
|
-
lineNumber,
|
|
428
|
-
'"animate" / "no-animate" have been removed — animation is controlled by the rendering context.'
|
|
429
|
-
);
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
if (tryParseSharedOption(trimmed, options)) {
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// Top-level options: `key value` (space-separated, no colon)
|
|
438
|
-
const optMatch = trimmed.match(OPTION_NOCOLON_RE);
|
|
439
|
-
// Capture groups 1 & 2 in-bounds after successful match.
|
|
440
|
-
if (optMatch && TOP_LEVEL_OPTIONS.has(optMatch[1]!.toLowerCase())) {
|
|
441
|
-
options[optMatch[1]!.toLowerCase()] = optMatch[2]!.trim();
|
|
442
|
-
continue;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// Tag group: `tag Name as <alias>` (via shared matchTagBlockHeading)
|
|
446
|
-
const tagMatch = matchTagBlockHeading(trimmed);
|
|
447
|
-
if (tagMatch) {
|
|
448
|
-
finishCurrentNode();
|
|
449
|
-
finishCurrentTagGroup();
|
|
450
|
-
if (tagMatch.alias) tagAliasSet.add(normalizeName(tagMatch.alias));
|
|
451
|
-
currentTagGroup = {
|
|
452
|
-
name: tagMatch.name,
|
|
453
|
-
alias: tagMatch.alias ?? null,
|
|
454
|
-
values: [],
|
|
455
|
-
lineNumber,
|
|
456
|
-
};
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// [Group Name] [as <alias>] [| t: Engineering] — TD-18 native `as` postfix
|
|
461
|
-
const groupMatch = trimmed.match(GROUP_RE);
|
|
462
|
-
if (groupMatch) {
|
|
463
|
-
finishCurrentNode();
|
|
464
|
-
finishCurrentTagGroup();
|
|
465
|
-
// Capture group 1 in-bounds after successful match.
|
|
466
|
-
const gLabel = groupMatch[1]!.trim();
|
|
467
|
-
const groupAlias = groupMatch[2];
|
|
468
|
-
const gId = groupId(gLabel);
|
|
469
|
-
if (groupAlias) nameAliasMap.set(groupAlias, gId);
|
|
470
|
-
const groupMeta = groupMatch[3]
|
|
471
|
-
? extractPipeMetadata(groupMatch[3]).tags
|
|
472
|
-
: undefined;
|
|
473
|
-
if (groupMeta) {
|
|
474
|
-
warnUnknownMetaKeys(
|
|
475
|
-
groupMeta,
|
|
476
|
-
withTagAliases(INFRA_REGISTRY, tagAliasSet),
|
|
477
|
-
(msg) => warn(lineNumber, msg)
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
const hasMeta = groupMeta && Object.keys(groupMeta).length > 0;
|
|
481
|
-
const newGroup: Writable<InfraGroup> = {
|
|
482
|
-
id: gId,
|
|
483
|
-
label: gLabel,
|
|
484
|
-
...(hasMeta && { metadata: groupMeta }),
|
|
485
|
-
lineNumber,
|
|
486
|
-
};
|
|
487
|
-
currentGroup = newGroup;
|
|
488
|
-
result.groups.push(newGroup);
|
|
489
|
-
continue;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Component at top level (no indent) — supports `as` postfix
|
|
493
|
-
const compMatch = trimmed.match(COMPONENT_RE);
|
|
494
|
-
if (compMatch) {
|
|
495
|
-
finishCurrentNode();
|
|
496
|
-
finishCurrentTagGroup();
|
|
497
|
-
|
|
498
|
-
const { qname, uname, alias: asAlias, meta } = compMatch.groups!;
|
|
499
|
-
const rawName = (qname ?? uname ?? '').trim();
|
|
500
|
-
const peeled = peelInfraDecorations(rawName, lineNumber);
|
|
501
|
-
const name = peeled.label;
|
|
502
|
-
// Explicit `as` capture wins; fall back to an alias the peeler split out
|
|
503
|
-
// of a bare name (and the quoted-literal `"Foo as bar"` legacy case).
|
|
504
|
-
const alias = asAlias ?? peeled.alias;
|
|
505
|
-
const rest = meta ?? '';
|
|
506
|
-
const { tags } = extractPipeMetadata(rest);
|
|
507
|
-
warnUnknownMetaKeys(
|
|
508
|
-
tags,
|
|
509
|
-
withTagAliases(INFRA_REGISTRY, tagAliasSet),
|
|
510
|
-
(msg) => warn(lineNumber, msg)
|
|
511
|
-
);
|
|
512
|
-
// A bare reference line may be an alias for an already-declared node
|
|
513
|
-
// (its label-slug differs from the alias, e.g. "Web App" as web). Resolve
|
|
514
|
-
// through the alias map first so an edge-source/reference merges into
|
|
515
|
-
// that node instead of spawning a duplicate stub.
|
|
516
|
-
const id = nameAliasMap.get(name) ?? nodeId(name);
|
|
517
|
-
if (alias) nameAliasMap.set(alias, id);
|
|
518
|
-
const isEdge = EDGE_NODE_NAMES.has(id.toLowerCase());
|
|
519
|
-
|
|
520
|
-
currentNode = {
|
|
521
|
-
id,
|
|
522
|
-
label: name,
|
|
523
|
-
properties: [],
|
|
524
|
-
groupId: null,
|
|
525
|
-
tags,
|
|
526
|
-
isEdge,
|
|
527
|
-
lineNumber,
|
|
528
|
-
};
|
|
529
|
-
nodeMutableTags.set(id, tags);
|
|
530
|
-
currentGroup = null;
|
|
531
|
-
baseIndent = 0;
|
|
532
|
-
continue;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// ---- Indented lines ----
|
|
537
|
-
|
|
538
|
-
// Tag value inside tag group — first value is the default unless another is marked `default`
|
|
539
|
-
if (currentTagGroup && indent > 0) {
|
|
540
|
-
const { text: cleanEntry, isDefault } = stripDefaultModifier(trimmed);
|
|
541
|
-
// Trailing-token color (universal rule, §1.5): peel off a lowercase
|
|
542
|
-
// recognized color word from the end of the line. Downstream stores
|
|
543
|
-
// the raw color NAME (not the palette hex) so the renderer can resolve
|
|
544
|
-
// against whichever theme/palette is active at render time.
|
|
545
|
-
const lastSpaceIdx = cleanEntry.lastIndexOf(' ');
|
|
546
|
-
let valueName = cleanEntry;
|
|
547
|
-
let rawColor: string | undefined;
|
|
548
|
-
if (lastSpaceIdx > 0) {
|
|
549
|
-
const trailing = cleanEntry.substring(lastSpaceIdx + 1);
|
|
550
|
-
if (isRecognizedColorName(trailing)) {
|
|
551
|
-
rawColor = trailing;
|
|
552
|
-
valueName = cleanEntry.substring(0, lastSpaceIdx).trimEnd();
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
const tvMatch = valueName.match(TAG_VALUE_RE);
|
|
556
|
-
if (tvMatch || /^\w+$/.test(valueName)) {
|
|
557
|
-
currentTagGroup.values.push({
|
|
558
|
-
name: valueName,
|
|
559
|
-
...(rawColor !== undefined && { color: rawColor }),
|
|
560
|
-
});
|
|
561
|
-
if (isDefault) {
|
|
562
|
-
currentTagGroup.defaultValue = valueName;
|
|
563
|
-
} else if (currentTagGroup.values.length === 1) {
|
|
564
|
-
currentTagGroup.defaultValue = valueName;
|
|
565
|
-
}
|
|
566
|
-
continue;
|
|
567
|
-
}
|
|
568
|
-
warn(
|
|
569
|
-
lineNumber,
|
|
570
|
-
`Invalid tag value '${trimmed}' in tag group '${currentTagGroup.name}' — a value is a bare word with an optional trailing color (e.g. 'Production red'); no ':' or '|'.`
|
|
571
|
-
);
|
|
572
|
-
continue;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// Inside a [Group] but no current node — group properties or component declaration
|
|
576
|
-
if (currentGroup && !currentNode && indent > 0) {
|
|
577
|
-
// Group-level properties (instances, collapsed)
|
|
578
|
-
const propMatch = trimmed.match(PROPERTY_RE);
|
|
579
|
-
if (propMatch) {
|
|
580
|
-
// Capture groups 1 & 2 in-bounds after successful match.
|
|
581
|
-
const key = propMatch[1]!.toLowerCase();
|
|
582
|
-
const val = propMatch[2]!.trim();
|
|
583
|
-
if (key === 'instances') {
|
|
584
|
-
const rangeM = val.match(RANGE_RE);
|
|
585
|
-
if (rangeM) {
|
|
586
|
-
currentGroup.instances = val;
|
|
587
|
-
} else {
|
|
588
|
-
const num = parseInt(val, 10);
|
|
589
|
-
if (!isNaN(num)) currentGroup.instances = num;
|
|
590
|
-
}
|
|
591
|
-
continue;
|
|
592
|
-
}
|
|
593
|
-
if (key === 'collapsed') {
|
|
594
|
-
currentGroup.collapsed = val.toLowerCase() === 'true';
|
|
595
|
-
continue;
|
|
596
|
-
}
|
|
597
|
-
// Fall through to component matching — could be a component name
|
|
598
|
-
// that happens to match PROPERTY_RE (e.g., "MyService: v2")
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
const compMatch = trimmed.match(COMPONENT_RE);
|
|
602
|
-
if (compMatch) {
|
|
603
|
-
finishCurrentTagGroup();
|
|
604
|
-
const { qname, uname, alias: asAlias, meta } = compMatch.groups!;
|
|
605
|
-
const rawName = (qname ?? uname ?? '').trim();
|
|
606
|
-
const peeled = peelInfraDecorations(rawName, lineNumber);
|
|
607
|
-
const name = peeled.label;
|
|
608
|
-
const alias = asAlias ?? peeled.alias;
|
|
609
|
-
const rest = meta ?? '';
|
|
610
|
-
const { tags: nodeTags } = extractPipeMetadata(rest);
|
|
611
|
-
warnUnknownMetaKeys(
|
|
612
|
-
nodeTags,
|
|
613
|
-
withTagAliases(INFRA_REGISTRY, tagAliasSet),
|
|
614
|
-
(msg) => warn(lineNumber, msg)
|
|
615
|
-
);
|
|
616
|
-
// A bare reference line may be an alias for an already-declared node
|
|
617
|
-
// (its label-slug differs from the alias, e.g. "Web App" as web). Resolve
|
|
618
|
-
// through the alias map first so an edge-source/reference merges into
|
|
619
|
-
// that node instead of spawning a duplicate stub.
|
|
620
|
-
const id = nameAliasMap.get(name) ?? nodeId(name);
|
|
621
|
-
if (alias) nameAliasMap.set(alias, id);
|
|
622
|
-
// Cascade group metadata into node tags; node-level metadata overrides
|
|
623
|
-
const tags: Record<string, string> = currentGroup.metadata
|
|
624
|
-
? { ...currentGroup.metadata, ...nodeTags }
|
|
625
|
-
: nodeTags;
|
|
626
|
-
|
|
627
|
-
currentNode = {
|
|
628
|
-
id,
|
|
629
|
-
label: name,
|
|
630
|
-
properties: [],
|
|
631
|
-
groupId: currentGroup.id,
|
|
632
|
-
tags,
|
|
633
|
-
isEdge: false,
|
|
634
|
-
lineNumber,
|
|
635
|
-
};
|
|
636
|
-
nodeMutableTags.set(id, tags);
|
|
637
|
-
baseIndent = indent;
|
|
638
|
-
continue;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// Inside a component block — properties and connections
|
|
643
|
-
if (currentNode && indent > baseIndent) {
|
|
644
|
-
// Detect deprecated xN fanout syntax
|
|
645
|
-
const deprecatedFanout = trimmed.match(DEPRECATED_FANOUT_RE);
|
|
646
|
-
if (
|
|
647
|
-
deprecatedFanout &&
|
|
648
|
-
(trimmed.startsWith('->') ||
|
|
649
|
-
trimmed.startsWith('-') ||
|
|
650
|
-
trimmed.startsWith('~'))
|
|
651
|
-
) {
|
|
652
|
-
const n = deprecatedFanout[1];
|
|
653
|
-
setError(
|
|
654
|
-
lineNumber,
|
|
655
|
-
`'x${n}' fanout syntax is no longer supported — use 'fanout: ${n}' instead`
|
|
656
|
-
);
|
|
657
|
-
continue;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// Async simple connection: ~> Target
|
|
661
|
-
const asyncSimpleConn = trimmed.match(ASYNC_SIMPLE_CONNECTION_RE);
|
|
662
|
-
if (asyncSimpleConn) {
|
|
663
|
-
// Capture group 1 in-bounds after successful match.
|
|
664
|
-
const targetRaw = asyncSimpleConn[1]!.trim();
|
|
665
|
-
const pipeMeta = extractPipeMetadata(targetRaw);
|
|
666
|
-
const targetName = pipeMeta.clean || targetRaw;
|
|
667
|
-
warnUnparsedPipeMeta(targetName, lineNumber, warn);
|
|
668
|
-
warnUnknownMetaKeys(pipeMeta.tags, EDGE_PIPE_REGISTRY, (msg) =>
|
|
669
|
-
warn(lineNumber, msg)
|
|
670
|
-
);
|
|
671
|
-
const split = pipeMeta.tags['split']
|
|
672
|
-
? parseFloat(pipeMeta.tags['split'])
|
|
673
|
-
: null;
|
|
674
|
-
const fanoutRaw = pipeMeta.tags['fanout']
|
|
675
|
-
? parseInt(pipeMeta.tags['fanout'], 10)
|
|
676
|
-
: null;
|
|
677
|
-
if (fanoutRaw !== null && fanoutRaw < 1) {
|
|
678
|
-
warn(
|
|
679
|
-
lineNumber,
|
|
680
|
-
`Fan-out multiplier must be at least 1 (got fanout: ${fanoutRaw}). Ignoring.`
|
|
681
|
-
);
|
|
682
|
-
}
|
|
683
|
-
const fanout = fanoutRaw !== null && fanoutRaw >= 1 ? fanoutRaw : null;
|
|
684
|
-
const targetIdAsync = resolveTargetId(targetName);
|
|
685
|
-
rememberTargetDisplay(targetIdAsync, targetName);
|
|
686
|
-
result.edges.push({
|
|
687
|
-
sourceId: currentNode.id,
|
|
688
|
-
targetId: targetIdAsync,
|
|
689
|
-
label: '',
|
|
690
|
-
async: true,
|
|
691
|
-
split,
|
|
692
|
-
fanout,
|
|
693
|
-
lineNumber,
|
|
694
|
-
});
|
|
695
|
-
continue;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
// Async labeled connection: ~label~> Target
|
|
699
|
-
const asyncConnMatch = trimmed.match(ASYNC_CONNECTION_RE);
|
|
700
|
-
if (asyncConnMatch) {
|
|
701
|
-
const rawLabel = asyncConnMatch[1] ?? '';
|
|
702
|
-
const labelResult = parseInArrowLabel(rawLabel, lineNumber);
|
|
703
|
-
result.diagnostics.push(...labelResult.diagnostics);
|
|
704
|
-
const label = labelResult.label ?? '';
|
|
705
|
-
// Capture group 2 in-bounds after successful match.
|
|
706
|
-
const targetRaw = asyncConnMatch[2]!.trim();
|
|
707
|
-
const pipeMeta = extractPipeMetadata(targetRaw);
|
|
708
|
-
const targetName = pipeMeta.clean || targetRaw;
|
|
709
|
-
warnUnparsedPipeMeta(targetName, lineNumber, warn);
|
|
710
|
-
warnUnknownMetaKeys(pipeMeta.tags, EDGE_PIPE_REGISTRY, (msg) =>
|
|
711
|
-
warn(lineNumber, msg)
|
|
712
|
-
);
|
|
713
|
-
const split = pipeMeta.tags['split']
|
|
714
|
-
? parseFloat(pipeMeta.tags['split'])
|
|
715
|
-
: null;
|
|
716
|
-
const fanoutRaw = pipeMeta.tags['fanout']
|
|
717
|
-
? parseInt(pipeMeta.tags['fanout'], 10)
|
|
718
|
-
: null;
|
|
719
|
-
if (fanoutRaw !== null && fanoutRaw < 1) {
|
|
720
|
-
warn(
|
|
721
|
-
lineNumber,
|
|
722
|
-
`Fan-out multiplier must be at least 1 (got fanout: ${fanoutRaw}). Ignoring.`
|
|
723
|
-
);
|
|
724
|
-
}
|
|
725
|
-
const fanout = fanoutRaw !== null && fanoutRaw >= 1 ? fanoutRaw : null;
|
|
726
|
-
|
|
727
|
-
let targetId: string;
|
|
728
|
-
const targetGroupMatch = targetName.match(/^\[([^\]]+)\]/);
|
|
729
|
-
if (targetGroupMatch) {
|
|
730
|
-
// Capture group 1 in-bounds after successful match.
|
|
731
|
-
targetId = groupId(targetGroupMatch[1]!);
|
|
732
|
-
rememberTargetDisplay(targetId, `[${targetGroupMatch[1]}]`);
|
|
733
|
-
} else {
|
|
734
|
-
targetId = resolveTargetId(targetName);
|
|
735
|
-
rememberTargetDisplay(targetId, targetName);
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
result.edges.push({
|
|
739
|
-
sourceId: currentNode.id,
|
|
740
|
-
targetId,
|
|
741
|
-
label,
|
|
742
|
-
async: true,
|
|
743
|
-
split,
|
|
744
|
-
fanout,
|
|
745
|
-
lineNumber,
|
|
746
|
-
});
|
|
747
|
-
continue;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
// Simple connection: -> Target or -> Target | fanout: 5
|
|
751
|
-
const simpleConn = trimmed.match(SIMPLE_CONNECTION_RE);
|
|
752
|
-
if (simpleConn) {
|
|
753
|
-
// Capture group 1 in-bounds after successful match.
|
|
754
|
-
const targetRaw = simpleConn[1]!.trim();
|
|
755
|
-
const pipeMeta = extractPipeMetadata(targetRaw);
|
|
756
|
-
const targetName = pipeMeta.clean || targetRaw;
|
|
757
|
-
warnUnparsedPipeMeta(targetName, lineNumber, warn);
|
|
758
|
-
warnUnknownMetaKeys(pipeMeta.tags, EDGE_PIPE_REGISTRY, (msg) =>
|
|
759
|
-
warn(lineNumber, msg)
|
|
760
|
-
);
|
|
761
|
-
const split = pipeMeta.tags['split']
|
|
762
|
-
? parseFloat(pipeMeta.tags['split'])
|
|
763
|
-
: null;
|
|
764
|
-
const fanoutRaw = pipeMeta.tags['fanout']
|
|
765
|
-
? parseInt(pipeMeta.tags['fanout'], 10)
|
|
766
|
-
: null;
|
|
767
|
-
if (fanoutRaw !== null && fanoutRaw < 1) {
|
|
768
|
-
warn(
|
|
769
|
-
lineNumber,
|
|
770
|
-
`Fan-out multiplier must be at least 1 (got fanout: ${fanoutRaw}). Ignoring.`
|
|
771
|
-
);
|
|
772
|
-
}
|
|
773
|
-
const fanout = fanoutRaw !== null && fanoutRaw >= 1 ? fanoutRaw : null;
|
|
774
|
-
const targetIdSimple = resolveTargetId(targetName);
|
|
775
|
-
rememberTargetDisplay(targetIdSimple, targetName);
|
|
776
|
-
result.edges.push({
|
|
777
|
-
sourceId: currentNode.id,
|
|
778
|
-
targetId: targetIdSimple,
|
|
779
|
-
label: '',
|
|
780
|
-
async: false,
|
|
781
|
-
split,
|
|
782
|
-
fanout,
|
|
783
|
-
lineNumber,
|
|
784
|
-
});
|
|
785
|
-
continue;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
// Labeled connection: -label-> Target | split: N%, fanout: 3
|
|
789
|
-
const connMatch = trimmed.match(CONNECTION_RE);
|
|
790
|
-
if (connMatch) {
|
|
791
|
-
const rawLabel = connMatch[1] ?? '';
|
|
792
|
-
const labelResult = parseInArrowLabel(rawLabel, lineNumber);
|
|
793
|
-
result.diagnostics.push(...labelResult.diagnostics);
|
|
794
|
-
const label = labelResult.label ?? '';
|
|
795
|
-
// Capture group 2 in-bounds after successful match.
|
|
796
|
-
const targetRaw = connMatch[2]!.trim();
|
|
797
|
-
const pipeMeta = extractPipeMetadata(targetRaw);
|
|
798
|
-
const targetName = pipeMeta.clean || targetRaw;
|
|
799
|
-
warnUnparsedPipeMeta(targetName, lineNumber, warn);
|
|
800
|
-
warnUnknownMetaKeys(pipeMeta.tags, EDGE_PIPE_REGISTRY, (msg) =>
|
|
801
|
-
warn(lineNumber, msg)
|
|
802
|
-
);
|
|
803
|
-
const split = pipeMeta.tags['split']
|
|
804
|
-
? parseFloat(pipeMeta.tags['split'])
|
|
805
|
-
: null;
|
|
806
|
-
const fanoutRaw = pipeMeta.tags['fanout']
|
|
807
|
-
? parseInt(pipeMeta.tags['fanout'], 10)
|
|
808
|
-
: null;
|
|
809
|
-
if (fanoutRaw !== null && fanoutRaw < 1) {
|
|
810
|
-
warn(
|
|
811
|
-
lineNumber,
|
|
812
|
-
`Fan-out multiplier must be at least 1 (got fanout: ${fanoutRaw}). Ignoring.`
|
|
813
|
-
);
|
|
814
|
-
}
|
|
815
|
-
const fanout = fanoutRaw !== null && fanoutRaw >= 1 ? fanoutRaw : null;
|
|
816
|
-
|
|
817
|
-
// Target might be a group ref like [API Pods] or an alias literal.
|
|
818
|
-
let targetId: string;
|
|
819
|
-
const targetGroupMatch = targetName.match(/^\[([^\]]+)\]/);
|
|
820
|
-
if (targetGroupMatch) {
|
|
821
|
-
// Capture group 1 in-bounds after successful match.
|
|
822
|
-
targetId = groupId(targetGroupMatch[1]!);
|
|
823
|
-
rememberTargetDisplay(targetId, `[${targetGroupMatch[1]}]`);
|
|
824
|
-
} else {
|
|
825
|
-
targetId = resolveTargetId(targetName);
|
|
826
|
-
rememberTargetDisplay(targetId, targetName);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
result.edges.push({
|
|
830
|
-
sourceId: currentNode.id,
|
|
831
|
-
targetId,
|
|
832
|
-
label,
|
|
833
|
-
async: false,
|
|
834
|
-
split,
|
|
835
|
-
fanout,
|
|
836
|
-
lineNumber,
|
|
837
|
-
});
|
|
838
|
-
continue;
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
// Empty description (no value) — silently skip rather than emitting "Unexpected line"
|
|
842
|
-
if (/^description\s*:?\s*$/i.test(trimmed)) continue;
|
|
843
|
-
|
|
844
|
-
// Property: key: value
|
|
845
|
-
const propMatch = trimmed.match(PROPERTY_RE);
|
|
846
|
-
if (propMatch) {
|
|
847
|
-
// Capture groups 1 & 2 in-bounds after successful match.
|
|
848
|
-
const key = propMatch[1]!.toLowerCase();
|
|
849
|
-
const rawVal = propMatch[2]!.trim();
|
|
850
|
-
|
|
851
|
-
// description is display metadata, not a behavior key; silently ignored on edge nodes.
|
|
852
|
-
// Single-line only — no length enforcement, but keep it short for legibility.
|
|
853
|
-
if (key === 'description' && currentNode) {
|
|
854
|
-
if (!currentNode.isEdge) {
|
|
855
|
-
pushDescription(currentNode, rawVal);
|
|
856
|
-
}
|
|
857
|
-
continue;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
// Indented tag assignment — `t: Edge` on its own line, the indented twin
|
|
861
|
-
// of same-line `Name t: Edge`. A tag alias is neither a behavior property
|
|
862
|
-
// nor 'description', so without this it falls through to the unknown-key
|
|
863
|
-
// path and is mis-collected as description text, silently dropping the
|
|
864
|
-
// node's tier. currentNode.tags is the same object held in
|
|
865
|
-
// nodeMutableTags, so a direct assign is enough.
|
|
866
|
-
if (currentNode && tagAliasSet.has(normalizeName(key))) {
|
|
867
|
-
const mut = nodeMutableTags.get(currentNode.id);
|
|
868
|
-
if (mut) mut[tagAttrKey(key)] = rawVal;
|
|
869
|
-
continue;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
// Unknown keys: decide between property typo warning vs description collection.
|
|
873
|
-
// Heuristic: if the key looks like a plausible property (alphanumeric-hyphen, close
|
|
874
|
-
// match to a known key, or the value looks numeric/percentage), warn as typo.
|
|
875
|
-
// Otherwise treat the whole line as description text.
|
|
876
|
-
if (!INFRA_BEHAVIOR_KEYS.has(key) && !EDGE_ONLY_KEYS.has(key)) {
|
|
877
|
-
const allKeys = [...INFRA_BEHAVIOR_KEYS, ...EDGE_ONLY_KEYS];
|
|
878
|
-
const hint = suggest(key, allKeys);
|
|
879
|
-
const valueLooksNumeric = /^[\d.]+%?$/.test(rawVal);
|
|
880
|
-
if (hint || valueLooksNumeric) {
|
|
881
|
-
// Likely a typo — warn
|
|
882
|
-
let msg = `Unknown infra property '${key}'.`;
|
|
883
|
-
if (hint) msg += ` ${hint}`;
|
|
884
|
-
else
|
|
885
|
-
msg +=
|
|
886
|
-
` Properties are colon-keyed 'key: value' from the known schema` +
|
|
887
|
-
` (e.g. latency-ms, max-rps, cache-hit, instances, concurrency).`;
|
|
888
|
-
warn(lineNumber, msg);
|
|
889
|
-
} else if (!currentNode.isEdge) {
|
|
890
|
-
// Likely prose — collect as description
|
|
891
|
-
pushDescription(currentNode, trimmed);
|
|
892
|
-
continue;
|
|
893
|
-
}
|
|
894
|
-
continue;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
// Validate edge-only keys
|
|
898
|
-
if (EDGE_ONLY_KEYS.has(key) && !currentNode.isEdge) {
|
|
899
|
-
warn(
|
|
900
|
-
lineNumber,
|
|
901
|
-
`Property '${key}' belongs on the entry point — declare it indented under an 'internet' or 'edge' node, not on '${currentNode.label}'.`
|
|
902
|
-
);
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
const value = parsePropertyValue(rawVal);
|
|
906
|
-
currentNode.properties.push({ key, value, lineNumber });
|
|
907
|
-
continue;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
// Indented line inside a component: only the `description` keyword
|
|
911
|
-
// attaches as a description; a bare prose line is no longer auto-promoted.
|
|
912
|
-
const descResult = tryStripDescriptionKeyword(trimmed);
|
|
913
|
-
if (descResult.isKeyword && !descResult.needsColon) {
|
|
914
|
-
if (currentNode.isEdge) {
|
|
915
|
-
// description on edge nodes is silently ignored
|
|
916
|
-
continue;
|
|
917
|
-
}
|
|
918
|
-
pushDescription(currentNode, descResult.text);
|
|
919
|
-
continue;
|
|
920
|
-
}
|
|
921
|
-
warn(
|
|
922
|
-
lineNumber,
|
|
923
|
-
`Unexpected line inside component '${currentNode.label}'. Expected a property (key: value), connection (-> Target), or a description (description: text).`
|
|
924
|
-
);
|
|
925
|
-
continue;
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
// Component inside group (same indent as group children)
|
|
929
|
-
if (currentGroup && indent > 0) {
|
|
930
|
-
finishCurrentNode();
|
|
931
|
-
|
|
932
|
-
const compMatch = trimmed.match(COMPONENT_RE);
|
|
933
|
-
if (compMatch) {
|
|
934
|
-
const { qname, uname, alias: asAlias, meta } = compMatch.groups!;
|
|
935
|
-
const rawName = (qname ?? uname ?? '').trim();
|
|
936
|
-
const peeled = peelInfraDecorations(rawName, lineNumber);
|
|
937
|
-
const name = peeled.label;
|
|
938
|
-
const alias = asAlias ?? peeled.alias;
|
|
939
|
-
const rest = meta ?? '';
|
|
940
|
-
const { tags: nodeTags } = extractPipeMetadata(rest);
|
|
941
|
-
warnUnknownMetaKeys(
|
|
942
|
-
nodeTags,
|
|
943
|
-
withTagAliases(INFRA_REGISTRY, tagAliasSet),
|
|
944
|
-
(msg) => warn(lineNumber, msg)
|
|
945
|
-
);
|
|
946
|
-
// A bare reference line may be an alias for an already-declared node
|
|
947
|
-
// (its label-slug differs from the alias, e.g. "Web App" as web). Resolve
|
|
948
|
-
// through the alias map first so an edge-source/reference merges into
|
|
949
|
-
// that node instead of spawning a duplicate stub.
|
|
950
|
-
const id = nameAliasMap.get(name) ?? nodeId(name);
|
|
951
|
-
if (alias) nameAliasMap.set(alias, id);
|
|
952
|
-
const tags: Record<string, string> = currentGroup.metadata
|
|
953
|
-
? { ...currentGroup.metadata, ...nodeTags }
|
|
954
|
-
: nodeTags;
|
|
955
|
-
|
|
956
|
-
currentNode = {
|
|
957
|
-
id,
|
|
958
|
-
label: name,
|
|
959
|
-
properties: [],
|
|
960
|
-
groupId: currentGroup.id,
|
|
961
|
-
tags,
|
|
962
|
-
isEdge: false,
|
|
963
|
-
lineNumber,
|
|
964
|
-
};
|
|
965
|
-
nodeMutableTags.set(id, tags);
|
|
966
|
-
baseIndent = indent;
|
|
967
|
-
continue;
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
// If we reach here and indent is 0, try as a top-level component
|
|
972
|
-
if (indent === 0) {
|
|
973
|
-
const compMatch = trimmed.match(COMPONENT_RE);
|
|
974
|
-
if (compMatch) {
|
|
975
|
-
finishCurrentNode();
|
|
976
|
-
finishCurrentTagGroup();
|
|
977
|
-
currentGroup = null;
|
|
978
|
-
|
|
979
|
-
const { qname, uname, alias: asAlias, meta } = compMatch.groups!;
|
|
980
|
-
const rawName = (qname ?? uname ?? '').trim();
|
|
981
|
-
const peeled = peelInfraDecorations(rawName, lineNumber);
|
|
982
|
-
const name = peeled.label;
|
|
983
|
-
const alias = asAlias ?? peeled.alias;
|
|
984
|
-
const rest = meta ?? '';
|
|
985
|
-
const { tags } = extractPipeMetadata(rest);
|
|
986
|
-
warnUnknownMetaKeys(
|
|
987
|
-
tags,
|
|
988
|
-
withTagAliases(INFRA_REGISTRY, tagAliasSet),
|
|
989
|
-
(msg) => warn(lineNumber, msg)
|
|
990
|
-
);
|
|
991
|
-
// A bare reference line may be an alias for an already-declared node
|
|
992
|
-
// (its label-slug differs from the alias, e.g. "Web App" as web). Resolve
|
|
993
|
-
// through the alias map first so an edge-source/reference merges into
|
|
994
|
-
// that node instead of spawning a duplicate stub.
|
|
995
|
-
const id = nameAliasMap.get(name) ?? nodeId(name);
|
|
996
|
-
if (alias) nameAliasMap.set(alias, id);
|
|
997
|
-
|
|
998
|
-
currentNode = {
|
|
999
|
-
id,
|
|
1000
|
-
label: name,
|
|
1001
|
-
properties: [],
|
|
1002
|
-
groupId: null,
|
|
1003
|
-
tags,
|
|
1004
|
-
isEdge: EDGE_NODE_NAMES.has(id.toLowerCase()),
|
|
1005
|
-
lineNumber,
|
|
1006
|
-
};
|
|
1007
|
-
nodeMutableTags.set(id, tags);
|
|
1008
|
-
baseIndent = 0;
|
|
1009
|
-
continue;
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
// Catch-all: nothing matched this line
|
|
1014
|
-
warn(
|
|
1015
|
-
lineNumber,
|
|
1016
|
-
currentGroup
|
|
1017
|
-
? `Unexpected line: '${trimmed}'. Inside a group, indent a component name under the '[Group]'.`
|
|
1018
|
-
: `Unexpected line: '${trimmed}'. Expected a node ('Api Gateway'), a group ('[Backend]'), a tag group ('tag Tier as t'), or an option ('default-rps 100'); node properties go indented under a node as 'key: value'.`
|
|
1019
|
-
);
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
// Flush last open blocks
|
|
1023
|
-
finishCurrentNode();
|
|
1024
|
-
finishCurrentTagGroup();
|
|
1025
|
-
|
|
1026
|
-
// Ensure referenced targets exist (create stub nodes for forward references)
|
|
1027
|
-
for (const edge of result.edges) {
|
|
1028
|
-
if (!nodeMap.has(edge.targetId)) {
|
|
1029
|
-
// Check if target is a group
|
|
1030
|
-
const isGroup = result.groups.some((g) => g.id === edge.targetId);
|
|
1031
|
-
if (!isGroup) {
|
|
1032
|
-
// Create a stub node for forward-referenced targets. Display label
|
|
1033
|
-
// recovers the user's first-seen casing so the rendered SVG shows
|
|
1034
|
-
// 'CDN' instead of the lowercased internal id.
|
|
1035
|
-
const stubDisplay = targetDisplays.get(edge.targetId) ?? edge.targetId;
|
|
1036
|
-
const stubTags: Record<string, string> = {};
|
|
1037
|
-
const stub: Writable<InfraNode> = {
|
|
1038
|
-
id: edge.targetId,
|
|
1039
|
-
label: stubDisplay,
|
|
1040
|
-
properties: [],
|
|
1041
|
-
groupId: null,
|
|
1042
|
-
tags: stubTags,
|
|
1043
|
-
isEdge: false,
|
|
1044
|
-
lineNumber: edge.lineNumber,
|
|
1045
|
-
};
|
|
1046
|
-
// stub.id is edge.targetId, already-normalized via nodeId() at edge construction
|
|
1047
|
-
// eslint-disable-next-line name-normalize/required-at-insertion
|
|
1048
|
-
nodeMap.set(stub.id, stub);
|
|
1049
|
-
result.nodes.push(stub);
|
|
1050
|
-
nodeMutableTags.set(stub.id, stubTags);
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
// Inject default tag values into nodes that don't have one
|
|
1056
|
-
for (const tg of result.tagGroups) {
|
|
1057
|
-
if (!tg.defaultValue) continue;
|
|
1058
|
-
const key = tagAttrKey(tg.alias ?? tg.name);
|
|
1059
|
-
for (const node of result.nodes) {
|
|
1060
|
-
if (node.isEdge) continue;
|
|
1061
|
-
if (!(key in node.tags)) {
|
|
1062
|
-
const tags = nodeMutableTags.get(node.id);
|
|
1063
|
-
if (tags) tags[key] = tg.defaultValue;
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
validateTagGroupNames(result.tagGroups, warn, setError);
|
|
1069
|
-
|
|
1070
|
-
checkReachability(result);
|
|
1071
|
-
|
|
1072
|
-
return result;
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
/**
|
|
1076
|
-
* Reachability lint: an infra diagram traces request traffic flowing inward
|
|
1077
|
-
* from an `internet`/`edge` entry, so every node must have a directed path
|
|
1078
|
-
* back to an entry. A node nothing routes to carries no traffic — it's dead
|
|
1079
|
-
* and doesn't belong. Emits `warning`-severity diagnostics (never blocks
|
|
1080
|
-
* rendering). Mirrors the compute model: an edge targeting a `[Group]`
|
|
1081
|
-
* delivers to all of that group's children.
|
|
1082
|
-
*/
|
|
1083
|
-
function checkReachability(result: Writable<ParsedInfra>): void {
|
|
1084
|
-
if (result.nodes.length === 0) return;
|
|
1085
|
-
|
|
1086
|
-
const entries = result.nodes.filter((n) => n.isEdge);
|
|
1087
|
-
|
|
1088
|
-
// No entry at all: the rule can't be satisfied. One diagnostic, not N.
|
|
1089
|
-
if (entries.length === 0) {
|
|
1090
|
-
const line = result.titleLineNumber ?? result.nodes[0]?.lineNumber ?? 1;
|
|
1091
|
-
result.diagnostics.push(emit(GRAPH_DX.INFRA_NO_ENTRY, line));
|
|
1092
|
-
return;
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
// groupId -> child node ids (for expanding edges that target a [Group]).
|
|
1096
|
-
const groupChildMap = new Map<string, string[]>();
|
|
1097
|
-
for (const node of result.nodes) {
|
|
1098
|
-
if (node.groupId) {
|
|
1099
|
-
const list = groupChildMap.get(node.groupId) ?? [];
|
|
1100
|
-
list.push(node.id);
|
|
1101
|
-
// node.groupId is already a normalized id from parse time.
|
|
1102
|
-
// eslint-disable-next-line name-normalize/required-at-insertion
|
|
1103
|
-
groupChildMap.set(node.groupId, list);
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
// Outbound adjacency: an edge to a group reaches every child of that group.
|
|
1108
|
-
const outbound = new Map<string, string[]>();
|
|
1109
|
-
for (const edge of result.edges) {
|
|
1110
|
-
const targets = groupChildMap.get(edge.targetId) ?? [edge.targetId];
|
|
1111
|
-
const list = outbound.get(edge.sourceId) ?? [];
|
|
1112
|
-
list.push(...targets);
|
|
1113
|
-
outbound.set(edge.sourceId, list);
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
// BFS from every entry node.
|
|
1117
|
-
const reachable = new Set<string>();
|
|
1118
|
-
const queue: string[] = [];
|
|
1119
|
-
for (const entry of entries) {
|
|
1120
|
-
reachable.add(entry.id);
|
|
1121
|
-
queue.push(entry.id);
|
|
1122
|
-
}
|
|
1123
|
-
while (queue.length > 0) {
|
|
1124
|
-
const current = queue.shift()!;
|
|
1125
|
-
for (const next of outbound.get(current) ?? []) {
|
|
1126
|
-
if (!reachable.has(next)) {
|
|
1127
|
-
reachable.add(next);
|
|
1128
|
-
queue.push(next);
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
for (const node of result.nodes) {
|
|
1134
|
-
if (node.isEdge || reachable.has(node.id)) continue;
|
|
1135
|
-
result.diagnostics.push(
|
|
1136
|
-
emit(GRAPH_DX.INFRA_UNREACHABLE, node.lineNumber, { label: node.label })
|
|
1137
|
-
);
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
// ============================================================
|
|
1142
|
-
// Symbol extraction (for completion API)
|
|
1143
|
-
// ============================================================
|
|
1144
|
-
|
|
1145
|
-
import type { DiagramSymbols } from '../completion-types';
|
|
1146
|
-
|
|
1147
|
-
/**
|
|
1148
|
-
* Extract component names (entities) from infra document text.
|
|
1149
|
-
* Used by the dgmo completion API for ghost hints and popup completions.
|
|
1150
|
-
*/
|
|
1151
|
-
/** Strip ` as <alias>` and ` is a/an <type>` decorations from a node name
|
|
1152
|
-
* so completion suggests the bare identifier the user will reference. */
|
|
1153
|
-
function stripNodeDecorations(name: string): string {
|
|
1154
|
-
let s = name.trim();
|
|
1155
|
-
const aliasMatch = s.match(/^(.*?)\s+as\s+[A-Za-z][A-Za-z0-9_]{0,11}\s*$/);
|
|
1156
|
-
// Capture group 1 in-bounds after successful match.
|
|
1157
|
-
if (aliasMatch) s = aliasMatch[1]!.trim();
|
|
1158
|
-
const isAMatch = s.match(/^(.*?)\s+is\s+an?\s+[A-Za-z][\w-]*\s*$/i);
|
|
1159
|
-
// Capture group 1 in-bounds after successful match.
|
|
1160
|
-
if (isAMatch) s = isAMatch[1]!.trim();
|
|
1161
|
-
return s;
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
export function extractSymbols(docText: string): DiagramSymbols {
|
|
1165
|
-
const entities: string[] = [];
|
|
1166
|
-
let inMetadata = true;
|
|
1167
|
-
let inTagGroup = false;
|
|
1168
|
-
for (const rawLine of docText.split('\n')) {
|
|
1169
|
-
const line = rawLine.trim();
|
|
1170
|
-
if (line.length === 0) continue;
|
|
1171
|
-
const indented = /^\s/.test(rawLine);
|
|
1172
|
-
|
|
1173
|
-
// Metadata phase: skip until first non-metadata root-level line.
|
|
1174
|
-
// Metadata includes: `infra [Title]`, `chart: type`, `direction X`, `slo-*`, etc.
|
|
1175
|
-
if (inMetadata) {
|
|
1176
|
-
if (!indented) {
|
|
1177
|
-
// Recognize new-style bare options (`key value`) and old-style (`key: value`)
|
|
1178
|
-
const firstLine = parseFirstLine(line);
|
|
1179
|
-
if (firstLine) continue; // chart type line
|
|
1180
|
-
if (/^(?:direction-tb|animate|no-animate|slo-|default-)/i.test(line))
|
|
1181
|
-
continue;
|
|
1182
|
-
if (/^[a-z-]+\s*:/i.test(line)) continue; // legacy colon options
|
|
1183
|
-
inMetadata = false;
|
|
1184
|
-
} else {
|
|
1185
|
-
continue;
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
if (!indented) {
|
|
1190
|
-
// Root-level: tag group declaration, group header, or component
|
|
1191
|
-
if (/^tag\s/i.test(line)) {
|
|
1192
|
-
inTagGroup = true;
|
|
1193
|
-
continue;
|
|
1194
|
-
}
|
|
1195
|
-
if (/^tag\s*:/i.test(line)) {
|
|
1196
|
-
inTagGroup = true;
|
|
1197
|
-
continue;
|
|
1198
|
-
} // legacy
|
|
1199
|
-
inTagGroup = false;
|
|
1200
|
-
if (/^\[/.test(line)) continue; // [Group] header
|
|
1201
|
-
const m = COMPONENT_RE.exec(line);
|
|
1202
|
-
if (m) {
|
|
1203
|
-
const name = stripNodeDecorations((m[1] ?? m[2] ?? '').trim());
|
|
1204
|
-
if (name && !entities.includes(name)) entities.push(name);
|
|
1205
|
-
}
|
|
1206
|
-
} else {
|
|
1207
|
-
// Indented: skip tag values, connections, and properties; extract grouped components
|
|
1208
|
-
if (inTagGroup) continue;
|
|
1209
|
-
if (/^->/.test(line)) continue; // simple connection
|
|
1210
|
-
if (/^~>/.test(line)) continue; // async simple connection
|
|
1211
|
-
if (/^-[^>]+-?>/.test(line)) continue; // labeled connection
|
|
1212
|
-
if (/^~[^~]+~>/.test(line)) continue; // async labeled connection
|
|
1213
|
-
if (/^\w[\w-]*\s*:/.test(line)) continue; // property (key: value) legacy
|
|
1214
|
-
// New-style property: first token is a known behavior/property key
|
|
1215
|
-
// String.split always returns at least one element; line.length > 0 here.
|
|
1216
|
-
const firstToken = line.split(/\s/)[0]!.toLowerCase();
|
|
1217
|
-
if (
|
|
1218
|
-
(INFRA_BEHAVIOR_KEYS.has(firstToken) ||
|
|
1219
|
-
EDGE_ONLY_KEYS.has(firstToken) ||
|
|
1220
|
-
firstToken === 'description' ||
|
|
1221
|
-
firstToken === 'instances' ||
|
|
1222
|
-
firstToken === 'collapsed') &&
|
|
1223
|
-
/\s/.test(line)
|
|
1224
|
-
)
|
|
1225
|
-
continue;
|
|
1226
|
-
const m = COMPONENT_RE.exec(line);
|
|
1227
|
-
if (m) {
|
|
1228
|
-
const name = stripNodeDecorations((m[1] ?? m[2] ?? '').trim());
|
|
1229
|
-
if (name && !entities.includes(name)) entities.push(name);
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
return { kind: 'infra', entities };
|
|
1234
|
-
}
|