@r0kshan/cairn 1.0.0-RC13 → 1.0.0-RC15

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/cairn.mjs CHANGED
@@ -1,3 +1,23 @@
1
+ /*!
2
+ * cairn — Apache-2.0 — https://github.com/R0kshan/cairn
3
+ * Copyright 2026 Røkshan.
4
+ *
5
+ * This artifact contains third-party code and artwork:
6
+ *
7
+ * elkjs 0.12.0 — (c) 2017 Kiel University and others.
8
+ * Dual-licensed EPL-2.0 OR GPL-3.0-or-later; cairn elects EPL-2.0.
9
+ * Full text: licenses/elkjs-EPL-2.0.md — https://www.eclipse.org/legal/epl-2.0
10
+ *
11
+ * Simple Icons 16.29.0 artwork — CC0-1.0 project-wide.
12
+ * Six of the vendored icons carry their own terms instead — MIT,
13
+ * BSD-3-Clause, CC-BY-4.0 or Apache-2.0 — and are attributed individually.
14
+ * Full texts: licenses/
15
+ *
16
+ * Per-icon attribution and the full licence texts ship with this artifact:
17
+ * the package root (npm), the app directory (Scoop), or
18
+ * share/doc/cairn (Homebrew, install.sh). Also at
19
+ * https://github.com/R0kshan/cairn/blob/main/THIRD-PARTY-NOTICES.md
20
+ */
1
21
  var __create = Object.create;
2
22
  var __defProp = Object.defineProperty;
3
23
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -92303,6 +92323,7 @@ function lex(src, diagnostics) {
92303
92323
 
92304
92324
  // src/models/ast.ts
92305
92325
  var ATTACH_SIDES = ["left", "right", "top", "bottom"];
92326
+ var ATTACH_ROLES = ["producer", "consumer"];
92306
92327
  var defaultDiagramStyle = () => ({
92307
92328
  crossingHops: true,
92308
92329
  compact: false,
@@ -92369,10 +92390,6 @@ var darkPalette = {
92369
92390
  chipStroke: "#b08d2a",
92370
92391
  chipText: "#e0c068"
92371
92392
  };
92372
- var palettes = {
92373
- light: lightPalette,
92374
- dark: darkPalette
92375
- };
92376
92393
  var flowPalette = {
92377
92394
  light: [
92378
92395
  "#1f77b4",
@@ -92419,16 +92436,15 @@ var KIND_ROLE_MAP = {
92419
92436
  datastore: "datastore",
92420
92437
  queue: "datastore",
92421
92438
  gateway: "authGateway",
92439
+ firewall: "firewall",
92422
92440
  auth: "auth",
92423
92441
  idp: "identityProvider",
92424
92442
  site: "site",
92425
92443
  "network-zone": "networkZone",
92426
92444
  server: "server",
92427
- "app-instance": "appInstance",
92428
- "security-node": "securityNode",
92429
- asset: "leaf"
92445
+ "app-instance": "appInstance"
92430
92446
  };
92431
- var roleForKind = (kind, viewName) => viewName === "security" && kind === "external" ? "untrusted" : KIND_ROLE_MAP[kind] ?? "leaf";
92447
+ var roleForKind = (kind) => KIND_ROLE_MAP[kind] ?? "leaf";
92432
92448
  var buildTheme = (spec) => {
92433
92449
  const paletteSpec = spec.pal, accentColors = spec.accentColors;
92434
92450
  const palette = {
@@ -92464,28 +92480,25 @@ var buildTheme = (spec) => {
92464
92480
  application: containerStyle(accentColors.appF, accentColors.app),
92465
92481
  layer: containerStyle(accentColors.goldF, accentColors.gold, false, 1),
92466
92482
  external: containerStyle(accentColors.violetF, accentColors.violet, true),
92467
- untrusted: containerStyle(accentColors.redF, accentColors.red, true, 1.3),
92468
92483
  leaf: leafStyle(accentColors.leafF, accentColors.leafS),
92469
92484
  datastore: leafStyle(accentColors.purpleF, accentColors.purple),
92470
92485
  site: containerStyle(accentColors.siteF, accentColors.siteS, false, 1.4),
92471
92486
  networkZone: containerStyle(accentColors.greenF, accentColors.green, true),
92472
92487
  server: containerStyle(accentColors.serverF, accentColors.serverS, false, 1.5),
92473
92488
  appInstance: leafStyle(accentColors.aiF, accentColors.aiS, 1.2),
92474
- securityNode: leafStyle(accentColors.nodeF, accentColors.node, 1.6),
92489
+ // Accent keys predate the roles that read them: `auth`/`authF` is the
92490
+ // gateway's orange, `authn`/`authnF` the auth middleware's blue. The two
92491
+ // used to share the `node` orange, which is what made a gateway and an
92492
+ // auth middleware indistinguishable in a dense infrastructure view.
92475
92493
  authGateway: leafStyle(accentColors.authF, accentColors.auth, 1.4),
92476
- auth: leafStyle(accentColors.nodeF, accentColors.node, 1.5),
92494
+ firewall: leafStyle(accentColors.fwF, accentColors.fw, 1.6),
92495
+ auth: leafStyle(accentColors.authnF, accentColors.authn, 1.5),
92477
92496
  identityProvider: leafStyle(accentColors.idpF, accentColors.idp)
92478
- },
92479
- levels: {
92480
- public: containerStyle(spec.lv.public[0], spec.lv.public[1], false, 1.4),
92481
- internal: containerStyle(spec.lv.internal[0], spec.lv.internal[1], false, 1.4),
92482
- restricted: containerStyle(spec.lv.restricted[0], spec.lv.restricted[1], false, 1.4),
92483
- secret: containerStyle(spec.lv.secret[0], spec.lv.secret[1], false, 1.4)
92484
92497
  }
92485
92498
  };
92486
92499
  };
92487
- var themes = {
92488
- light: buildTheme({
92500
+ var THEME_SPECS = {
92501
+ light: {
92489
92502
  pal: {
92490
92503
  bg: "#ffffff",
92491
92504
  text: "#17202c",
@@ -92533,16 +92546,15 @@ var themes = {
92533
92546
  auth: "#b85a30",
92534
92547
  authF: "#f5e6dd",
92535
92548
  idp: "#3a8f8f",
92536
- idpF: "#e0f0f0"
92537
- },
92538
- lv: {
92539
- public: ["#fdeceb", "#d0463f"],
92540
- internal: ["#fef2e2", "#cf9436"],
92541
- restricted: ["#e9f2fb", "#2f7cc4"],
92542
- secret: ["#efe9f7", "#7a55a8"]
92549
+ idpF: "#e0f0f0",
92550
+ fw: "#c0392b",
92551
+ fwF: "#fdecea",
92552
+ authn: "#2f6fb5",
92553
+ authnF: "#e8f1fb"
92543
92554
  }
92544
- }),
92545
- dark: buildTheme({
92555
+ },
92556
+ dark: {
92557
+ dark: true,
92546
92558
  pal: {
92547
92559
  bg: "#1e2530",
92548
92560
  text: "#e6edf3",
@@ -92590,16 +92602,14 @@ var themes = {
92590
92602
  auth: "#c96a4a",
92591
92603
  authF: "#332218",
92592
92604
  idp: "#4fafaf",
92593
- idpF: "#1a2e2e"
92594
- },
92595
- lv: {
92596
- public: ["#3a2422", "#c25a54"],
92597
- internal: ["#332a1c", "#c08a44"],
92598
- restricted: ["#1f2a37", "#4a86b8"],
92599
- secret: ["#291f33", "#8a6cb0"]
92605
+ idpF: "#1a2e2e",
92606
+ fw: "#e5675a",
92607
+ fwF: "#33211f",
92608
+ authn: "#6fa8e0",
92609
+ authnF: "#1d2735"
92600
92610
  }
92601
- }),
92602
- slate: buildTheme({
92611
+ },
92612
+ slate: {
92603
92613
  pal: {
92604
92614
  bg: "#f7f9fb",
92605
92615
  text: "#26303c",
@@ -92647,16 +92657,14 @@ var themes = {
92647
92657
  auth: "#a85a30",
92648
92658
  authF: "#f0e4dd",
92649
92659
  idp: "#3a8f8f",
92650
- idpF: "#e4f0f0"
92651
- },
92652
- lv: {
92653
- public: ["#f7ece9", "#c05a4a"],
92654
- internal: ["#f3efe6", "#a8823f"],
92655
- restricted: ["#e8eff6", "#3b6ea5"],
92656
- secret: ["#efecf5", "#7d6ba8"]
92660
+ idpF: "#e4f0f0",
92661
+ fw: "#bd4b3c",
92662
+ fwF: "#f9e9e6",
92663
+ authn: "#3a6ea5",
92664
+ authnF: "#e9f0f8"
92657
92665
  }
92658
- }),
92659
- sand: buildTheme({
92666
+ },
92667
+ sand: {
92660
92668
  pal: {
92661
92669
  bg: "#faf6ee",
92662
92670
  text: "#3a2f22",
@@ -92704,16 +92712,14 @@ var themes = {
92704
92712
  auth: "#a85a30",
92705
92713
  authF: "#f0e5dd",
92706
92714
  idp: "#3a7a6f",
92707
- idpF: "#e5f0ed"
92708
- },
92709
- lv: {
92710
- public: ["#f7e2da", "#c0562a"],
92711
- internal: ["#f6ecd2", "#b0842e"],
92712
- restricted: ["#e6f0f1", "#3f7a8c"],
92713
- secret: ["#f0e8ef", "#8a5f7a"]
92715
+ idpF: "#e5f0ed",
92716
+ fw: "#b8492f",
92717
+ fwF: "#f8e8e0",
92718
+ authn: "#3a6b9a",
92719
+ authnF: "#e9eff7"
92714
92720
  }
92715
- }),
92716
- contrast: buildTheme({
92721
+ },
92722
+ contrast: {
92717
92723
  pal: {
92718
92724
  bg: "#ffffff",
92719
92725
  text: "#000000",
@@ -92761,16 +92767,15 @@ var themes = {
92761
92767
  auth: "#993a1a",
92762
92768
  authF: "#f6e3dd",
92763
92769
  idp: "#005a5a",
92764
- idpF: "#daf0f0"
92765
- },
92766
- lv: {
92767
- public: ["#f9dcd6", "#c0341a"],
92768
- internal: ["#f6e6c8", "#9a6a00"],
92769
- restricted: ["#e0edf7", "#005a9c"],
92770
- secret: ["#eee0f7", "#6a2fa0"]
92770
+ idpF: "#daf0f0",
92771
+ fw: "#a3140a",
92772
+ fwF: "#fbe3df",
92773
+ authn: "#00408f",
92774
+ authnF: "#dfe8fb"
92771
92775
  }
92772
- }),
92773
- nord: buildTheme({
92776
+ },
92777
+ nord: {
92778
+ dark: true,
92774
92779
  pal: {
92775
92780
  bg: "#2e3440",
92776
92781
  text: "#eceff4",
@@ -92818,16 +92823,14 @@ var themes = {
92818
92823
  auth: "#c96a4a",
92819
92824
  authF: "#332218",
92820
92825
  idp: "#6fafaf",
92821
- idpF: "#203432"
92822
- },
92823
- lv: {
92824
- public: ["#3a2a2d", "#bf616a"],
92825
- internal: ["#3a3524", "#d0a85f"],
92826
- restricted: ["#2f3a44", "#81a1c1"],
92827
- secret: ["#312a3a", "#a38bbd"]
92826
+ idpF: "#203432",
92827
+ fw: "#bf616a",
92828
+ fwF: "#3b2f34",
92829
+ authn: "#81a1c1",
92830
+ authnF: "#333c4a"
92828
92831
  }
92829
- }),
92830
- solarized: buildTheme({
92832
+ },
92833
+ solarized: {
92831
92834
  pal: {
92832
92835
  bg: "#fdf6e3",
92833
92836
  text: "#586e75",
@@ -92875,34 +92878,41 @@ var themes = {
92875
92878
  auth: "#b85a30",
92876
92879
  authF: "#f5e6dd",
92877
92880
  idp: "#2aa198",
92878
- idpF: "#dff0ec"
92879
- },
92880
- lv: {
92881
- public: ["#f7ddd6", "#dc322f"],
92882
- internal: ["#f2e6c8", "#b58900"],
92883
- restricted: ["#e3edf3", "#268bd2"],
92884
- secret: ["#e8e6f2", "#6c71c4"]
92881
+ idpF: "#dff0ec",
92882
+ fw: "#dc322f",
92883
+ fwF: "#f7e3dd",
92884
+ authn: "#268bd2",
92885
+ authnF: "#e3edf5"
92885
92886
  }
92886
- })
92887
+ }
92887
92888
  };
92889
+ var themes = Object.fromEntries(
92890
+ Object.entries(THEME_SPECS).map(([name, spec]) => [name, buildTheme(spec)])
92891
+ );
92888
92892
  var themeNames = [...Object.keys(themes), "classic", "classic-dark"];
92893
+ function isDarkTheme(name) {
92894
+ return name === "classic-dark" || THEME_SPECS[name]?.dark === true;
92895
+ }
92889
92896
  function themeFor(name, view) {
92890
- if (name === "classic")
92891
- return {
92892
- palette: lightPalette,
92893
- kinds: view.defaults,
92894
- levels: view.levelDefaults ?? {}
92895
- };
92896
- if (name === "classic-dark")
92897
- return {
92898
- palette: darkPalette,
92899
- kinds: view.defaultsDark,
92900
- levels: view.levelDefaultsDark ?? {}
92901
- };
92902
- const theme = themes[name] ?? themes.light;
92897
+ if (name === "classic") return { palette: lightPalette, kinds: view.defaults };
92898
+ if (name === "classic-dark") return { palette: darkPalette, kinds: view.defaultsDark };
92899
+ return applyTheme(themes[name] ?? themes.light, view);
92900
+ }
92901
+ function themeFromSpec(spec, view) {
92902
+ return applyTheme(buildTheme(spec), view);
92903
+ }
92904
+ function paletteFor(name) {
92905
+ if (name === "classic") return lightPalette;
92906
+ if (name === "classic-dark") return darkPalette;
92907
+ return themes[name]?.palette ?? lightPalette;
92908
+ }
92909
+ function paletteFromSpec(spec) {
92910
+ return buildTheme(spec).palette;
92911
+ }
92912
+ function applyTheme(theme, view) {
92903
92913
  const kinds = {};
92904
- for (const kind of view.kinds) kinds[kind] = theme.roles[roleForKind(kind, view.name)] ?? {};
92905
- return { palette: theme.palette, kinds, levels: theme.levels };
92914
+ for (const kind of view.kinds) kinds[kind] = theme.roles[roleForKind(kind)] ?? {};
92915
+ return { palette: theme.palette, kinds };
92906
92916
  }
92907
92917
 
92908
92918
  // src/element-tree.ts
@@ -93020,7 +93030,7 @@ function parseFlow(p, sourceToken) {
93020
93030
  model.flows.push(flow);
93021
93031
  }
93022
93032
  function parseElement(p, sourceToken, parent) {
93023
- const { matchToken, advance, reportError, lookAhead, syncToNextLine, model } = p;
93033
+ const { matchToken, advance, reportError, syncToNextLine, model } = p;
93024
93034
  if (!matchToken("id")) {
93025
93035
  reportError(
93026
93036
  `invalid declaration: \`${sourceToken.text}\` alone on this line`,
@@ -93040,84 +93050,66 @@ function parseElement(p, sourceToken, parent) {
93040
93050
  parent: parent ?? void 0
93041
93051
  };
93042
93052
  if (matchToken("str")) element.label = advance().text;
93043
- if (matchToken("lparen")) {
93044
- advance();
93045
- if (matchToken("id")) {
93046
- const token = advance();
93047
- element.attr = {
93048
- value: token.text,
93049
- span: token.span
93050
- };
93051
- } else
93052
- reportError(
93053
- "attribute value expected after `(`",
93054
- lookAhead().span,
93055
- 'e.g. `trust-zone DMZ "DMZ" (public)`'
93056
- );
93057
- if (matchToken("rparen")) advance();
93058
- else
93059
- reportError(
93060
- "`)` expected to close the attribute",
93061
- lookAhead().span,
93062
- 'e.g. `trust-zone DMZ "DMZ" (public)`'
93063
- );
93064
- }
93065
93053
  if (matchToken("lbrace")) {
93066
93054
  advance();
93067
93055
  p.parseElementBody(element);
93068
93056
  }
93069
93057
  (parent ? parent.children : model.elements).push(element);
93070
93058
  }
93071
- function splitEndpoint(token, flow, role) {
93059
+ function splitEndpoint(token, flow, end) {
93072
93060
  const dot = token.text.lastIndexOf(".");
93073
93061
  if (dot <= 0 || dot === token.text.length - 1) return null;
93074
93062
  const base = token.text.slice(0, dot);
93075
- const side = token.text.slice(dot + 1);
93063
+ const suffix = token.text.slice(dot + 1);
93076
93064
  return {
93077
93065
  flow,
93078
- role,
93066
+ end,
93079
93067
  raw: token.text,
93080
93068
  rawSpan: token.span,
93081
93069
  base,
93082
93070
  baseSpan: { line: token.span.line, col: token.span.col, len: base.length },
93083
- side,
93084
- sideSpan: { line: token.span.line, col: token.span.col + dot + 1, len: side.length }
93071
+ suffix,
93072
+ suffixSpan: { line: token.span.line, col: token.span.col + dot + 1, len: suffix.length }
93085
93073
  };
93086
93074
  }
93087
- function resolveAttachSides(splits, model, diagnostics) {
93075
+ function resolveEndpointSuffixes(splits, model, diagnostics) {
93088
93076
  for (const split of splits) {
93077
+ const isSide = ATTACH_SIDES.includes(split.suffix);
93078
+ const isRole = ATTACH_ROLES.includes(split.suffix);
93089
93079
  if (model.index.has(split.raw)) {
93090
- if (ATTACH_SIDES.includes(split.side))
93080
+ if (isSide || isRole)
93091
93081
  diagnostics.push({
93092
93082
  code: "W0571",
93093
93083
  severity: "warning",
93094
- message: `\`${split.raw}\` is a declared element, so \`.${split.side}\` is not read as an attachment side`,
93084
+ message: `\`${split.raw}\` is a declared element, so \`.${split.suffix}\` is not read as ${isSide ? "an attachment side" : "a role"}`,
93095
93085
  span: split.rawSpan,
93096
93086
  note: "a declared id always wins over the `ID.side` reading",
93097
- help: `rename the element if you meant to attach the flow to the ${split.side} side of \`${split.base}\``
93087
+ help: isSide ? `rename the element if you meant to attach the flow to the ${split.suffix} side of \`${split.base}\`` : `rename the element if you meant \`${split.base}\` to be the ${split.suffix} of the queue at the other end`
93098
93088
  });
93099
93089
  continue;
93100
93090
  }
93101
93091
  if (!model.index.has(split.base)) continue;
93102
- const known = ATTACH_SIDES.includes(split.side);
93103
- if (!known)
93092
+ if (!isSide && !isRole)
93104
93093
  diagnostics.push({
93105
93094
  code: "E0223",
93106
93095
  severity: "error",
93107
- message: `unknown attachment side \`${split.side}\``,
93108
- span: split.sideSpan,
93109
- note: "sides are named as the diagram is read",
93110
- help: "use `left`, `right`, `top` or `bottom`, e.g. `APP.right -> DB.left`"
93096
+ message: `unknown endpoint suffix \`${split.suffix}\``,
93097
+ span: split.suffixSpan,
93098
+ note: "an endpoint names either the side it attaches to, as the diagram is read, or its role towards a queue",
93099
+ help: "use `left`, `right`, `top` or `bottom` \u2014 `APP.right -> DB.left` \u2014 or `producer` / `consumer`, e.g. `CAPTURE.producer -> EVENTS`"
93111
93100
  });
93112
- const side = known ? { value: split.side, span: split.sideSpan } : void 0;
93113
- if (split.role === "from") {
93101
+ const side = isSide ? { value: split.suffix, span: split.suffixSpan } : void 0;
93102
+ const role = isRole ? { value: split.suffix, span: split.suffixSpan } : void 0;
93103
+ if (split.end === "from") {
93114
93104
  split.flow.from = split.base;
93115
93105
  split.flow.fromSpan = split.baseSpan;
93116
93106
  split.flow.fromSide = side;
93107
+ split.flow.fromRole = role;
93117
93108
  } else {
93118
93109
  split.flow.to = split.base;
93119
93110
  split.flow.toSpan = split.baseSpan;
93120
93111
  split.flow.toSide = side;
93112
+ split.flow.toRole = role;
93121
93113
  }
93122
93114
  }
93123
93115
  }
@@ -93146,6 +93138,34 @@ function tryOrderEntry(p, parent) {
93146
93138
  parent.order = { value, span: valueToken.span };
93147
93139
  return true;
93148
93140
  }
93141
+ function tryLogoEntry(p, parent) {
93142
+ const { matchToken, advance, reportCoded, save, restore, syncToNextLine } = p;
93143
+ if (!parent || !matchToken("id", "logo")) return false;
93144
+ const mark = save();
93145
+ const keyToken = advance();
93146
+ if (!matchToken("colon")) {
93147
+ restore(mark);
93148
+ return false;
93149
+ }
93150
+ advance();
93151
+ const valueToken = matchToken("id") || matchToken("str") ? advance() : null;
93152
+ if (!valueToken || valueToken.text.length === 0) {
93153
+ reportCoded(
93154
+ "E0105",
93155
+ "`logo` expects a built-in name or a quoted file path",
93156
+ (valueToken ?? keyToken).span,
93157
+ 'e.g. `logo: react`, or `logo: "./logos/acme.svg"` for your own'
93158
+ );
93159
+ syncToNextLine();
93160
+ return true;
93161
+ }
93162
+ parent.logo = {
93163
+ value: valueToken.text,
93164
+ source: valueToken.kind === "str" ? "file" : "builtin",
93165
+ span: valueToken.span
93166
+ };
93167
+ return true;
93168
+ }
93149
93169
  function tryLegendBlock(p) {
93150
93170
  const { matchToken, advance, reportError, lookAhead, skipNewlines, syncToNextLine, model } = p;
93151
93171
  if (!matchToken("id", "legend")) return false;
@@ -93353,6 +93373,7 @@ function parse(src) {
93353
93373
  }
93354
93374
  if (tryStyleBlock(parent)) return;
93355
93375
  if (tryOrderEntry(parser, parent)) return;
93376
+ if (tryLogoEntry(parser, parent)) return;
93356
93377
  if (!parent && tryLegendBlock(parser)) return;
93357
93378
  if (!parent && tryBusinessObject(parser)) return;
93358
93379
  parseFlowOrElement(parser, parent);
@@ -93365,7 +93386,7 @@ function parse(src) {
93365
93386
  for (const [id, element] of indexElementsById(model.elements)) {
93366
93387
  if (!model.index.has(id)) model.index.set(id, element);
93367
93388
  }
93368
- resolveAttachSides(endpointSplits, model, diagnostics);
93389
+ resolveEndpointSuffixes(endpointSplits, model, diagnostics);
93369
93390
  return { model, diags: diagnostics };
93370
93391
  }
93371
93392
  var LINE_STYLES = /* @__PURE__ */ new Set(["solid", "dashed", "dotted"]);
@@ -93587,8 +93608,10 @@ function applyStyleEntry(entry) {
93587
93608
  }
93588
93609
 
93589
93610
  // src/views.ts
93611
+ var LINE_STYLES2 = ["solid", "dashed", "dotted"];
93590
93612
  var logicalView = {
93591
93613
  name: "logical",
93614
+ laneKinds: ["external"],
93592
93615
  kinds: ["actor-group", "actor", "system", "layer", "block", "external"],
93593
93616
  containerKinds: ["actor-group", "system", "layer", "external"],
93594
93617
  // Logical flows carry no technical detail (flowTech* are null below), so the
@@ -93617,6 +93640,16 @@ var logicalView = {
93617
93640
  },
93618
93641
  legendFlowLabel: "Functional flow (label = exchanged data)",
93619
93642
  legendFlowLabelFr: "Flux fonctionnel (libell\xE9 = donn\xE9es \xE9chang\xE9es)",
93643
+ legendLineStyles: {
93644
+ solid: "Direct exchange",
93645
+ dashed: "Asynchronous or event-driven exchange",
93646
+ dotted: "Dependency \u2014 no data exchanged"
93647
+ },
93648
+ legendLineStylesFr: {
93649
+ solid: "\xC9change direct",
93650
+ dashed: "\xC9change asynchrone ou \xE9v\xE9nementiel",
93651
+ dotted: "D\xE9pendance \u2014 aucune donn\xE9e \xE9chang\xE9e"
93652
+ },
93620
93653
  flowTechRequired: null,
93621
93654
  flowTechRecommended: null,
93622
93655
  businessObjects: true,
@@ -93711,17 +93744,33 @@ var logicalView = {
93711
93744
  };
93712
93745
  var applicationView = {
93713
93746
  name: "application",
93747
+ laneKinds: ["external"],
93748
+ hubKinds: ["queue"],
93714
93749
  kinds: [
93715
93750
  "actor-group",
93716
93751
  "actor",
93717
93752
  "system",
93718
93753
  "application",
93719
93754
  "module",
93755
+ "gateway",
93756
+ "auth",
93757
+ "idp",
93720
93758
  "queue",
93721
93759
  "datastore",
93722
93760
  "external"
93723
93761
  ],
93724
93762
  containerKinds: ["actor-group", "system", "application", "external"],
93763
+ // A logo says what a thing is built with, so it belongs to the kinds that run
93764
+ // code. `actor` and `actor-group` are people; `system` is a grouping whose
93765
+ // children carry their own stacks. `gateway`, `auth` and `idp` are left out
93766
+ // on purpose: they are drawn with a corner glyph, and the glyph renderer has
93767
+ // no logo slot — a logo on them would reserve width and draw nothing.
93768
+ logoKinds: ["application", "module", "queue", "datastore", "external"],
93769
+ // The same three kinds as the infrastructure view, drawn the same way: an API
93770
+ // gateway, an auth middleware and an identity provider are containers in
93771
+ // their own right at the application level, and the glyph is what tells them
93772
+ // apart from a plain `application` at a glance.
93773
+ glyphKinds: ["gateway", "auth", "idp"],
93725
93774
  // C4-style `(API_REST, JSON)`: the protocol half is worth tabulating, the port is not.
93726
93775
  // `zoneOf` walks ancestors nearest-first, so an endpoint inside an application
93727
93776
  // still reads `Name (App)`; the system only annotates what sits directly in it.
@@ -93733,6 +93782,9 @@ var applicationView = {
93733
93782
  "actor-group": 0,
93734
93783
  system: 1,
93735
93784
  application: 1,
93785
+ gateway: 1,
93786
+ auth: 1,
93787
+ idp: 1,
93736
93788
  queue: 1,
93737
93789
  datastore: 1,
93738
93790
  external: 2
@@ -93743,6 +93795,9 @@ var applicationView = {
93743
93795
  system: "System",
93744
93796
  application: "Application",
93745
93797
  module: "Application module",
93798
+ gateway: "Gateway / reverse proxy",
93799
+ auth: "Auth middleware",
93800
+ idp: "Identity provider (IdP)",
93746
93801
  queue: "Message queue / broker",
93747
93802
  datastore: "Datastore / registry",
93748
93803
  external: "External system"
@@ -93753,6 +93808,9 @@ var applicationView = {
93753
93808
  system: "Syst\xE8me",
93754
93809
  application: "Application",
93755
93810
  module: "Module applicatif",
93811
+ gateway: "Passerelle / proxy",
93812
+ auth: "Middleware d'authentification",
93813
+ idp: "Fournisseur d'identit\xE9 (IdP)",
93756
93814
  queue: "File de messages / broker",
93757
93815
  datastore: "Entrep\xF4t / r\xE9f\xE9rentiel",
93758
93816
  external: "Syst\xE8me externe"
@@ -93764,6 +93822,16 @@ var applicationView = {
93764
93822
  },
93765
93823
  legendFlowLabel: "Application flow \u2014 (protocol, format) under the label",
93766
93824
  legendFlowLabelFr: "Flux applicatif \u2014 (protocole, format) sous le libell\xE9",
93825
+ legendLineStyles: {
93826
+ solid: "Synchronous call (request / response)",
93827
+ dashed: "Asynchronous exchange (message, event)",
93828
+ dotted: "Dependency \u2014 no direct call"
93829
+ },
93830
+ legendLineStylesFr: {
93831
+ solid: "Appel synchrone (requ\xEAte / r\xE9ponse)",
93832
+ dashed: "\xC9change asynchrone (message, \xE9v\xE9nement)",
93833
+ dotted: "D\xE9pendance \u2014 sans appel direct"
93834
+ },
93767
93835
  flowLabelRequired: null,
93768
93836
  flowTechRequired: null,
93769
93837
  flowTechRecommended: {
@@ -93790,7 +93858,7 @@ var applicationView = {
93790
93858
  minCounts: [],
93791
93859
  isolatedWarn: {
93792
93860
  code: "W0510",
93793
- kinds: ["module", "queue", "datastore"],
93861
+ kinds: ["module", "gateway", "auth", "idp", "queue", "datastore"],
93794
93862
  message: "isolated element: no incoming or outgoing flow"
93795
93863
  },
93796
93864
  defaults: {
@@ -93812,6 +93880,20 @@ var applicationView = {
93812
93880
  fill: "#ffffff",
93813
93881
  stroke: { color: "#5b7a99", style: "solid", width: 1.3 }
93814
93882
  },
93883
+ // Same colours as the infrastructure view's `gateway`, `auth` and `idp`: one
93884
+ // thing drawn one way, whichever view it appears in.
93885
+ gateway: {
93886
+ fill: "#f5e6dd",
93887
+ stroke: { color: "#bf5530", style: "solid", width: 1.6 }
93888
+ },
93889
+ auth: {
93890
+ fill: "#e8f1fb",
93891
+ stroke: { color: "#2f6fb5", style: "solid", width: 1.5 }
93892
+ },
93893
+ idp: {
93894
+ fill: "#e0f0f0",
93895
+ stroke: { color: "#3a8f8f", style: "solid", width: 1.3 }
93896
+ },
93815
93897
  queue: {
93816
93898
  fill: "#f3eef8",
93817
93899
  stroke: { color: "#8a6fae", style: "solid", width: 1.3 }
@@ -93843,6 +93925,18 @@ var applicationView = {
93843
93925
  fill: "#252a31",
93844
93926
  stroke: { color: "#5f7f9e", style: "solid", width: 1.3 }
93845
93927
  },
93928
+ gateway: {
93929
+ fill: "#332218",
93930
+ stroke: { color: "#c96a4a", style: "solid", width: 1.6 }
93931
+ },
93932
+ auth: {
93933
+ fill: "#1d2735",
93934
+ stroke: { color: "#6fa8e0", style: "solid", width: 1.5 }
93935
+ },
93936
+ idp: {
93937
+ fill: "#1a2e2e",
93938
+ stroke: { color: "#4fafaf", style: "solid", width: 1.3 }
93939
+ },
93846
93940
  queue: {
93847
93941
  fill: "#2a2433",
93848
93942
  stroke: { color: "#7a5f9e", style: "solid", width: 1.3 }
@@ -93860,19 +93954,27 @@ var applicationView = {
93860
93954
  };
93861
93955
  var infrastructureView = {
93862
93956
  name: "infrastructure",
93957
+ laneKinds: ["external"],
93958
+ hubKinds: ["queue"],
93863
93959
  kinds: [
93864
93960
  "actor",
93961
+ "device",
93865
93962
  "site",
93866
93963
  "network-zone",
93867
93964
  "server",
93868
93965
  "app-instance",
93869
93966
  "queue",
93870
93967
  "gateway",
93968
+ "firewall",
93871
93969
  "auth",
93872
93970
  "idp",
93873
93971
  "external"
93874
93972
  ],
93875
93973
  containerKinds: ["site", "network-zone", "server"],
93974
+ glyphKinds: ["gateway", "firewall", "auth", "idp", "device"],
93975
+ // A `device` is a flow origin, so it belongs on the entry side with the
93976
+ // actors rather than in a declaration-order band with the zones.
93977
+ ingressKinds: ["actor", "actor-group", "device"],
93876
93978
  // The reference shape: the matrice des flux techniques as an EA dossier expects it.
93877
93979
  matrix: {
93878
93980
  zoneKinds: ["network-zone", "site"],
@@ -93883,24 +93985,28 @@ var infrastructureView = {
93883
93985
  actorLegend: true,
93884
93986
  legendNames: {
93885
93987
  actor: "User / consumer",
93988
+ device: "Device / workstation",
93886
93989
  site: "Site / data center",
93887
93990
  "network-zone": "Network zone",
93888
93991
  server: "Server / VM",
93889
93992
  "app-instance": "Deployed application",
93890
93993
  queue: "Message queue / broker",
93891
93994
  gateway: "Gateway / reverse proxy",
93995
+ firewall: "Firewall",
93892
93996
  auth: "Auth middleware",
93893
93997
  idp: "Identity provider (IdP)",
93894
93998
  external: "External system"
93895
93999
  },
93896
94000
  legendNamesFr: {
93897
94001
  actor: "Utilisateur / consommateur",
94002
+ device: "Poste de travail",
93898
94003
  site: "Site / centre de donn\xE9es",
93899
94004
  "network-zone": "Zone r\xE9seau",
93900
94005
  server: "Serveur / VM",
93901
94006
  "app-instance": "Application d\xE9ploy\xE9e",
93902
94007
  queue: "File de messages / broker",
93903
94008
  gateway: "Passerelle / proxy",
94009
+ firewall: "Pare-feu",
93904
94010
  auth: "Middleware d'authentification",
93905
94011
  idp: "Fournisseur d'identit\xE9 (IdP)",
93906
94012
  external: "Syst\xE8me externe"
@@ -93912,6 +94018,16 @@ var infrastructureView = {
93912
94018
  },
93913
94019
  legendFlowLabel: "Technical flow (protocol, port)",
93914
94020
  legendFlowLabelFr: "Flux technique (protocole, port)",
94021
+ legendLineStyles: {
94022
+ solid: "Permanent link \u2014 nominal traffic",
94023
+ dashed: "Asynchronous or intermittent link",
94024
+ dotted: "Dependency \u2014 outside nominal traffic"
94025
+ },
94026
+ legendLineStylesFr: {
94027
+ solid: "Lien permanent \u2014 trafic nominal",
94028
+ dashed: "Lien asynchrone ou intermittent",
94029
+ dotted: "D\xE9pendance \u2014 hors trafic nominal"
94030
+ },
93915
94031
  flowLabelRequired: null,
93916
94032
  flowTechRecommended: null,
93917
94033
  flowTechRequired: {
@@ -93945,7 +94061,7 @@ var infrastructureView = {
93945
94061
  minCounts: [],
93946
94062
  isolatedWarn: {
93947
94063
  code: "W0510",
93948
- kinds: ["app-instance", "queue", "gateway", "auth", "idp"],
94064
+ kinds: ["app-instance", "queue", "gateway", "firewall", "auth", "idp", "device"],
93949
94065
  message: "isolated element: no incoming or outgoing flow"
93950
94066
  },
93951
94067
  defaults: {
@@ -93962,6 +94078,12 @@ var infrastructureView = {
93962
94078
  fill: "#ffffff",
93963
94079
  stroke: { color: "#55606b", style: "solid", width: 1.5 }
93964
94080
  },
94081
+ // The server's grey: a workstation is hardware like a server is, and the
94082
+ // monitor glyph is what tells the two apart.
94083
+ device: {
94084
+ fill: "#eef0f2",
94085
+ stroke: { color: "#55606b", style: "solid", width: 1.3 }
94086
+ },
93965
94087
  "app-instance": {
93966
94088
  fill: "#fff7e6",
93967
94089
  stroke: { color: "#b08d2a", style: "solid", width: 1.2 }
@@ -93974,9 +94096,13 @@ var infrastructureView = {
93974
94096
  fill: "#f5e6dd",
93975
94097
  stroke: { color: "#bf5530", style: "solid", width: 1.6 }
93976
94098
  },
94099
+ firewall: {
94100
+ fill: "#fdecea",
94101
+ stroke: { color: "#c0392b", style: "solid", width: 1.6 }
94102
+ },
93977
94103
  auth: {
93978
- fill: "#fef3e2",
93979
- stroke: { color: "#d68a2a", style: "solid", width: 1.5 }
94104
+ fill: "#e8f1fb",
94105
+ stroke: { color: "#2f6fb5", style: "solid", width: 1.5 }
93980
94106
  },
93981
94107
  idp: {
93982
94108
  fill: "#e0f0f0",
@@ -94001,6 +94127,10 @@ var infrastructureView = {
94001
94127
  fill: "#252a31",
94002
94128
  stroke: { color: "#6b7885", style: "solid", width: 1.5 }
94003
94129
  },
94130
+ device: {
94131
+ fill: "#242a30",
94132
+ stroke: { color: "#6b7885", style: "solid", width: 1.3 }
94133
+ },
94004
94134
  "app-instance": {
94005
94135
  fill: "#2e2717",
94006
94136
  stroke: { color: "#b08d2a", style: "solid", width: 1.2 }
@@ -94013,9 +94143,13 @@ var infrastructureView = {
94013
94143
  fill: "#332218",
94014
94144
  stroke: { color: "#c96a4a", style: "solid", width: 1.6 }
94015
94145
  },
94146
+ firewall: {
94147
+ fill: "#33211f",
94148
+ stroke: { color: "#e5675a", style: "solid", width: 1.6 }
94149
+ },
94016
94150
  auth: {
94017
- fill: "#332614",
94018
- stroke: { color: "#b88a30", style: "solid", width: 1.5 }
94151
+ fill: "#1d2735",
94152
+ stroke: { color: "#6fa8e0", style: "solid", width: 1.5 }
94019
94153
  },
94020
94154
  idp: {
94021
94155
  fill: "#1a2e2e",
@@ -94027,186 +94161,180 @@ var infrastructureView = {
94027
94161
  }
94028
94162
  }
94029
94163
  };
94030
- var SEC_LEVELS = ["public", "internal", "restricted", "secret"];
94031
- var securityView = {
94032
- name: "security",
94033
- kinds: ["trust-zone", "security-node", "asset", "actor-group", "actor", "external"],
94034
- containerKinds: ["trust-zone", "actor-group"],
94035
- // Security flows state their encryption (W0561) but no port; the trust zone
94036
- // an endpoint sits in is the point of the view, so it annotates every row.
94037
- matrix: {
94038
- zoneKinds: ["trust-zone"],
94039
- columns: ["num", "source", "dest", "proto", "nature"]
94164
+ var views = {
94165
+ logical: logicalView,
94166
+ application: applicationView,
94167
+ infrastructure: infrastructureView
94168
+ };
94169
+
94170
+ // src/logos.ts
94171
+ var LOGOS = {
94172
+ angular: {
94173
+ title: "Angular",
94174
+ d: "M16.712 17.711H7.288l-1.204 2.916L12 24l5.916-3.373-1.204-2.916ZM14.692 0l7.832 16.855.814-12.856L14.692 0ZM9.308 0 .662 3.999l.814 12.856L9.308 0Zm-.405 13.93h6.198L12 6.396 8.903 13.93Z",
94175
+ license: "CC-BY-4.0",
94176
+ licenseUrl: "https://creativecommons.org/licenses/by/4.0/legalcode",
94177
+ source: "https://angular.dev/press-kit"
94040
94178
  },
94041
- legendNames: {
94042
- "trust-zone": "Trust zone (sensitivity)",
94043
- "security-node": "Filtering / security node",
94044
- asset: "Sensitive asset",
94045
- "actor-group": "Actor group",
94046
- actor: "Actor",
94047
- external: "Untrusted external"
94179
+ apache: {
94180
+ title: "Apache",
94181
+ d: "M17.805 2.197v.066h.156v.44h.072v-.44h.156v-.066zm.9 0l-.175.353-.172-.353h-.087v.506h.067V2.3l.172.35h.045l.172-.35v.404h.066v-.506zm-4.257 1c-.204.31-.424.66-.66 1.06l-.04.062a44.457 44.457 0 00-1.265 2.29c-.187.36-.38.742-.577 1.146l2.267-.25c.66-.302.955-.578 1.242-.976a15.5 15.5 0 00.23-.342c.23-.363.46-.763.663-1.16.197-.386.37-.767.505-1.11.083-.22.15-.422.198-.6.042-.158.074-.307.1-.45-.884.15-1.965.295-2.668.33zM11.894 7.78l-.077.16c-.078.16-.157.32-.236.488-.086.18-.172.364-.26.552l-.132.287a75.265 75.265 0 00-1.427 3.3c-.163.397-.327.807-.493 1.23-.15.38-.297.765-.45 1.164l-.02.06c-.15.396-.3.802-.453 1.22l-.01.027.72-.08a.213.213 0 01-.042-.006c.863-.106 2.01-.75 2.75-1.547.342-.367.652-.8.94-1.306.213-.377.413-.795.604-1.258.168-.405.328-.843.48-1.318-.196.105-.423.18-.673.235a2.184 2.184 0 01-.273.046c.806-.31 1.314-.905 1.683-1.64a2.816 2.816 0 01-.968.428c-.06.012-.116.022-.174.03l-.043.006h.002c.278-.118.514-.248.718-.403a2.571 2.571 0 00.637-.698l.063-.104.077-.154a8.107 8.107 0 00.367-.85l.03-.088a3.04 3.04 0 00.123-.463.733.733 0 01-.094.065c-.243.145-.66.277-.996.34l.663-.074-.664.073h-.017l-.1.017c.006-.003.01-.006.017-.008l-2.265.25-.013.022zM8.27 16.45c-.117.323-.236.654-.355.992l-.005.015c-.016.046-.032.094-.05.142-.08.227-.15.432-.31.9.264.12.475.435.675.793a1.44 1.44 0 00-.466-.99c1.293.06 2.41-.27 2.99-1.217.05-.084.096-.173.14-.268-.26.333-.59.474-1.2.44 0 0-.004 0-.005.002l.004-.002c.9-.404 1.354-.79 1.754-1.433.094-.153.186-.32.28-.503-.788.81-1.702 1.04-2.664.865l-.72.078a6.43 6.43 0 00-.067.183zM15.42.112c-.376.222-1 .85-1.748 1.763l.686 1.294c.48-.687.97-1.307 1.462-1.836l.058-.062c-.02.02-.04.04-.057.062-.16.176-.644.74-1.375 1.863.703-.035 1.784-.18 2.666-.33.262-1.47-.258-2.142-.258-2.142s-.66-1.07-1.436-.61zm-3.084 6.402a40.253 40.253 0 011.306-2.26l.04-.064c.224-.352.45-.693.677-1.02l-.685-1.293-.157.192c-.197.245-.403.51-.613.79a39.853 39.853 0 00-2.016 2.97l-.022.038.893 1.763c.19-.378.38-.752.575-1.118zm-3.73 8.32c.158-.406.319-.81.483-1.225.156-.394.32-.79.484-1.19a91.133 91.133 0 011.6-3.604l.205-.424c.12-.243.237-.485.36-.724a.125.125 0 01.02-.04l-.895-1.763-.044.07c-.207.34-.414.687-.617 1.042a38.056 38.056 0 00-1.092 2.04l-.094.193a24.573 24.573 0 00-1.258 3.087 18.492 18.492 0 00-.52 1.997l.896 1.77c.117-.317.24-.638.364-.963zm-1.376-.476a13.38 13.38 0 00-.234 1.692c0 .02-.004.04-.005.06-.28-.45-1.03-.888-1.026-.884.537.778.944 1.55 1.005 2.31-.29.058-.684-.027-1.14-.195.475.436.83.556.97.588-.434.03-.89.328-1.346.67.668-.27 1.21-.38 1.596-.29-.61 1.74-1.23 3.655-1.843 5.69a.538.538 0 00.364-.354c.11-.368.84-2.786 1.978-5.965l.097-.27.028-.078c.12-.332.246-.672.374-1.02l.09-.237v-.004L7.24 14.3c-.003.02-.01.04-.012.06z",
94182
+ license: "Apache-2.0",
94183
+ licenseUrl: "https://www.apache.org/licenses/LICENSE-2.0.txt",
94184
+ source: "https://www.apache.org/foundation/press/kit"
94048
94185
  },
94049
- legendNamesFr: {
94050
- "trust-zone": "Zone de confiance (sensibilit\xE9)",
94051
- "security-node": "N\u0153ud de filtrage / s\xE9curit\xE9",
94052
- asset: "Actif sensible",
94053
- "actor-group": "Groupe d'acteurs",
94054
- actor: "Acteur",
94055
- external: "Externe non ma\xEEtris\xE9"
94186
+ apachekafka: {
94187
+ title: "Apache Kafka",
94188
+ d: "M9.71 2.136a1.43 1.43 0 0 0-2.047 0h-.007a1.48 1.48 0 0 0-.421 1.042c0 .41.161.777.422 1.039l.007.007c.257.264.616.426 1.019.426.404 0 .766-.162 1.027-.426l.003-.007c.261-.262.421-.629.421-1.039 0-.408-.159-.777-.421-1.042H9.71zM8.683 22.295c.404 0 .766-.167 1.027-.429l.003-.008c.261-.261.421-.631.421-1.036 0-.41-.159-.778-.421-1.044H9.71a1.42 1.42 0 0 0-1.027-.432 1.4 1.4 0 0 0-1.02.432h-.007c-.26.266-.422.634-.422 1.044 0 .406.161.775.422 1.036l.007.008c.258.262.617.429 1.02.429zm7.89-4.462c.359-.096.683-.33.882-.684l.027-.052a1.47 1.47 0 0 0 .114-1.067 1.454 1.454 0 0 0-.675-.896l-.021-.014a1.425 1.425 0 0 0-1.078-.132c-.36.091-.684.335-.881.686-.2.349-.241.75-.146 1.119.099.363.33.691.675.896h.002c.346.203.737.239 1.101.144zm-6.405-7.342a2.083 2.083 0 0 0-1.485-.627c-.58 0-1.103.242-1.482.627-.378.385-.612.916-.612 1.507s.233 1.124.612 1.514a2.08 2.08 0 0 0 2.967 0c.379-.39.612-.923.612-1.514s-.233-1.122-.612-1.507zm-.835-2.51c.843.141 1.6.552 2.178 1.144h.004c.092.093.182.196.265.299l1.446-.851a3.176 3.176 0 0 1-.047-1.808 3.149 3.149 0 0 1 1.456-1.926l.025-.016a3.062 3.062 0 0 1 2.345-.306c.77.21 1.465.721 1.898 1.482v.002c.431.757.518 1.626.313 2.408a3.145 3.145 0 0 1-1.456 1.928l-.198.118h-.02a3.095 3.095 0 0 1-2.154.201 3.127 3.127 0 0 1-1.514-.944l-1.444.848a4.162 4.162 0 0 1 0 2.879l1.444.846c.413-.47.939-.789 1.514-.944a3.041 3.041 0 0 1 2.371.319l.048.023v.002a3.17 3.17 0 0 1 1.408 1.906 3.215 3.215 0 0 1-.313 2.405l-.026.053-.003-.005a3.147 3.147 0 0 1-1.867 1.436 3.096 3.096 0 0 1-2.371-.318v-.006a3.156 3.156 0 0 1-1.456-1.927 3.175 3.175 0 0 1 .047-1.805l-1.446-.848a3.905 3.905 0 0 1-.265.294l-.004.005a3.938 3.938 0 0 1-2.178 1.138v1.699a3.09 3.09 0 0 1 1.56.862l.002.004c.565.572.914 1.368.914 2.243 0 .873-.35 1.664-.914 2.239l-.002.009a3.1 3.1 0 0 1-2.21.931 3.1 3.1 0 0 1-2.206-.93h-.002v-.009a3.186 3.186 0 0 1-.916-2.239c0-.875.35-1.672.916-2.243v-.004h.002a3.1 3.1 0 0 1 1.558-.862v-1.699a3.926 3.926 0 0 1-2.176-1.138l-.006-.005a4.098 4.098 0 0 1-1.173-2.874c0-1.122.452-2.136 1.173-2.872h.006a3.947 3.947 0 0 1 2.176-1.144V6.289a3.137 3.137 0 0 1-1.558-.864h-.002v-.004a3.192 3.192 0 0 1-.916-2.243c0-.871.35-1.669.916-2.243l.002-.002A3.084 3.084 0 0 1 8.683 0c.861 0 1.641.355 2.21.932v.002h.002c.565.574.914 1.372.914 2.243 0 .876-.35 1.667-.914 2.243l-.002.005a3.142 3.142 0 0 1-1.56.864v1.692zm8.121-1.129l-.012-.019a1.452 1.452 0 0 0-.87-.668 1.43 1.43 0 0 0-1.103.146h.002c-.347.2-.58.529-.677.896-.095.365-.054.768.146 1.119l.007.009c.2.347.519.579.874.673.357.103.755.059 1.098-.144l.019-.009a1.47 1.47 0 0 0 .657-.885 1.493 1.493 0 0 0-.141-1.118",
94189
+ license: "Apache-2.0",
94190
+ licenseUrl: "https://www.apache.org/licenses/LICENSE-2.0.txt",
94191
+ source: "https://apache.org/logos"
94056
94192
  },
94057
- bandTitles: {
94058
- flows: "FLOWS",
94059
- objects: "BUSINESS OBJECTS",
94060
- legend: "LEGEND"
94193
+ apachespark: {
94194
+ title: "Apache Spark",
94195
+ d: "M10.812 0c-.425.013-.845.215-1.196.605a3.593 3.593 0 00-.493.722c-.355.667-.425 1.415-.556 2.143a551.9 551.9 0 00-.726 4.087c-.027.16-.096.227-.244.273C5.83 8.386 4.06 8.94 2.3 9.514c-.387.125-.773.289-1.114.506-1.042.665-1.196 1.753-.415 2.71.346.422.79.715 1.284.936 1.1.49 2.202.976 3.3 1.47.019.01.036.013.053.019h-.004l1.306.535c0 .023.002.045 0 .073-.2 2.03-.39 4.063-.58 6.095-.04.419-.012.831.134 1.23.317.87 1.065 1.148 1.881.701.372-.204.666-.497.937-.818 1.372-1.623 2.746-3.244 4.113-4.872.111-.133.205-.15.363-.098.349.117.697.231 1.045.347h.001c.02.012.045.02.073.03l.142.042c1.248.416 2.68.775 3.929 1.19.4.132.622.164 1.045.098.311-.048.592-.062.828-.236.602-.33.995-.957.988-1.682-.005-.427-.154-.813-.35-1.186-.82-1.556-1.637-3.113-2.461-4.666-.078-.148-.076-.243.037-.375 1.381-1.615 2.756-3.236 4.133-4.855.272-.32.513-.658.653-1.058.308-.878-.09-1.57-1-1.741a2.783 2.783 0 00-1.235.069c-1.974.521-3.947 1.041-5.918 1.57-.175.047-.26.015-.355-.144a353.08 353.08 0 00-2.421-4.018 4.61 4.61 0 00-.652-.849c-.371-.37-.802-.549-1.227-.536zm.172 3.703a.592.592 0 01.189.211c.87 1.446 1.742 2.89 2.609 4.338.07.118.135.16.277.121 1.525-.41 3.052-.813 4.579-1.217.367-.098.735-.193 1.103-.289a.399.399 0 01-.1.2c-1.259 1.48-2.516 2.962-3.779 4.438-.11.13-.12.22-.04.37.937 1.803 1.768 3.309 2.498 4.76l-3.696-1.019c-.538-.18-1.077-.358-1.615-.539-.163-.055-.25-.03-.36.1-1.248 1.488-2.504 2.97-3.759 4.454a.398.398 0 01-.18.132c.035-.378.068-.757.104-1.136.149-1.572.297-3.144.451-4.716-.03-.318.117-.405-.322-.545-1.493-.593-3.346-1.321-4.816-1.905a.595.595 0 01.24-.134c1.797-.57 3.595-1.14 5.394-1.705.127-.04.199-.092.211-.233.013-.148.05-.294.076-.441.241-1.363.483-2.726.726-4.088.068-.386.14-.771.21-1.157z",
94196
+ license: "Apache-2.0",
94197
+ licenseUrl: "https://www.apache.org/licenses/LICENSE-2.0.txt",
94198
+ source: "https://apache.org/logos"
94061
94199
  },
94062
- legendFlowLabel: "Security flow \u2014 cross-zone flows should be filtered and encrypted",
94063
- legendFlowLabelFr: "Flux de s\xE9curit\xE9 \u2014 les flux inter-zones doivent \xEAtre filtr\xE9s et chiffr\xE9s",
94064
- partitions: {},
94065
- partitionByOrder: true,
94066
- flowLabelRequired: {
94067
- code: "E0203",
94068
- message: "flow without a label",
94069
- help: 'add a label describing the exchange: `A -> B : "\u2026" (TLS1.3)`'
94200
+ django: {
94201
+ title: "Django",
94202
+ d: "M11.146 0h3.924v18.166c-2.013.382-3.491.535-5.096.535-4.791 0-7.288-2.166-7.288-6.32 0-4.002 2.65-6.6 6.753-6.6.637 0 1.121.05 1.707.203zm0 9.143a3.894 3.894 0 00-1.325-.204c-1.988 0-3.134 1.223-3.134 3.365 0 2.09 1.096 3.236 3.109 3.236.433 0 .79-.025 1.35-.102V9.142zM21.314 6.06v9.098c0 3.134-.229 4.638-.917 5.937-.637 1.249-1.478 2.039-3.211 2.905l-3.644-1.733c1.733-.815 2.574-1.53 3.109-2.625.561-1.121.739-2.421.739-5.835V6.059h3.924zM17.39.021h3.924v4.026H17.39z"
94070
94203
  },
94071
- flowTechRequired: null,
94072
- flowTechRecommended: null,
94073
- attrSpec: {
94074
- kind: "trust-zone",
94075
- values: SEC_LEVELS,
94076
- code: "E0250",
94077
- message: "trust zone without a valid sensitivity level",
94078
- help: 'set a level in parentheses: `trust-zone DMZ "DMZ" (public)` \u2014 one of public, internal, restricted, secret'
94204
+ docker: {
94205
+ title: "Docker",
94206
+ d: "M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z"
94079
94207
  },
94080
- trustOrder: { public: 0, internal: 1, restricted: 2, secret: 3 },
94081
- boundaryLint: {
94082
- code: "W0560",
94083
- nodeKind: "security-node",
94084
- message: "unfiltered trust-boundary crossing",
94085
- help: "route this flow through a `security-node` (firewall/WAF/bastion), or confirm the direct path is intended"
94208
+ dotnet: {
94209
+ title: ".NET",
94210
+ d: "M24 8.77h-2.468v7.565h-1.425V8.77h-2.462V7.53H24zm-6.852 7.565h-4.821V7.53h4.63v1.24h-3.205v2.494h2.953v1.234h-2.953v2.604h3.396zm-6.708 0H8.882L4.78 9.863a2.896 2.896 0 0 1-.258-.51h-.036c.032.189.048.592.048 1.21v5.772H3.157V7.53h1.659l3.965 6.32c.167.261.275.442.323.54h.024c-.04-.233-.06-.629-.06-1.185V7.529h1.372zm-8.703-.693a.868.829 0 0 1-.869.829.868.829 0 0 1-.868-.83.868.829 0 0 1 .868-.828.868.829 0 0 1 .869.829Z"
94086
94211
  },
94087
- crossZoneTechRecommended: {
94088
- code: "W0561",
94089
- message: "cross-zone flow without stated encryption/protocol",
94090
- help: 'add the protocol/encryption on inter-zone flows: `A -> B : "\u2026" (TLS1.3)`'
94212
+ elasticsearch: {
94213
+ title: "Elasticsearch",
94214
+ d: "M13.394 0C8.683 0 4.609 2.716 2.644 6.667h15.641a4.77 4.77 0 0 0 3.073-1.11c.446-.375.864-.785 1.247-1.243l.001-.002A11.974 11.974 0 0 0 13.394 0zM1.804 8.889a12.009 12.009 0 0 0 0 6.222h14.7a3.111 3.111 0 1 0 0-6.222zm.84 8.444C4.61 21.283 8.684 24 13.395 24c3.701 0 7.011-1.677 9.212-4.312l-.001-.002a9.958 9.958 0 0 0-1.247-1.243 4.77 4.77 0 0 0-3.073-1.11z"
94091
94215
  },
94092
- nesting: [
94093
- {
94094
- code: "E0217",
94095
- child: "asset",
94096
- parents: ["trust-zone"],
94097
- message: "sensitive asset outside any trust zone",
94098
- help: "move this `asset` inside a `trust-zone`"
94099
- },
94100
- {
94101
- code: "E0218",
94102
- child: "security-node",
94103
- parents: ["trust-zone"],
94104
- message: "security node outside any trust zone",
94105
- help: "place this `security-node` inside a `trust-zone` (typically the exposed one it protects)"
94106
- },
94107
- {
94108
- code: "E0211",
94109
- child: "actor",
94110
- parents: ["actor-group"],
94111
- message: "actor outside any group",
94112
- help: "move this `actor` inside an `actor-group`"
94113
- }
94114
- ],
94115
- minCounts: [],
94116
- isolatedWarn: {
94117
- code: "W0510",
94118
- kinds: ["asset"],
94119
- message: "isolated element: no incoming or outgoing flow"
94216
+ express: {
94217
+ title: "Express",
94218
+ d: "M12.262 16.666h1.146l6.975-9.325H19.22zm9.778 1.441v.004l-4.334-5.706-.557.74 4.873 6.682H.945V4.173h9.505l5.026 6.7.574-.772-4.374-5.928h.003l-.719-.945H0v17.544h24zM10.917 8.705a3.8 3.8 0 0 0-1.292-1.183q-.796-.45-1.916-.45c-.746 0-1.37.14-1.906.424a3.76 3.76 0 0 0-1.31 1.12 4.9 4.9 0 0 0-.75 1.581 7.17 7.17 0 0 0 0 3.696c.148.567.402 1.101.75 1.573a3.5 3.5 0 0 0 1.31 1.066q.803.39 1.906.389 1.77 0 2.739-.868.966-.867 1.328-2.457h-1.139q-.271 1.084-.977 1.734-.704.651-1.952.65-.812 0-1.392-.342a3.1 3.1 0 0 1-.957-.869 3.5 3.5 0 0 1-.551-1.182 5 5 0 0 1-.17-1.133 9 9 0 0 0-.015-.286 4.5 4.5 0 0 1 .015-.829c.047-.418.147-.83.296-1.223A3.7 3.7 0 0 1 5.54 9.05a2.9 2.9 0 0 1 .922-.742q.541-.28 1.246-.28c.47 0 .869.093 1.23.28q.541.281.922.742.379.461.587 1.057t.225 1.246H5.625l.004.957h6.182a7.3 7.3 0 0 0-.18-1.924 4.9 4.9 0 0 0-.715-1.68z"
94120
94219
  },
94121
- defaults: {
94122
- "trust-zone": {
94123
- fill: "#f5f5f4",
94124
- stroke: { color: "#8a8a85", style: "solid", width: 1.3 }
94125
- },
94126
- "security-node": {
94127
- fill: "#fff7e6",
94128
- stroke: { color: "#c46b2a", style: "solid", width: 1.6 }
94129
- },
94130
- asset: {
94131
- fill: "#ffffff",
94132
- stroke: { color: "#55606b", style: "solid", width: 1.3 }
94133
- },
94134
- "actor-group": {
94135
- fill: "#eef4fb",
94136
- stroke: { color: "#7a9cc4", style: "dashed", width: 1.2 }
94137
- },
94138
- external: {
94139
- fill: "#fdecea",
94140
- stroke: { color: "#d9534f", style: "dashed", width: 1.3 }
94141
- },
94142
- actor: {}
94220
+ fastapi: {
94221
+ title: "FastAPI",
94222
+ d: "M12 .0387C5.3729.0384.0003 5.3931 0 11.9988c-.001 6.6066 5.372 11.9628 12 11.9625 6.628.0003 12.001-5.3559 12-11.9625-.0003-6.6057-5.3729-11.9604-12-11.96m-.829 5.4153h7.55l-7.5805 5.3284h5.1828L5.279 18.5436q2.9466-6.5444 5.892-13.0896"
94143
94223
  },
94144
- defaultsDark: {
94145
- "trust-zone": {
94146
- fill: "#26261f",
94147
- stroke: { color: "#8a8a72", style: "solid", width: 1.3 }
94148
- },
94149
- "security-node": {
94150
- fill: "#2e2717",
94151
- stroke: { color: "#c46b2a", style: "solid", width: 1.6 }
94152
- },
94153
- asset: {
94154
- fill: "#252a31",
94155
- stroke: { color: "#6b7885", style: "solid", width: 1.3 }
94156
- },
94157
- "actor-group": {
94158
- fill: "#232a33",
94159
- stroke: { color: "#5c7fa8", style: "dashed", width: 1.2 }
94160
- },
94161
- external: {
94162
- fill: "#3a2422",
94163
- stroke: { color: "#c25a54", style: "dashed", width: 1.3 }
94164
- },
94165
- actor: {}
94224
+ github: {
94225
+ title: "GitHub",
94226
+ d: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
94166
94227
  },
94167
- levelDefaults: {
94168
- public: {
94169
- fill: "#fdecea",
94170
- stroke: { color: "#d9534f", style: "solid", width: 1.4 }
94171
- },
94172
- internal: {
94173
- fill: "#fff4e5",
94174
- stroke: { color: "#e0a458", style: "solid", width: 1.4 }
94175
- },
94176
- restricted: {
94177
- fill: "#e8f1f8",
94178
- stroke: { color: "#5b8db8", style: "solid", width: 1.4 }
94179
- },
94180
- secret: {
94181
- fill: "#ece8f5",
94182
- stroke: { color: "#7a5fae", style: "solid", width: 1.4 }
94183
- }
94228
+ gitlab: {
94229
+ title: "GitLab",
94230
+ d: "m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z"
94184
94231
  },
94185
- levelDefaultsDark: {
94186
- public: {
94187
- fill: "#3a2422",
94188
- stroke: { color: "#c25a54", style: "solid", width: 1.4 }
94189
- },
94190
- internal: {
94191
- fill: "#332a1c",
94192
- stroke: { color: "#c08a44", style: "solid", width: 1.4 }
94193
- },
94194
- restricted: {
94195
- fill: "#1f2a33",
94196
- stroke: { color: "#4a7ba6", style: "solid", width: 1.4 }
94197
- },
94198
- secret: {
94199
- fill: "#2a2433",
94200
- stroke: { color: "#7a5f9e", style: "solid", width: 1.4 }
94201
- }
94232
+ go: {
94233
+ title: "Go",
94234
+ d: "M1.811 10.231c-.047 0-.058-.023-.035-.059l.246-.315c.023-.035.081-.058.128-.058h4.172c.046 0 .058.035.035.07l-.199.303c-.023.036-.082.07-.117.07zM.047 11.306c-.047 0-.059-.023-.035-.058l.245-.316c.023-.035.082-.058.129-.058h5.328c.047 0 .07.035.058.07l-.093.28c-.012.047-.058.07-.105.07zm2.828 1.075c-.047 0-.059-.035-.035-.07l.163-.292c.023-.035.07-.07.117-.07h2.337c.047 0 .07.035.07.082l-.023.28c0 .047-.047.082-.082.082zm12.129-2.36c-.736.187-1.239.327-1.963.514-.176.046-.187.058-.34-.117-.174-.199-.303-.327-.548-.444-.737-.362-1.45-.257-2.115.175-.795.514-1.204 1.274-1.192 2.22.011.935.654 1.706 1.577 1.835.795.105 1.46-.175 1.987-.77.105-.13.198-.27.315-.434H10.47c-.245 0-.304-.152-.222-.35.152-.362.432-.97.596-1.274a.315.315 0 01.292-.187h4.253c-.023.316-.023.631-.07.947a4.983 4.983 0 01-.958 2.29c-.841 1.11-1.94 1.8-3.33 1.986-1.145.152-2.209-.07-3.143-.77-.865-.655-1.356-1.52-1.484-2.595-.152-1.274.222-2.419.993-3.424.83-1.086 1.928-1.776 3.272-2.02 1.098-.2 2.15-.07 3.096.571.62.41 1.063.97 1.356 1.648.07.105.023.164-.117.2m3.868 6.461c-1.064-.024-2.034-.328-2.852-1.029a3.665 3.665 0 01-1.262-2.255c-.21-1.32.152-2.489.947-3.529.853-1.122 1.881-1.706 3.272-1.95 1.192-.21 2.314-.095 3.33.595.923.63 1.496 1.484 1.648 2.605.198 1.578-.257 2.863-1.344 3.962-.771.783-1.718 1.273-2.805 1.495-.315.06-.63.07-.934.106zm2.78-4.72c-.011-.153-.011-.27-.034-.387-.21-1.157-1.274-1.81-2.384-1.554-1.087.245-1.788.935-2.045 2.033-.21.912.234 1.835 1.075 2.21.643.28 1.285.244 1.905-.07.923-.48 1.425-1.228 1.484-2.233z"
94235
+ },
94236
+ googlecloud: {
94237
+ title: "Google Cloud",
94238
+ d: "M12.19 2.38a9.344 9.344 0 0 0-9.234 6.893c.053-.02-.055.013 0 0-3.875 2.551-3.922 8.11-.247 10.941l.006-.007-.007.03a6.717 6.717 0 0 0 4.077 1.356h5.173l.03.03h5.192c6.687.053 9.376-8.605 3.835-12.35a9.365 9.365 0 0 0-2.821-4.552l-.043.043.006-.05A9.344 9.344 0 0 0 12.19 2.38zm-.358 4.146c1.244-.04 2.518.368 3.486 1.15a5.186 5.186 0 0 1 1.862 4.078v.518c3.53-.07 3.53 5.262 0 5.193h-5.193l-.008.009v-.04H6.785a2.59 2.59 0 0 1-1.067-.23h.001a2.597 2.597 0 1 1 3.437-3.437l3.013-3.012A6.747 6.747 0 0 0 8.11 8.24c.018-.01.04-.026.054-.023a5.186 5.186 0 0 1 3.67-1.69z"
94239
+ },
94240
+ grafana: {
94241
+ title: "Grafana",
94242
+ d: "M23.02 10.59a8.578 8.578 0 0 0-.862-3.034 8.911 8.911 0 0 0-1.789-2.445c.337-1.342-.413-2.505-.413-2.505-1.292-.08-2.113.4-2.416.62-.052-.02-.102-.044-.154-.064-.22-.089-.446-.172-.677-.247-.231-.073-.47-.14-.711-.197a9.867 9.867 0 0 0-.875-.161C14.557.753 12.94 0 12.94 0c-1.804 1.145-2.147 2.744-2.147 2.744l-.018.093c-.098.029-.2.057-.298.088-.138.042-.275.094-.413.143-.138.055-.275.107-.41.166a8.869 8.869 0 0 0-1.557.87l-.063-.029c-2.497-.955-4.716.195-4.716.195-.203 2.658.996 4.33 1.235 4.636a11.608 11.608 0 0 0-.607 2.635C1.636 12.677.953 15.014.953 15.014c1.926 2.214 4.171 2.351 4.171 2.351.003-.002.006-.002.006-.005.285.509.615.994.986 1.446.156.19.32.371.488.548-.704 2.009.099 3.68.099 3.68 2.144.08 3.553-.937 3.849-1.173a9.784 9.784 0 0 0 3.164.501h.08l.055-.003.107-.002.103-.005.003.002c1.01 1.44 2.788 1.646 2.788 1.646 1.264-1.332 1.337-2.653 1.337-2.94v-.058c0-.02-.003-.039-.003-.06.265-.187.52-.387.758-.6a7.875 7.875 0 0 0 1.415-1.7c1.43.083 2.437-.885 2.437-.885-.236-1.49-1.085-2.216-1.264-2.354l-.018-.013-.016-.013a.217.217 0 0 1-.031-.02c.008-.092.016-.18.02-.27.011-.162.016-.323.016-.48v-.253l-.005-.098-.008-.135a1.891 1.891 0 0 0-.01-.13c-.003-.042-.008-.083-.013-.125l-.016-.124-.018-.122a6.215 6.215 0 0 0-2.032-3.73 6.015 6.015 0 0 0-3.222-1.46 6.292 6.292 0 0 0-.85-.048l-.107.002h-.063l-.044.003-.104.008a4.777 4.777 0 0 0-3.335 1.695c-.332.4-.592.84-.768 1.297a4.594 4.594 0 0 0-.312 1.817l.003.091c.005.055.007.11.013.164a3.615 3.615 0 0 0 .698 1.82 3.53 3.53 0 0 0 1.827 1.282c.33.098.66.14.971.137.039 0 .078 0 .114-.002l.063-.003c.02 0 .041-.003.062-.003.034-.002.065-.007.099-.01.007 0 .018-.003.028-.003l.031-.005.06-.008a1.18 1.18 0 0 0 .112-.02c.036-.008.072-.013.109-.024a2.634 2.634 0 0 0 .914-.415c.028-.02.056-.041.085-.065a.248.248 0 0 0 .039-.35.244.244 0 0 0-.309-.06l-.078.042c-.09.044-.184.083-.283.116a2.476 2.476 0 0 1-.475.096c-.028.003-.054.006-.083.006l-.083.002c-.026 0-.054 0-.08-.002l-.102-.006h-.012l-.024.006c-.016-.003-.031-.003-.044-.006-.031-.002-.06-.007-.091-.01a2.59 2.59 0 0 1-.724-.213 2.557 2.557 0 0 1-.667-.438 2.52 2.52 0 0 1-.805-1.475 2.306 2.306 0 0 1-.029-.444l.006-.122v-.023l.002-.031c.003-.021.003-.04.005-.06a3.163 3.163 0 0 1 1.352-2.29 3.12 3.12 0 0 1 .937-.43 2.946 2.946 0 0 1 .776-.101h.06l.07.002.045.003h.026l.07.005a4.041 4.041 0 0 1 1.635.49 3.94 3.94 0 0 1 1.602 1.662 3.77 3.77 0 0 1 .397 1.414l.005.076.003.075c.002.026.002.05.002.075 0 .024.003.052 0 .07v.065l-.002.073-.008.174a6.195 6.195 0 0 1-.08.639 5.1 5.1 0 0 1-.267.927 5.31 5.31 0 0 1-.624 1.13 5.052 5.052 0 0 1-3.237 2.014 4.82 4.82 0 0 1-.649.066l-.039.003h-.287a6.607 6.607 0 0 1-1.716-.265 6.776 6.776 0 0 1-3.4-2.274 6.75 6.75 0 0 1-.746-1.15 6.616 6.616 0 0 1-.714-2.596l-.005-.083-.002-.02v-.056l-.003-.073v-.096l-.003-.104v-.07l.003-.163c.008-.22.026-.45.054-.678a8.707 8.707 0 0 1 .28-1.355c.128-.444.286-.872.473-1.277a7.04 7.04 0 0 1 1.456-2.1 5.925 5.925 0 0 1 .953-.763c.169-.111.343-.213.524-.306.089-.05.182-.091.273-.135.047-.02.093-.042.138-.062a7.177 7.177 0 0 1 .714-.267l.145-.045c.049-.015.098-.026.148-.041.098-.029.197-.052.296-.076.049-.013.1-.02.15-.033l.15-.032.151-.028.076-.013.075-.01.153-.024c.057-.01.114-.013.171-.023l.169-.021c.036-.003.073-.008.106-.01l.073-.008.036-.003.042-.002c.057-.003.114-.008.171-.01l.086-.006h.023l.037-.003.145-.007a7.999 7.999 0 0 1 1.708.125 7.917 7.917 0 0 1 2.048.68 8.253 8.253 0 0 1 1.672 1.09l.09.077.089.078c.06.052.114.107.171.159.057.052.112.106.166.16.052.055.107.107.159.164a8.671 8.671 0 0 1 1.41 1.978c.012.026.028.052.04.078l.04.078.075.156c.023.051.05.1.07.153l.065.15a8.848 8.848 0 0 1 .45 1.34.19.19 0 0 0 .201.142.186.186 0 0 0 .172-.184c.01-.246.002-.532-.024-.856z"
94243
+ },
94244
+ graphql: {
94245
+ title: "GraphQL",
94246
+ d: "M12.002 0a2.138 2.138 0 1 0 0 4.277 2.138 2.138 0 1 0 0-4.277zm8.54 4.931a2.138 2.138 0 1 0 0 4.277 2.138 2.138 0 1 0 0-4.277zm0 9.862a2.138 2.138 0 1 0 0 4.277 2.138 2.138 0 1 0 0-4.277zm-8.54 4.931a2.138 2.138 0 1 0 0 4.276 2.138 2.138 0 1 0 0-4.276zm-8.542-4.93a2.138 2.138 0 1 0 0 4.276 2.138 2.138 0 1 0 0-4.277zm0-9.863a2.138 2.138 0 1 0 0 4.277 2.138 2.138 0 1 0 0-4.277zm8.542-3.378L2.953 6.777v10.448l9.049 5.224 9.047-5.224V6.777zm0 1.601 7.66 13.27H4.34zm-1.387.371L3.97 15.037V7.363zm2.774 0 6.646 3.838v7.674zM5.355 17.44h13.293l-6.646 3.836z"
94247
+ },
94248
+ javascript: {
94249
+ title: "JavaScript",
94250
+ d: "M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71 1.005-1.14 1.291-.811 3.541.569 4.471 1.365 1.02 3.361 1.244 3.616 2.205.24 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.586-1.755-1.336l-1.83 1.051c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.666 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z",
94251
+ license: "MIT",
94252
+ licenseUrl: "https://spdx.org/licenses/MIT.html",
94253
+ source: "https://github.com/voodootikigod/logo.js/blob/1544bdeed6d618a6cfe4f0650d04ab8d9cfa76d9/js.svg",
94254
+ copyright: "Copyright (c) 2011 Christopher Williams <chris@iterativedesigns.com>"
94255
+ },
94256
+ kotlin: { title: "Kotlin", d: "M24 24H0V0h24L12 12Z" },
94257
+ kubernetes: {
94258
+ title: "Kubernetes",
94259
+ d: "M10.204 14.35l.007.01-.999 2.413a5.171 5.171 0 0 1-2.075-2.597l2.578-.437.004.005a.44.44 0 0 1 .484.606zm-.833-2.129a.44.44 0 0 0 .173-.756l.002-.011L7.585 9.7a5.143 5.143 0 0 0-.73 3.255l2.514-.725.002-.009zm1.145-1.98a.44.44 0 0 0 .699-.337l.01-.005.15-2.62a5.144 5.144 0 0 0-3.01 1.442l2.147 1.523.004-.002zm.76 2.75l.723.349.722-.347.18-.78-.5-.623h-.804l-.5.623.179.779zm1.5-3.095a.44.44 0 0 0 .7.336l.008.003 2.134-1.513a5.188 5.188 0 0 0-2.992-1.442l.148 2.615.002.001zm10.876 5.97l-5.773 7.181a1.6 1.6 0 0 1-1.248.594l-9.261.003a1.6 1.6 0 0 1-1.247-.596l-5.776-7.18a1.583 1.583 0 0 1-.307-1.34L2.1 5.573c.108-.47.425-.864.863-1.073L11.305.513a1.606 1.606 0 0 1 1.385 0l8.345 3.985c.438.209.755.604.863 1.073l2.062 8.955c.108.47-.005.963-.308 1.34zm-3.289-2.057c-.042-.01-.103-.026-.145-.034-.174-.033-.315-.025-.479-.038-.35-.037-.638-.067-.895-.148-.105-.04-.18-.165-.216-.216l-.201-.059a6.45 6.45 0 0 0-.105-2.332 6.465 6.465 0 0 0-.936-2.163c.052-.047.15-.133.177-.159.008-.09.001-.183.094-.282.197-.185.444-.338.743-.522.142-.084.273-.137.415-.242.032-.024.076-.062.11-.089.24-.191.295-.52.123-.736-.172-.216-.506-.236-.745-.045-.034.027-.08.062-.111.088-.134.116-.217.23-.33.35-.246.25-.45.458-.673.609-.097.056-.239.037-.303.033l-.19.135a6.545 6.545 0 0 0-4.146-2.003l-.012-.223c-.065-.062-.143-.115-.163-.25-.022-.268.015-.557.057-.905.023-.163.061-.298.068-.475.001-.04-.001-.099-.001-.142 0-.306-.224-.555-.5-.555-.275 0-.499.249-.499.555l.001.014c0 .041-.002.092 0 .128.006.177.044.312.067.475.042.348.078.637.056.906a.545.545 0 0 1-.162.258l-.012.211a6.424 6.424 0 0 0-4.166 2.003 8.373 8.373 0 0 1-.18-.128c-.09.012-.18.04-.297-.029-.223-.15-.427-.358-.673-.608-.113-.12-.195-.234-.329-.349-.03-.026-.077-.062-.111-.088a.594.594 0 0 0-.348-.132.481.481 0 0 0-.398.176c-.172.216-.117.546.123.737l.007.005.104.083c.142.105.272.159.414.242.299.185.546.338.743.522.076.082.09.226.1.288l.16.143a6.462 6.462 0 0 0-1.02 4.506l-.208.06c-.055.072-.133.184-.215.217-.257.081-.546.11-.895.147-.164.014-.305.006-.48.039-.037.007-.09.02-.133.03l-.004.002-.007.002c-.295.071-.484.342-.423.608.061.267.349.429.645.365l.007-.001.01-.003.129-.029c.17-.046.294-.113.448-.172.33-.118.604-.217.87-.256.112-.009.23.069.288.101l.217-.037a6.5 6.5 0 0 0 2.88 3.596l-.09.218c.033.084.069.199.044.282-.097.252-.263.517-.452.813-.091.136-.185.242-.268.399-.02.037-.045.095-.064.134-.128.275-.034.591.213.71.248.12.556-.007.69-.282v-.002c.02-.039.046-.09.062-.127.07-.162.094-.301.144-.458.132-.332.205-.68.387-.897.05-.06.13-.082.215-.105l.113-.205a6.453 6.453 0 0 0 4.609.012l.106.192c.086.028.18.042.256.155.136.232.229.507.342.84.05.156.074.295.145.457.016.037.043.09.062.129.133.276.442.402.69.282.247-.118.341-.435.213-.71-.02-.039-.045-.096-.065-.134-.083-.156-.177-.261-.268-.398-.19-.296-.346-.541-.443-.793-.04-.13.007-.21.038-.294-.018-.022-.059-.144-.083-.202a6.499 6.499 0 0 0 2.88-3.622c.064.01.176.03.213.038.075-.05.144-.114.28-.104.266.039.54.138.87.256.154.06.277.128.448.173.036.01.088.019.13.028l.009.003.007.001c.297.064.584-.098.645-.365.06-.266-.128-.537-.423-.608zM16.4 9.701l-1.95 1.746v.005a.44.44 0 0 0 .173.757l.003.01 2.526.728a5.199 5.199 0 0 0-.108-1.674A5.208 5.208 0 0 0 16.4 9.7zm-4.013 5.325a.437.437 0 0 0-.404-.232.44.44 0 0 0-.372.233h-.002l-1.268 2.292a5.164 5.164 0 0 0 3.326.003l-1.27-2.296h-.01zm1.888-1.293a.44.44 0 0 0-.27.036.44.44 0 0 0-.214.572l-.003.004 1.01 2.438a5.15 5.15 0 0 0 2.081-2.615l-2.6-.44-.004.005z"
94260
+ },
94261
+ mongodb: {
94262
+ title: "MongoDB",
94263
+ d: "M17.193 9.555c-1.264-5.58-4.252-7.414-4.573-8.115-.28-.394-.53-.954-.735-1.44-.036.495-.055.685-.523 1.184-.723.566-4.438 3.682-4.74 10.02-.282 5.912 4.27 9.435 4.888 9.884l.07.05A73.49 73.49 0 0111.91 24h.481c.114-1.032.284-2.056.51-3.07.417-.296.604-.463.85-.693a11.342 11.342 0 003.639-8.464c.01-.814-.103-1.662-.197-2.218zm-5.336 8.195s0-8.291.275-8.29c.213 0 .49 10.695.49 10.695-.381-.045-.765-1.76-.765-2.405z"
94264
+ },
94265
+ mysql: {
94266
+ title: "MySQL",
94267
+ d: "M16.405 5.501c-.115 0-.193.014-.274.033v.013h.014c.054.104.146.18.214.273.054.107.1.214.154.32l.014-.015c.094-.066.14-.172.14-.333-.04-.047-.046-.094-.08-.14-.04-.067-.126-.1-.18-.153zM5.77 18.695h-.927a50.854 50.854 0 00-.27-4.41h-.008l-1.41 4.41H2.45l-1.4-4.41h-.01a72.892 72.892 0 00-.195 4.41H0c.055-1.966.192-3.81.41-5.53h1.15l1.335 4.064h.008l1.347-4.064h1.095c.242 2.015.384 3.86.428 5.53zm4.017-4.08c-.378 2.045-.876 3.533-1.492 4.46-.482.716-1.01 1.073-1.583 1.073-.153 0-.34-.046-.566-.138v-.494c.11.017.24.026.386.026.268 0 .483-.075.647-.222.197-.18.295-.382.295-.605 0-.155-.077-.47-.23-.944L6.23 14.615h.91l.727 2.36c.164.536.233.91.205 1.123.4-1.064.678-2.227.835-3.483zm12.325 4.08h-2.63v-5.53h.885v4.85h1.745zm-3.32.135l-1.016-.5c.09-.076.177-.158.255-.25.433-.506.648-1.258.648-2.253 0-1.83-.718-2.746-2.155-2.746-.704 0-1.254.232-1.65.697-.43.508-.646 1.256-.646 2.245 0 .972.19 1.686.574 2.14.35.41.877.615 1.583.615.264 0 .506-.033.725-.098l1.325.772.36-.622zM15.5 17.588c-.225-.36-.337-.94-.337-1.736 0-1.393.424-2.09 1.27-2.09.443 0 .77.167.977.5.224.362.336.936.336 1.723 0 1.404-.424 2.108-1.27 2.108-.445 0-.77-.167-.978-.5zm-1.658-.425c0 .47-.172.856-.516 1.156-.344.3-.803.45-1.384.45-.543 0-1.064-.172-1.573-.515l.237-.476c.438.22.833.328 1.19.328.332 0 .593-.073.783-.22a.754.754 0 00.3-.615c0-.33-.23-.61-.648-.845-.388-.213-1.163-.657-1.163-.657-.422-.307-.632-.636-.632-1.177 0-.45.157-.81.47-1.085.315-.278.72-.415 1.22-.415.512 0 .98.136 1.4.41l-.213.476a2.726 2.726 0 00-1.064-.23c-.283 0-.502.068-.654.206a.685.685 0 00-.248.524c0 .328.234.61.666.85.393.215 1.187.67 1.187.67.433.305.648.63.648 1.168zm9.382-5.852c-.535-.014-.95.04-1.297.188-.1.04-.26.04-.274.167.055.053.063.14.11.214.08.134.218.313.346.407.14.11.28.216.427.31.26.16.555.255.81.416.145.094.293.213.44.313.073.05.12.14.214.172v-.02c-.046-.06-.06-.147-.105-.214-.067-.067-.134-.127-.2-.193a3.223 3.223 0 00-.695-.675c-.214-.146-.682-.35-.77-.595l-.013-.014c.146-.013.32-.066.46-.106.227-.06.435-.047.67-.106.106-.027.213-.06.32-.094v-.06c-.12-.12-.21-.283-.334-.395a8.867 8.867 0 00-1.104-.823c-.21-.134-.476-.22-.697-.334-.08-.04-.214-.06-.26-.127-.12-.146-.19-.34-.275-.514a17.69 17.69 0 01-.547-1.163c-.12-.262-.193-.523-.34-.763-.69-1.137-1.437-1.826-2.586-2.5-.247-.14-.543-.2-.856-.274-.167-.008-.334-.02-.5-.027-.11-.047-.216-.174-.31-.235-.38-.24-1.364-.76-1.644-.072-.18.434.267.862.422 1.082.115.153.26.328.34.5.047.116.06.235.107.356.106.294.207.622.347.897.073.14.153.287.247.413.054.073.146.107.167.227-.094.136-.1.334-.154.5-.24.757-.146 1.693.194 2.25.107.166.362.534.703.393.3-.12.234-.5.32-.835.02-.08.007-.133.048-.187v.015c.094.188.188.367.274.555.206.328.566.668.867.895.16.12.287.328.487.402v-.02h-.015c-.043-.058-.1-.086-.154-.133a3.445 3.445 0 01-.35-.4 8.76 8.76 0 01-.747-1.218c-.11-.21-.202-.436-.29-.643-.04-.08-.04-.2-.107-.24-.1.146-.247.273-.32.453-.127.288-.14.642-.188 1.01-.027.007-.014 0-.027.014-.214-.052-.287-.274-.367-.46-.2-.475-.233-1.238-.06-1.785.047-.14.247-.582.167-.716-.042-.127-.174-.2-.247-.303a2.478 2.478 0 01-.24-.427c-.16-.374-.24-.788-.414-1.162-.08-.173-.22-.354-.334-.513-.127-.18-.267-.307-.368-.52-.033-.073-.08-.194-.027-.274.014-.054.042-.075.094-.09.088-.072.335.022.422.062.247.1.455.194.662.334.094.066.195.193.315.226h.14c.214.047.455.014.655.073.355.114.675.28.962.46a5.953 5.953 0 012.085 2.286c.08.154.115.295.188.455.14.33.313.663.455.982.14.315.275.636.476.897.1.14.502.213.682.286.133.06.34.115.46.188.23.14.454.3.67.454.11.076.443.243.463.378z"
94268
+ },
94269
+ natsdotio: {
94270
+ title: "NATS.io",
94271
+ d: "M12.004 0H.404v18.807h9.938l1.714 1.602v-.026L15.966 24v-5.193h7.63V0H12.003zm7.578 14.45H15.38L6.898 6.519v7.93H4.116V4.376h4.349l8.344 7.784V4.375h2.773V14.45z"
94272
+ },
94273
+ nextdotjs: {
94274
+ title: "Next.js",
94275
+ d: "M18.665 21.978C16.758 23.255 14.465 24 12 24 5.377 24 0 18.623 0 12S5.377 0 12 0s12 5.377 12 12c0 3.583-1.574 6.801-4.067 9.001L9.219 7.2H7.2v9.596h1.615V9.251l9.85 12.727Zm-3.332-8.533 1.6 2.061V7.2h-1.6v6.245Z"
94276
+ },
94277
+ nginx: {
94278
+ title: "NGINX",
94279
+ d: "M12 0L1.605 6v12L12 24l10.395-6V6L12 0zm6 16.59c0 .705-.646 1.29-1.529 1.29-.631 0-1.351-.255-1.801-.81l-6-7.141v6.66c0 .721-.57 1.29-1.274 1.29H7.32c-.721 0-1.29-.6-1.29-1.29V7.41c0-.705.63-1.29 1.5-1.29.646 0 1.38.255 1.83.81l5.97 7.141V7.41c0-.721.6-1.29 1.29-1.29h.075c.72 0 1.29.6 1.29 1.29v9.18H18z"
94280
+ },
94281
+ nodedotjs: {
94282
+ title: "Node.js",
94283
+ d: "M11.998,24c-0.321,0-0.641-0.084-0.922-0.247l-2.936-1.737c-0.438-0.245-0.224-0.332-0.08-0.383 c0.585-0.203,0.703-0.25,1.328-0.604c0.065-0.037,0.151-0.023,0.218,0.017l2.256,1.339c0.082,0.045,0.197,0.045,0.272,0l8.795-5.076 c0.082-0.047,0.134-0.141,0.134-0.238V6.921c0-0.099-0.053-0.192-0.137-0.242l-8.791-5.072c-0.081-0.047-0.189-0.047-0.271,0 L3.075,6.68C2.99,6.729,2.936,6.825,2.936,6.921v10.15c0,0.097,0.054,0.189,0.139,0.235l2.409,1.392 c1.307,0.654,2.108-0.116,2.108-0.89V7.787c0-0.142,0.114-0.253,0.256-0.253h1.115c0.139,0,0.255,0.112,0.255,0.253v10.021 c0,1.745-0.95,2.745-2.604,2.745c-0.508,0-0.909,0-2.026-0.551L2.28,18.675c-0.57-0.329-0.922-0.945-0.922-1.604V6.921 c0-0.659,0.353-1.275,0.922-1.603l8.795-5.082c0.557-0.315,1.296-0.315,1.848,0l8.794,5.082c0.57,0.329,0.924,0.944,0.924,1.603 v10.15c0,0.659-0.354,1.273-0.924,1.604l-8.794,5.078C12.643,23.916,12.324,24,11.998,24z M19.099,13.993 c0-1.9-1.284-2.406-3.987-2.763c-2.731-0.361-3.009-0.548-3.009-1.187c0-0.528,0.235-1.233,2.258-1.233 c1.807,0,2.473,0.389,2.747,1.607c0.024,0.115,0.129,0.199,0.247,0.199h1.141c0.071,0,0.138-0.031,0.186-0.081 c0.048-0.054,0.074-0.123,0.067-0.196c-0.177-2.098-1.571-3.076-4.388-3.076c-2.508,0-4.004,1.058-4.004,2.833 c0,1.925,1.488,2.457,3.895,2.695c2.88,0.282,3.103,0.703,3.103,1.269c0,0.983-0.789,1.402-2.642,1.402 c-2.327,0-2.839-0.584-3.011-1.742c-0.02-0.124-0.126-0.215-0.253-0.215h-1.137c-0.141,0-0.254,0.112-0.254,0.253 c0,1.482,0.806,3.248,4.655,3.248C17.501,17.007,19.099,15.91,19.099,13.993z"
94284
+ },
94285
+ openjdk: {
94286
+ title: "OpenJDK",
94287
+ d: "M11.915 0 11.7.215C9.515 2.4 7.47 6.39 6.046 10.483c-1.064 1.024-3.633 2.81-3.711 3.551-.093.87 1.746 2.611 1.55 3.235-.198.625-1.304 1.408-1.014 1.939.1.188.823.011 1.277-.491a13.389 13.389 0 0 0-.017 2.14c.076.906.27 1.668.643 2.232.372.563.956.911 1.667.911.397 0 .727-.114 1.024-.264.298-.149.571-.33.91-.5.68-.34 1.634-.666 3.53-.604 1.903.062 2.872.39 3.559.704.687.314 1.15.664 1.925.664.767 0 1.395-.336 1.807-.9.412-.563.631-1.33.72-2.24.06-.623.055-1.32 0-2.066.454.45 1.117.604 1.213.424.29-.53-.816-1.314-1.013-1.937-.198-.624 1.642-2.366 1.549-3.236-.08-.748-2.707-2.568-3.748-3.586C16.428 6.374 14.308 2.394 12.13.215zm.175 6.038a2.95 2.95 0 0 1 2.943 2.942 2.95 2.95 0 0 1-2.943 2.943A2.95 2.95 0 0 1 9.148 8.98a2.95 2.95 0 0 1 2.942-2.942zM8.685 7.983a3.515 3.515 0 0 0-.145.997c0 1.951 1.6 3.55 3.55 3.55 1.95 0 3.55-1.598 3.55-3.55 0-.329-.046-.648-.132-.951.334.095.64.208.915.336a42.699 42.699 0 0 1 2.042 5.829c.678 2.545 1.01 4.92.846 6.607-.082.844-.29 1.51-.606 1.94-.315.431-.713.651-1.315.651-.593 0-.932-.27-1.673-.61-.741-.338-1.825-.694-3.792-.758-1.974-.064-3.073.293-3.821.669-.375.188-.659.373-.911.5s-.466.2-.752.2c-.53 0-.876-.209-1.16-.64-.285-.43-.474-1.101-.545-1.948-.141-1.693.176-4.069.823-6.614a43.155 43.155 0 0 1 1.934-5.783c.348-.167.749-.31 1.192-.425zm-3.382 4.362a.216.216 0 0 1 .13.031c-.166.56-.323 1.116-.463 1.665a33.849 33.849 0 0 0-.547 2.555 3.9 3.9 0 0 0-.2-.39c-.58-1.012-.914-1.642-1.16-2.08.315-.24 1.679-1.755 2.24-1.781zm13.394.01c.562.027 1.926 1.543 2.24 1.783-.246.438-.58 1.068-1.16 2.08a4.428 4.428 0 0 0-.163.309 32.354 32.354 0 0 0-.562-2.49 40.579 40.579 0 0 0-.482-1.652.216.216 0 0 1 .127-.03z",
94288
+ license: "BSD-3-Clause",
94289
+ licenseUrl: "https://spdx.org/licenses/BSD-3-Clause.html",
94290
+ source: "https://github.com/openjdk/duke/blob/master/vector/Agent.svg"
94291
+ },
94292
+ postgresql: {
94293
+ title: "PostgreSQL",
94294
+ d: "M23.5594 14.7228a.5269.5269 0 0 0-.0563-.1191c-.139-.2632-.4768-.3418-1.0074-.2321-1.6533.3411-2.2935.1312-2.5256-.0191 1.342-2.0482 2.445-4.522 3.0411-6.8297.2714-1.0507.7982-3.5237.1222-4.7316a1.5641 1.5641 0 0 0-.1509-.235C21.6931.9086 19.8007.0248 17.5099.0005c-1.4947-.0158-2.7705.3461-3.1161.4794a9.449 9.449 0 0 0-.5159-.0816 8.044 8.044 0 0 0-1.3114-.1278c-1.1822-.0184-2.2038.2642-3.0498.8406-.8573-.3211-4.7888-1.645-7.2219.0788C.9359 2.1526.3086 3.8733.4302 6.3043c.0409.818.5069 3.334 1.2423 5.7436.4598 1.5065.9387 2.7019 1.4334 3.582.553.9942 1.1259 1.5933 1.7143 1.7895.4474.1491 1.1327.1441 1.8581-.7279.8012-.9635 1.5903-1.8258 1.9446-2.2069.4351.2355.9064.3625 1.39.3772a.0569.0569 0 0 0 .0004.0041 11.0312 11.0312 0 0 0-.2472.3054c-.3389.4302-.4094.5197-1.5002.7443-.3102.064-1.1344.2339-1.1464.8115-.0025.1224.0329.2309.0919.3268.2269.4231.9216.6097 1.015.6331 1.3345.3335 2.5044.092 3.3714-.6787-.017 2.231.0775 4.4174.3454 5.0874.2212.5529.7618 1.9045 2.4692 1.9043.2505 0 .5263-.0291.8296-.0941 1.7819-.3821 2.5557-1.1696 2.855-2.9059.1503-.8707.4016-2.8753.5388-4.1012.0169-.0703.0357-.1207.057-.1362.0007-.0005.0697-.0471.4272.0307a.3673.3673 0 0 0 .0443.0068l.2539.0223.0149.001c.8468.0384 1.9114-.1426 2.5312-.4308.6438-.2988 1.8057-1.0323 1.5951-1.6698zM2.371 11.8765c-.7435-2.4358-1.1779-4.8851-1.2123-5.5719-.1086-2.1714.4171-3.6829 1.5623-4.4927 1.8367-1.2986 4.8398-.5408 6.108-.13-.0032.0032-.0066.0061-.0098.0094-2.0238 2.044-1.9758 5.536-1.9708 5.7495-.0002.0823.0066.1989.0162.3593.0348.5873.0996 1.6804-.0735 2.9184-.1609 1.1504.1937 2.2764.9728 3.0892.0806.0841.1648.1631.2518.2374-.3468.3714-1.1004 1.1926-1.9025 2.1576-.5677.6825-.9597.5517-1.0886.5087-.3919-.1307-.813-.5871-1.2381-1.3223-.4796-.839-.9635-2.0317-1.4155-3.5126zm6.0072 5.0871c-.1711-.0428-.3271-.1132-.4322-.1772.0889-.0394.2374-.0902.4833-.1409 1.2833-.2641 1.4815-.4506 1.9143-1.0002.0992-.126.2116-.2687.3673-.4426a.3549.3549 0 0 0 .0737-.1298c.1708-.1513.2724-.1099.4369-.0417.156.0646.3078.26.3695.4752.0291.1016.0619.2945-.0452.4444-.9043 1.2658-2.2216 1.2494-3.1676 1.0128zm2.094-3.988-.0525.141c-.133.3566-.2567.6881-.3334 1.003-.6674-.0021-1.3168-.2872-1.8105-.8024-.6279-.6551-.9131-1.5664-.7825-2.5004.1828-1.3079.1153-2.4468.079-3.0586-.005-.0857-.0095-.1607-.0122-.2199.2957-.2621 1.6659-.9962 2.6429-.7724.4459.1022.7176.4057.8305.928.5846 2.7038.0774 3.8307-.3302 4.7363-.084.1866-.1633.3629-.2311.5454zm7.3637 4.5725c-.0169.1768-.0358.376-.0618.5959l-.146.4383a.3547.3547 0 0 0-.0182.1077c-.0059.4747-.054.6489-.115.8693-.0634.2292-.1353.4891-.1794 1.0575-.11 1.4143-.8782 2.2267-2.4172 2.5565-1.5155.3251-1.7843-.4968-2.0212-1.2217a6.5824 6.5824 0 0 0-.0769-.2266c-.2154-.5858-.1911-1.4119-.1574-2.5551.0165-.5612-.0249-1.9013-.3302-2.6462.0044-.2932.0106-.5909.019-.8918a.3529.3529 0 0 0-.0153-.1126 1.4927 1.4927 0 0 0-.0439-.208c-.1226-.4283-.4213-.7866-.7797-.9351-.1424-.059-.4038-.1672-.7178-.0869.067-.276.1831-.5875.309-.9249l.0529-.142c.0595-.16.134-.3257.213-.5012.4265-.9476 1.0106-2.2453.3766-5.1772-.2374-1.0981-1.0304-1.6343-2.2324-1.5098-.7207.0746-1.3799.3654-1.7088.5321a5.6716 5.6716 0 0 0-.1958.1041c.0918-1.1064.4386-3.1741 1.7357-4.4823a4.0306 4.0306 0 0 1 .3033-.276.3532.3532 0 0 0 .1447-.0644c.7524-.5706 1.6945-.8506 2.802-.8325.4091.0067.8017.0339 1.1742.081 1.939.3544 3.2439 1.4468 4.0359 2.3827.8143.9623 1.2552 1.9315 1.4312 2.4543-1.3232-.1346-2.2234.1268-2.6797.779-.9926 1.4189.543 4.1729 1.2811 5.4964.1353.2426.2522.4522.2889.5413.2403.5825.5515.9713.7787 1.2552.0696.087.1372.1714.1885.245-.4008.1155-1.1208.3825-1.0552 1.717-.0123.1563-.0423.4469-.0834.8148-.0461.2077-.0702.4603-.0994.7662zm.8905-1.6211c-.0405-.8316.2691-.9185.5967-1.0105a2.8566 2.8566 0 0 0 .135-.0406 1.202 1.202 0 0 0 .1342.103c.5703.3765 1.5823.4213 3.0068.1344-.2016.1769-.5189.3994-.9533.6011-.4098.1903-1.0957.333-1.7473.3636-.7197.0336-1.0859-.0807-1.1721-.151zm.5695-9.2712c-.0059.3508-.0542.6692-.1054 1.0017-.055.3576-.112.7274-.1264 1.1762-.0142.4368.0404.8909.0932 1.3301.1066.887.216 1.8003-.2075 2.7014a3.5272 3.5272 0 0 1-.1876-.3856c-.0527-.1276-.1669-.3326-.3251-.6162-.6156-1.1041-2.0574-3.6896-1.3193-4.7446.3795-.5427 1.3408-.5661 2.1781-.463zm.2284 7.0137a12.3762 12.3762 0 0 0-.0853-.1074l-.0355-.0444c.7262-1.1995.5842-2.3862.4578-3.4385-.0519-.4318-.1009-.8396-.0885-1.2226.0129-.4061.0666-.7543.1185-1.0911.0639-.415.1288-.8443.1109-1.3505.0134-.0531.0188-.1158.0118-.1902-.0457-.4855-.5999-1.938-1.7294-3.253-.6076-.7073-1.4896-1.4972-2.6889-2.0395.5251-.1066 1.2328-.2035 2.0244-.1859 2.0515.0456 3.6746.8135 4.8242 2.2824a.908.908 0 0 1 .0667.1002c.7231 1.3556-.2762 6.2751-2.9867 10.5405zm-8.8166-6.1162c-.025.1794-.3089.4225-.6211.4225a.5821.5821 0 0 1-.0809-.0056c-.1873-.026-.3765-.144-.5059-.3156-.0458-.0605-.1203-.178-.1055-.2844.0055-.0401.0261-.0985.0925-.1488.1182-.0894.3518-.1226.6096-.0867.3163.0441.6426.1938.6113.4186zm7.9305-.4114c.0111.0792-.049.201-.1531.3102-.0683.0717-.212.1961-.4079.2232a.5456.5456 0 0 1-.075.0052c-.2935 0-.5414-.2344-.5607-.3717-.024-.1765.2641-.3106.5611-.352.297-.0414.6111.0088.6356.1851z"
94295
+ },
94296
+ prometheus: {
94297
+ title: "Prometheus",
94298
+ d: "M12 0C5.373 0 0 5.372 0 12c0 6.627 5.373 12 12 12s12-5.373 12-12c0-6.628-5.373-12-12-12zm0 22.46c-1.885 0-3.414-1.26-3.414-2.814h6.828c0 1.553-1.528 2.813-3.414 2.813zm5.64-3.745H6.36v-2.046h11.28v2.046zm-.04-3.098H6.391c-.037-.043-.075-.086-.111-.13-1.155-1.401-1.427-2.133-1.69-2.879-.005-.025 1.4.287 2.395.511 0 0 .513.119 1.262.255-.72-.843-1.147-1.915-1.147-3.01 0-2.406 1.845-4.508 1.18-6.207.648.053 1.34 1.367 1.387 3.422.689-.951.977-2.69.977-3.755 0-1.103.727-2.385 1.454-2.429-.648 1.069.168 1.984.894 4.256.272.854.237 2.29.447 3.201.07-1.892.395-4.652 1.595-5.605-.529 1.2.079 2.702.494 3.424.671 1.164 1.078 2.047 1.078 3.716a4.642 4.642 0 01-1.11 2.996c.792-.149 1.34-.283 1.34-.283l2.573-.502s-.374 1.538-1.81 3.019z"
94299
+ },
94300
+ python: {
94301
+ title: "Python",
94302
+ d: "M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z"
94303
+ },
94304
+ rabbitmq: {
94305
+ title: "RabbitMQ",
94306
+ d: "M23.035 9.601h-7.677a.956.956 0 01-.962-.962V.962a.956.956 0 00-.962-.956H10.56a.956.956 0 00-.962.956V8.64a.956.956 0 01-.962.962H5.762a.956.956 0 01-.961-.962V.962A.956.956 0 003.839 0H.959a.956.956 0 00-.956.962v22.076A.956.956 0 00.965 24h22.07a.956.956 0 00.962-.962V10.58a.956.956 0 00-.962-.98zm-3.86 8.152a1.437 1.437 0 01-1.437 1.443h-1.924a1.437 1.437 0 01-1.436-1.443v-1.917a1.437 1.437 0 011.436-1.443h1.924a1.437 1.437 0 011.437 1.443z"
94307
+ },
94308
+ react: {
94309
+ title: "React",
94310
+ d: "M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z"
94311
+ },
94312
+ redis: {
94313
+ title: "Redis",
94314
+ d: "M22.71 13.145c-1.66 2.092-3.452 4.483-7.038 4.483-3.203 0-4.397-2.825-4.48-5.12.701 1.484 2.073 2.685 4.214 2.63 4.117-.133 6.94-3.852 6.94-7.239 0-4.05-3.022-6.972-8.268-6.972-3.752 0-8.4 1.428-11.455 3.685C2.59 6.937 3.885 9.958 4.35 9.626c2.648-1.904 4.748-3.13 6.784-3.744C8.12 9.244.886 17.05 0 18.425c.1 1.261 1.66 4.648 2.424 4.648.232 0 .431-.133.664-.365a100.49 100.49 0 0 0 5.54-6.765c.222 3.104 1.748 6.898 6.014 6.898 3.819 0 7.604-2.756 9.33-8.965.2-.764-.73-1.361-1.261-.73zm-4.349-5.013c0 1.959-1.926 2.922-3.685 2.922-.941 0-1.664-.247-2.235-.568 1.051-1.592 2.092-3.225 3.21-4.973 1.972.334 2.71 1.43 2.71 2.619z"
94315
+ },
94316
+ spring: {
94317
+ title: "Spring",
94318
+ d: "M21.8537 1.4158a10.4504 10.4504 0 0 1-1.284 2.2471A11.9666 11.9666 0 1 0 3.8518 20.7757l.4445.3951a11.9543 11.9543 0 0 0 19.6316-8.2971c.3457-3.0126-.568-6.8649-2.0743-11.458zM5.5805 20.8745a1.0174 1.0174 0 1 1-.1482-1.4323 1.0396 1.0396 0 0 1 .1482 1.4323zm16.1991-3.5806c-2.9385 3.9263-9.2601 2.5928-13.2852 2.7904 0 0-.7161.0494-1.4323.1481 0 0 .2717-.1234.6174-.2469 2.8398-.9877 4.1732-1.1853 5.9018-2.0743 3.2349-1.6545 6.4698-5.2844 7.1118-9.0379-1.2347 3.6053-4.9881 6.7167-8.3959 7.9761-2.3459.8643-6.5685 1.7039-6.5685 1.7039l-.1729-.0988c-2.8645-1.4076-2.9632-7.6304 2.2718-9.6306 2.2966-.889 4.4696-.395 6.9637-.9877 2.6422-.6174 5.7043-2.5929 6.939-5.1857 1.3828 4.1732 3.062 10.643.0493 14.6434z"
94319
+ },
94320
+ sqlite: {
94321
+ title: "SQLite",
94322
+ d: "M21.678.521c-1.032-.92-2.28-.55-3.513.544a8.71 8.71 0 0 0-.547.535c-2.109 2.237-4.066 6.38-4.674 9.544.237.48.422 1.093.544 1.561a13.044 13.044 0 0 1 .164.703s-.019-.071-.096-.296l-.05-.146a1.689 1.689 0 0 0-.033-.08c-.138-.32-.518-.995-.686-1.289-.143.423-.27.818-.376 1.176.484.884.778 2.4.778 2.4s-.025-.099-.147-.442c-.107-.303-.644-1.244-.772-1.464-.217.804-.304 1.346-.226 1.478.152.256.296.698.422 1.186.286 1.1.485 2.44.485 2.44l.017.224a22.41 22.41 0 0 0 .056 2.748c.095 1.146.273 2.13.5 2.657l.155-.084c-.334-1.038-.47-2.399-.41-3.967.09-2.398.642-5.29 1.661-8.304 1.723-4.55 4.113-8.201 6.3-9.945-1.993 1.8-4.692 7.63-5.5 9.788-.904 2.416-1.545 4.684-1.931 6.857.666-2.037 2.821-2.912 2.821-2.912s1.057-1.304 2.292-3.166c-.74.169-1.955.458-2.362.629-.6.251-.762.337-.762.337s1.945-1.184 3.613-1.72C21.695 7.9 24.195 2.767 21.678.521m-18.573.543A1.842 1.842 0 0 0 1.27 2.9v16.608a1.84 1.84 0 0 0 1.835 1.834h9.418a22.953 22.953 0 0 1-.052-2.707c-.006-.062-.011-.141-.016-.2a27.01 27.01 0 0 0-.473-2.378c-.121-.47-.275-.898-.369-1.057-.116-.197-.098-.31-.097-.432 0-.12.015-.245.037-.386a9.98 9.98 0 0 1 .234-1.045l.217-.028c-.017-.035-.014-.065-.031-.097l-.041-.381a32.8 32.8 0 0 1 .382-1.194l.2-.019c-.008-.016-.01-.038-.018-.053l-.043-.316c.63-3.28 2.587-7.443 4.8-9.791.066-.069.133-.128.198-.194Z"
94323
+ },
94324
+ terraform: {
94325
+ title: "Terraform",
94326
+ d: "M1.44 0v7.575l6.561 3.79V3.787zm21.12 4.227l-6.561 3.791v7.574l6.56-3.787zM8.72 4.23v7.575l6.561 3.787V8.018zm0 8.405v7.575L15.28 24v-7.578z"
94327
+ },
94328
+ typescript: {
94329
+ title: "TypeScript",
94330
+ d: "M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z"
94331
+ },
94332
+ vault: {
94333
+ title: "Vault",
94334
+ d: "M0 0l11.955 24L24 0zm13.366 4.827h1.393v1.38h-1.393zm-2.77 5.569H9.22V8.993h1.389zm0-2.087H9.22V6.906h1.389zm0-2.086H9.22V4.819h1.389zm2.087 6.263h-1.377V11.08h1.388zm0-2.09h-1.377V8.993h1.388zm0-2.087h-1.377V6.906h1.388zm0-2.086h-1.377V4.819h1.388zm.683.683h1.393v1.389h-1.393zm0 3.475V8.993h1.389v1.388Z"
94202
94335
  }
94203
94336
  };
94204
- var views = {
94205
- logical: logicalView,
94206
- application: applicationView,
94207
- infrastructure: infrastructureView,
94208
- security: securityView
94209
- };
94337
+ var LOGO_NAMES = Object.keys(LOGOS);
94210
94338
 
94211
94339
  // src/validator.ts
94212
94340
  function validate(model) {
@@ -94230,8 +94358,8 @@ function validate(model) {
94230
94358
  ...checkMissingLabels(elements),
94231
94359
  ...checkNesting(elements, view),
94232
94360
  ...checkFlows(model, view),
94233
- ...checkElementAttributes(elements, view),
94234
- ...checkTrustBoundaries(model, view),
94361
+ ...checkEndpointRoles(model, view),
94362
+ ...checkLogos(elements, view),
94235
94363
  ...checkBusinessObjects(model, view),
94236
94364
  ...checkMinimumCounts(elements, model, view),
94237
94365
  ...checkIsolatedElements(model, view, elements)
@@ -94273,6 +94401,48 @@ function checkUnknownKinds(elements, view) {
94273
94401
  }
94274
94402
  return diagnostics;
94275
94403
  }
94404
+ function checkLogos(elements, view) {
94405
+ const diagnostics = [];
94406
+ const logoKinds = view.logoKinds ?? [];
94407
+ for (const element of elements) {
94408
+ const logo = element.logo;
94409
+ if (!logo) continue;
94410
+ if (!logoKinds.includes(element.kind)) {
94411
+ diagnostics.push({
94412
+ code: "E0108",
94413
+ severity: "error",
94414
+ message: `\`${element.kind}\` does not carry a logo`,
94415
+ span: logo.span,
94416
+ note: logoKinds.length ? `the \`${view.name}\` view accepts \`logo\` on: ${logoKinds.join(", ")}` : `the \`${view.name}\` view has no kinds that carry a logo`
94417
+ });
94418
+ continue;
94419
+ }
94420
+ if (logo.source === "builtin") {
94421
+ if (Object.hasOwn(LOGOS, logo.value)) continue;
94422
+ const suggestion = nearest(logo.value, LOGO_NAMES);
94423
+ diagnostics.push({
94424
+ code: "E0107",
94425
+ severity: "error",
94426
+ message: `unknown built-in logo \`${logo.value}\``,
94427
+ span: logo.span,
94428
+ note: `${LOGO_NAMES.length} built-ins available \u2014 run \`cairn logos\` to list them`,
94429
+ help: suggestion !== void 0 ? `did you mean \`${suggestion}\`?` : `use a file instead: \`logo: "./logos/${logo.value}.svg"\``
94430
+ });
94431
+ continue;
94432
+ }
94433
+ if (/^[a-z][a-z0-9+.-]*:/i.test(logo.value) || logo.value.startsWith("//")) {
94434
+ diagnostics.push({
94435
+ code: "E0105",
94436
+ severity: "error",
94437
+ message: `\`logo\` will not fetch \`${logo.value}\``,
94438
+ span: logo.span,
94439
+ note: "a linked logo leaves the diagram depending on a server that can change or vanish",
94440
+ help: 'download it next to the diagram and point at the file: `logo: "./logos/name.svg"`'
94441
+ });
94442
+ }
94443
+ }
94444
+ return diagnostics;
94445
+ }
94276
94446
  function checkMissingLabels(elements) {
94277
94447
  const diagnostics = [];
94278
94448
  for (const element of elements) {
@@ -94307,6 +94477,42 @@ function checkNesting(elements, view) {
94307
94477
  }
94308
94478
  return diagnostics;
94309
94479
  }
94480
+ function checkEndpointRoles(model, view) {
94481
+ const diagnostics = [];
94482
+ const hubKinds = new Set(view.hubKinds ?? []);
94483
+ const isHub = (id) => hubKinds.has(model.index.get(id)?.kind ?? "");
94484
+ for (const flow of model.flows) {
94485
+ for (const [role, hubId, hubSide] of [
94486
+ [flow.fromRole, flow.to, flow.toSide],
94487
+ [flow.toRole, flow.from, flow.fromSide]
94488
+ ]) {
94489
+ if (!role) continue;
94490
+ if (!model.index.has(hubId)) continue;
94491
+ if (!isHub(hubId)) {
94492
+ const kinds = [...hubKinds];
94493
+ diagnostics.push({
94494
+ code: "E0224",
94495
+ severity: "error",
94496
+ message: `\`${role.value}\` needs a ${kinds.join(" or ") || "queue"} at the other end of the flow`,
94497
+ span: role.span,
94498
+ note: `\`${hubId}\` is ${model.index.get(hubId)?.kind ? `a \`${model.index.get(hubId).kind}\`` : "not one"}`,
94499
+ help: "a role says what this element does with a queue \u2014 between two ordinary elements the arrow already says it"
94500
+ });
94501
+ continue;
94502
+ }
94503
+ if (hubSide)
94504
+ diagnostics.push({
94505
+ code: "E0225",
94506
+ severity: "error",
94507
+ message: `\`${hubId}.${hubSide.value}\` and \`${role.value}\` both decide where this flow meets the queue`,
94508
+ span: hubSide.span,
94509
+ note: "`producer` means the queue's left cap and `consumer` its right",
94510
+ help: "keep the role and drop the side, or keep the side and drop the role"
94511
+ });
94512
+ }
94513
+ }
94514
+ return diagnostics;
94515
+ }
94310
94516
  function checkFlows(model, view) {
94311
94517
  const diagnostics = [];
94312
94518
  const isActor = (id) => {
@@ -94364,75 +94570,6 @@ function checkFlows(model, view) {
94364
94570
  }
94365
94571
  return diagnostics;
94366
94572
  }
94367
- function checkElementAttributes(elements, view) {
94368
- const spec = view.attrSpec;
94369
- if (!spec) return [];
94370
- const diagnostics = [];
94371
- for (const element of elements) {
94372
- if (element.kind !== spec.kind) continue;
94373
- if (!element.attr) {
94374
- diagnostics.push({
94375
- code: spec.code,
94376
- severity: "error",
94377
- message: spec.message + ` (\`${element.id}\`)`,
94378
- span: element.idSpan,
94379
- help: spec.help
94380
- });
94381
- } else if (!spec.values.includes(element.attr.value)) {
94382
- const suggestion = nearest(element.attr.value, spec.values);
94383
- diagnostics.push({
94384
- code: spec.code,
94385
- severity: "error",
94386
- message: `invalid ${spec.kind} value \`${element.attr.value}\` (\`${element.id}\`)`,
94387
- span: element.attr.span,
94388
- note: `allowed: ${spec.values.join(", ")}`,
94389
- help: suggestion ? `did you mean \`${suggestion}\`?` : spec.help
94390
- });
94391
- }
94392
- }
94393
- return diagnostics;
94394
- }
94395
- function checkTrustBoundaries(model, view) {
94396
- if (!view.boundaryLint && !view.crossZoneTechRecommended) return [];
94397
- const diagnostics = [];
94398
- const zoneOf2 = (id) => {
94399
- for (let ancestor = model.index.get(id)?.parent; ancestor; ancestor = ancestor.parent)
94400
- if (ancestor.kind === "trust-zone") return ancestor;
94401
- return void 0;
94402
- };
94403
- const trustLevelOf = (id) => {
94404
- const level = zoneOf2(id)?.attr?.value;
94405
- return level && view.trustOrder?.[level] !== void 0 ? view.trustOrder[level] : -1;
94406
- };
94407
- const lint = view.boundaryLint;
94408
- const isSecurityNode = (id) => lint !== void 0 && model.index.get(id)?.kind === lint.nodeKind;
94409
- for (const flow of model.flows) {
94410
- if (!model.index.has(flow.from) || !model.index.has(flow.to)) continue;
94411
- const crossesZone = zoneOf2(flow.from) !== zoneOf2(flow.to);
94412
- const boundaryViolation = lint !== void 0 && trustLevelOf(flow.to) > trustLevelOf(flow.from) && !isSecurityNode(flow.from) && !isSecurityNode(flow.to);
94413
- if (boundaryViolation) {
94414
- diagnostics.push({
94415
- code: lint.code,
94416
- severity: "warning",
94417
- message: lint.message,
94418
- span: flow.span,
94419
- note: `flow enters a more-trusted zone without passing a \`${lint.nodeKind}\``,
94420
- help: lint.help
94421
- });
94422
- }
94423
- if (view.crossZoneTechRecommended && crossesZone && !flow.tech?.protocol) {
94424
- diagnostics.push({
94425
- code: view.crossZoneTechRecommended.code,
94426
- severity: "warning",
94427
- message: view.crossZoneTechRecommended.message,
94428
- span: flow.span,
94429
- note: "inter-zone flow \u2014 state how the traffic is protected",
94430
- help: view.crossZoneTechRecommended.help
94431
- });
94432
- }
94433
- }
94434
- return diagnostics;
94435
- }
94436
94573
  function checkForbiddenBusinessObjects(model, view) {
94437
94574
  const diagnostics = [];
94438
94575
  for (const businessObject of model.businessObjects) {
@@ -94591,6 +94728,8 @@ function editDistance(wordA, wordB, cap) {
94591
94728
  var DEFAULT_FONT_SIZE_NODE = 12.5;
94592
94729
  var FONT_SIZE_BASE = 12.5;
94593
94730
  var CHAR_WIDTH = 0.56;
94731
+ var GLYPH_GUTTER = 26;
94732
+ var LOGO_GUTTER = 30;
94594
94733
  var fontSizes = (base) => {
94595
94734
  const scale = base / FONT_SIZE_BASE;
94596
94735
  return {
@@ -94640,11 +94779,11 @@ var flowLabelBox = (opts) => {
94640
94779
  height: measured.height + (tech ? 12 * scale : 0) + (chipNames.length ? CHIP_HEIGHT * scale : 0)
94641
94780
  };
94642
94781
  };
94643
- var nodeSize = (kind, label, fontSize = DEFAULT_FONT_SIZE_NODE) => {
94782
+ var nodeSize = (kind, label, fontSize = DEFAULT_FONT_SIZE_NODE, gutter = 0) => {
94644
94783
  const isActor = kind === "actor";
94645
94784
  const measured = measure(label, isActor ? fontSize - 1.5 : fontSize);
94646
94785
  return {
94647
- width: isActor ? Math.max(64, measured.width + 8) : Math.max(140, measured.width + 16),
94786
+ width: isActor ? Math.max(64, measured.width + 8) : Math.max(140, measured.width + 16 + gutter),
94648
94787
  height: isActor ? 56 + (label.split("\n").length - 1) * 11 : Math.max(46, measured.height + 18)
94649
94788
  };
94650
94789
  };
@@ -94672,12 +94811,13 @@ var LaneAllocator = class {
94672
94811
  return this.lanes.length - 1;
94673
94812
  }
94674
94813
  };
94675
- function foldStyle(model) {
94814
+ function foldStyle(model, view) {
94676
94815
  const businessObjectNames = new Map(model.businessObjects.map((bo) => [bo.id, bo.name]));
94677
94816
  const numbered = model.style.flowText === "numbered";
94678
94817
  const { edge, node, cont, scale } = fontSizes(model.style.font.size);
94679
94818
  return {
94680
94819
  numbered,
94820
+ glyphKinds: new Set(view.glyphKinds ?? []),
94681
94821
  edge,
94682
94822
  node,
94683
94823
  cont,
@@ -94692,7 +94832,15 @@ function foldStyle(model) {
94692
94832
  })
94693
94833
  };
94694
94834
  }
94695
- function toElkNode(element, containerFontSize, nodeFontSize) {
94835
+ function leafSize(element, style) {
94836
+ return nodeSize(
94837
+ element.kind,
94838
+ element.label ?? element.id,
94839
+ style.node,
94840
+ style.glyphKinds.has(element.kind) ? GLYPH_GUTTER : 0
94841
+ );
94842
+ }
94843
+ function toElkNode(element, style) {
94696
94844
  if (element.children.length) {
94697
94845
  const lineCount = (element.label ?? element.id).split("\n").length;
94698
94846
  return {
@@ -94703,13 +94851,13 @@ function toElkNode(element, containerFontSize, nodeFontSize) {
94703
94851
  labels: [
94704
94852
  {
94705
94853
  text: element.label ?? element.id,
94706
- ...measure(element.label ?? element.id, containerFontSize)
94854
+ ...measure(element.label ?? element.id, style.cont)
94707
94855
  }
94708
94856
  ],
94709
- children: element.children.map((child) => toElkNode(child, containerFontSize, nodeFontSize))
94857
+ children: element.children.map((child) => toElkNode(child, style))
94710
94858
  };
94711
94859
  }
94712
- const size = nodeSize(element.kind, element.label ?? element.id, nodeFontSize);
94860
+ const size = leafSize(element, style);
94713
94861
  return { id: element.id, width: size.width, height: size.height };
94714
94862
  }
94715
94863
  function walkFoldedNodes(elkNode, offset, elementById, syntheticIds) {
@@ -94788,7 +94936,7 @@ function internalFlowLabels(flow, style) {
94788
94936
  ];
94789
94937
  }
94790
94938
  function buildGroupGraph(group, style, fg) {
94791
- const node = toElkNode(group, style.cont, style.node);
94939
+ const node = toElkNode(group, style);
94792
94940
  const nodeChildren = node.children ??= [];
94793
94941
  for (const flow of fg.interFlows) {
94794
94942
  if (fg.rootOf.get(flow.from) === group) {
@@ -94846,7 +94994,7 @@ function buildGroupGraph(group, style, fg) {
94846
94994
  function layoutColumn(elements, style) {
94847
94995
  return elements.map((group) => {
94848
94996
  const blocks = group.children.map((child) => {
94849
- const size = nodeSize(child.kind, child.label ?? child.id, style.node);
94997
+ const size = leafSize(child, style);
94850
94998
  return { element: child, width: size.width, height: size.height, x: 0, y: 0 };
94851
94999
  });
94852
95000
  const columnWidth = Math.max(
@@ -94884,7 +95032,7 @@ function layoutMiddleRows(middles, middleResults, geom, style) {
94884
95032
  let yCursor = 16 + geom.gutterHeight(0);
94885
95033
  middles.forEach((element, index) => {
94886
95034
  const result = middleResults.get(element.id) ?? null;
94887
- const size = result ? { width: result.children[0].width, height: result.children[0].height } : nodeSize(element.kind, element.label ?? element.id, style.node);
95035
+ const size = result ? { width: result.children[0].width, height: result.children[0].height } : leafSize(element, style);
94888
95036
  rows.push({
94889
95037
  element,
94890
95038
  box: { x: geom.xMiddle, y: yCursor, width: size.width, height: size.height },
@@ -95048,7 +95196,7 @@ var sideMid = (box, side) => ({
95048
95196
  });
95049
95197
  async function foldedLayout(model, view, elk) {
95050
95198
  const roots = model.elements;
95051
- const style = foldStyle(model);
95199
+ const style = foldStyle(model, view);
95052
95200
  const partitionOf = (element) => view.partitions[element.kind] ?? 1;
95053
95201
  const sources = roots.filter((element) => partitionOf(element) === 0);
95054
95202
  const middles = roots.filter((element) => partitionOf(element) === 1);
@@ -95095,7 +95243,7 @@ async function foldedLayout(model, view, elk) {
95095
95243
  const widthSource = Math.max(0, ...sourceColumns.map((col) => col.width));
95096
95244
  const widthMiddle = Math.max(
95097
95245
  ...middles.map(
95098
- (element) => element.children.length ? middleResults.get(element.id).children[0].width : nodeSize(element.kind, element.label ?? element.id, style.node).width
95246
+ (element) => element.children.length ? middleResults.get(element.id).children[0].width : leafSize(element, style).width
95099
95247
  )
95100
95248
  );
95101
95249
  const widthSink = Math.max(0, ...sinkColumns.map((col) => col.width));
@@ -95954,6 +96102,27 @@ function compactVertical(scene) {
95954
96102
  }
95955
96103
  scene.height = Math.ceil(heightAfter + bottomMargin);
95956
96104
  }
96105
+ var CANVAS_MARGIN = 10;
96106
+ function fitCanvas(scene) {
96107
+ let maxX = 0;
96108
+ let maxY = 0;
96109
+ for (const node of scene.nodes) {
96110
+ maxX = Math.max(maxX, node.x + node.width);
96111
+ maxY = Math.max(maxY, node.y + node.height);
96112
+ }
96113
+ for (const edge of scene.edges) {
96114
+ for (const point of edge.pts) {
96115
+ maxX = Math.max(maxX, point.x);
96116
+ maxY = Math.max(maxY, point.y);
96117
+ }
96118
+ for (const label of edge.labels) {
96119
+ maxX = Math.max(maxX, label.x + label.width);
96120
+ maxY = Math.max(maxY, label.y + label.height);
96121
+ }
96122
+ }
96123
+ scene.width = Math.max(scene.width, Math.ceil(maxX) + CANVAS_MARGIN);
96124
+ scene.height = Math.max(scene.height, Math.ceil(maxY) + CANVAS_MARGIN);
96125
+ }
95957
96126
 
95958
96127
  // src/readability.ts
95959
96128
  var FAN_REACH = 48;
@@ -96085,8 +96254,7 @@ function labelSeatable(blockers, pts, label) {
96085
96254
  function scanLabelSeats(ctx, subj, profile) {
96086
96255
  for (const label of subj.edge.labels) {
96087
96256
  if (!label.width || !label.height) continue;
96088
- if (!labelSeatable(ctx.blockers, subj.pts, label))
96089
- profile.set(`unlabelled:${subj.edge.id}`, 1);
96257
+ if (!labelSeatable(ctx.blockers, subj.pts, label)) profile.set(`unlabelled:${subj.edge.id}`, 1);
96090
96258
  }
96091
96259
  }
96092
96260
  function scanArrowRoom(subj, profile) {
@@ -96159,8 +96327,7 @@ function scanCrossings(a, b, key, profile) {
96159
96327
  if (!end) continue;
96160
96328
  const dx = Math.max(0, end.node.x - hit.x, hit.x - (end.node.x + end.node.width));
96161
96329
  const dy = Math.max(0, end.node.y - hit.y, hit.y - (end.node.y + end.node.height));
96162
- if (dx * dx + dy * dy <= FAN_REACH * FAN_REACH)
96163
- profile.set(`fan:${key}@${end.node.id}`, 2);
96330
+ if (dx * dx + dy * dy <= FAN_REACH * FAN_REACH) profile.set(`fan:${key}@${end.node.id}`, 2);
96164
96331
  }
96165
96332
  }
96166
96333
  }
@@ -96188,8 +96355,7 @@ function scanSharedSeats(a, b, key, profile) {
96188
96355
  }
96189
96356
  const side = vertical ? seatA.node.height : seatA.node.width;
96190
96357
  const need = Math.min(12, (side - 6) / 2);
96191
- if (apart < need * 0.8)
96192
- profile.set(`tight:${key}@${seatA.node.id}|${seatA.side}`, 4);
96358
+ if (apart < need * 0.8) profile.set(`tight:${key}@${seatA.node.id}|${seatA.side}`, 4);
96193
96359
  }
96194
96360
  }
96195
96361
  function scanPairs(ctx, subj, query) {
@@ -96204,7 +96370,11 @@ function scanPairs(ctx, subj, query) {
96204
96370
  if (ids.has(other.id) && other.id < subj.edge.id) continue;
96205
96371
  const overridden = overrides.has(other.id);
96206
96372
  if (farApart(myBounds, ctx.boundsFor(other, theirs, overridden))) continue;
96207
- const them = { edge: other, pts: theirs, ends: ctx.endsFor(other, theirs, overridden) };
96373
+ const them = {
96374
+ edge: other,
96375
+ pts: theirs,
96376
+ ends: ctx.endsFor(other, theirs, overridden)
96377
+ };
96208
96378
  const key = pair(subj.edge.id, other.id);
96209
96379
  scanCrossings(subj, them, key, profile);
96210
96380
  scanParallelRuns(mine, ctx.runsFor(other, theirs, overridden), key, profile);
@@ -96271,6 +96441,7 @@ function ladderVerdict(before, after) {
96271
96441
  }
96272
96442
 
96273
96443
  // src/edge-tidy.ts
96444
+ var sideFixed = (edge, end) => !!(edge.pinned?.[end] || edge.hubSided?.[end]);
96274
96445
  var ORTHOGONAL_EPSILON = 0.5;
96275
96446
  var MIN_HUG_SPAN = 24;
96276
96447
  var CHANNEL_INSET = 24;
@@ -98664,8 +98835,8 @@ function optimiseRoutes(scene, titleBoxes = [], folded = false) {
98664
98835
  const subject = { ends, edge };
98665
98836
  const out = [];
98666
98837
  const seen = /* @__PURE__ */ new Set();
98667
- const aSides = edge.pinned?.start ? [ends[0].side] : SIDES;
98668
- const bSides = edge.pinned?.end ? [ends[1].side] : SIDES;
98838
+ const aSides = sideFixed(edge, "start") ? [ends[0].side] : SIDES;
98839
+ const bSides = sideFixed(edge, "end") ? [ends[1].side] : SIDES;
98669
98840
  for (const aSide of aSides)
98670
98841
  for (const bSide of bSides) {
98671
98842
  const sides = { a: aSide, b: bSide };
@@ -99001,7 +99172,8 @@ function computeIngressExternalElements(model) {
99001
99172
  }
99002
99173
  var orderOption = (element) => element.order ? { "elk.position": `(0,${element.order.value})` } : {};
99003
99174
  var semiInteractiveOption = (children) => children.some((child) => child.order) ? { "elk.layered.crossingMinimization.semiInteractive": "true" } : {};
99004
- function toElkNode2(element, compact, fonts, root = false) {
99175
+ function toElkNode2(element, sizing, root = false) {
99176
+ const { compact, fonts, glyphKinds } = sizing;
99005
99177
  const { cont: containerFontSize, node: nodeFontSize } = fonts;
99006
99178
  if (element.children.length) {
99007
99179
  const lineCount = (element.label ?? element.id).split("\n").length;
@@ -99018,17 +99190,16 @@ function toElkNode2(element, compact, fonts, root = false) {
99018
99190
  ...measure(element.label ?? element.id, containerFontSize)
99019
99191
  }
99020
99192
  ],
99021
- children: element.children.map(
99022
- (child) => toElkNode2(child, compact, fonts)
99023
- )
99193
+ children: element.children.map((child) => toElkNode2(child, sizing))
99024
99194
  };
99025
99195
  }
99026
99196
  const measured = measure(element.label ?? element.id, nodeFontSize);
99027
99197
  const isActor = element.kind === "actor";
99198
+ const gutter = (glyphKinds.has(element.kind) ? GLYPH_GUTTER : 0) + (element.logo ? LOGO_GUTTER : 0);
99028
99199
  return {
99029
99200
  id: element.id,
99030
99201
  ...element.order && !root ? { layoutOptions: orderOption(element) } : {},
99031
- width: isActor ? Math.max(64, measure(element.label ?? element.id, nodeFontSize - 1.5).width + 8) : Math.max(compact ? 98 : 108, measured.width + (compact ? 10 : 12)),
99202
+ width: isActor ? Math.max(64, measure(element.label ?? element.id, nodeFontSize - 1.5).width + 8) : Math.max(compact ? 98 : 108, measured.width + (compact ? 10 : 12) + gutter),
99032
99203
  height: isActor ? 54 + ((element.label ?? element.id).split("\n").length - 1) * 11 : Math.max(compact ? 36 : 38, measured.height + (compact ? 10 : 12))
99033
99204
  };
99034
99205
  }
@@ -99187,20 +99358,20 @@ function selectionExtras(scene, model) {
99187
99358
  }
99188
99359
  return extra;
99189
99360
  }
99190
- function relayoutVerdict(before, after) {
99361
+ function tallyProfile(profile) {
99191
99362
  const normalize = (key) => key.replace(/@[-\d.,]+$/, "").replace(/:\d+(?=@|$)/, "");
99192
- const tally = (profile) => {
99193
- const out = /* @__PURE__ */ new Map();
99194
- for (const [key, tier] of profile) {
99195
- const id = normalize(key);
99196
- const entry = out.get(id) ?? { tier, count: 0 };
99197
- entry.count++;
99198
- out.set(id, entry);
99199
- }
99200
- return out;
99201
- };
99202
- const was = tally(before);
99203
- const now = tally(after);
99363
+ const out = /* @__PURE__ */ new Map();
99364
+ for (const [key, tier] of profile) {
99365
+ const id = normalize(key);
99366
+ const entry = out.get(id) ?? { tier, count: 0 };
99367
+ entry.count++;
99368
+ out.set(id, entry);
99369
+ }
99370
+ return out;
99371
+ }
99372
+ function relayoutVerdict(before, after) {
99373
+ const was = tallyProfile(before);
99374
+ const now = tallyProfile(after);
99204
99375
  for (let tier = 0; tier < 5; tier++) {
99205
99376
  let gained = false;
99206
99377
  let lost = false;
@@ -99216,6 +99387,62 @@ function relayoutVerdict(before, after) {
99216
99387
  }
99217
99388
  return -1;
99218
99389
  }
99390
+ function noLadderRegression(before, after) {
99391
+ const perTier = (profile) => {
99392
+ const totals = [0, 0, 0, 0, 0];
99393
+ for (const { tier, count } of tallyProfile(profile).values()) totals[tier] += count;
99394
+ return totals;
99395
+ };
99396
+ const was = perTier(before);
99397
+ const now = perTier(after);
99398
+ return now.every((count, tier) => count <= was[tier]);
99399
+ }
99400
+ function nodeCoverage(scene) {
99401
+ if (!scene.nodes.length) return 1;
99402
+ const left = Math.min(...scene.nodes.map((n) => n.x));
99403
+ const top = Math.min(...scene.nodes.map((n) => n.y));
99404
+ const right = Math.max(...scene.nodes.map((n) => n.x + n.width));
99405
+ const bottom = Math.max(...scene.nodes.map((n) => n.y + n.height));
99406
+ const width = right - left;
99407
+ const height = bottom - top;
99408
+ if (width <= 0 || height <= 0) return 1;
99409
+ const COLUMNS = 32;
99410
+ const ROWS = 16;
99411
+ let covered = 0;
99412
+ for (let row = 0; row < ROWS; row++) {
99413
+ for (let column = 0; column < COLUMNS; column++) {
99414
+ const cellLeft = left + column * width / COLUMNS;
99415
+ const cellRight = left + (column + 1) * width / COLUMNS;
99416
+ const cellTop = top + row * height / ROWS;
99417
+ const cellBottom = top + (row + 1) * height / ROWS;
99418
+ if (scene.nodes.some(
99419
+ (n) => n.x < cellRight && n.x + n.width > cellLeft && n.y < cellBottom && n.y + n.height > cellTop
99420
+ ))
99421
+ covered++;
99422
+ }
99423
+ }
99424
+ return covered / (COLUMNS * ROWS);
99425
+ }
99426
+ async function denserLayout(base, elkPass) {
99427
+ const densitySpecs = [
99428
+ { minLayers: true, dense: true },
99429
+ { placement: "LINEAR_SEGMENTS", dense: true }
99430
+ ];
99431
+ const laidOut = await Promise.allSettled(densitySpecs.map((spec) => elkPass.layout(spec)));
99432
+ const areaOf = (scene) => scene.width * scene.height;
99433
+ const baseProfile = elkPass.profile(base);
99434
+ let bestArea = areaOf(base) * DENSITY_GAIN;
99435
+ let winner = null;
99436
+ for (const [index, settled] of laidOut.entries()) {
99437
+ if (settled.status !== "fulfilled") continue;
99438
+ const candidate = elkPass.toScene(settled.value);
99439
+ if (areaOf(candidate) > bestArea) continue;
99440
+ if (!noLadderRegression(baseProfile, elkPass.profile(candidate))) continue;
99441
+ bestArea = areaOf(candidate);
99442
+ winner = { scene: candidate, options: densitySpecs[index] };
99443
+ }
99444
+ return winner;
99445
+ }
99219
99446
  var centerOf = (n) => ({ x: n.x + n.width / 2, y: n.y + n.height / 2 });
99220
99447
  function sideToward(from, to) {
99221
99448
  const dx = centerOf(to).x - centerOf(from).x;
@@ -99277,13 +99504,16 @@ function constrainPorts(graph, scene, flagged, model) {
99277
99504
  elkNode.ports = [...elkNode.ports ?? [], ...ports];
99278
99505
  }
99279
99506
  }
99507
+ var DENSE_ENOUGH = 0.6;
99508
+ var DENSITY_GAIN = 0.95;
99280
99509
  var INGRESS_PARTITION = -1;
99510
+ var DEFAULT_INGRESS_KINDS = ["actor", "actor-group"];
99281
99511
  var EGRESS_PARTITION = 900;
99282
99512
  var COMPACT_WRAP = 10;
99283
99513
  var SLOT_SCALE = 1e3;
99284
99514
  function elkPartitionOf(element, index, view, ingressExternal) {
99285
99515
  if (!view.partitionByOrder) return view.partitions[element.kind] ?? 1;
99286
- if (element.kind === "actor" || element.kind === "actor-group") return INGRESS_PARTITION;
99516
+ if ((view.ingressKinds ?? DEFAULT_INGRESS_KINDS).includes(element.kind)) return INGRESS_PARTITION;
99287
99517
  if (element.kind === "external")
99288
99518
  return ingressExternal.has(element.id) ? INGRESS_PARTITION : EGRESS_PARTITION;
99289
99519
  if (view.partitions[element.kind] !== void 0) return 90 + view.partitions[element.kind];
@@ -99307,16 +99537,13 @@ function readingSlots(model, view, ingressExternal) {
99307
99537
  const lowestOf = /* @__PURE__ */ new Map();
99308
99538
  for (const band of new Set(bandOf.values())) {
99309
99539
  const members = model.elements.filter((element) => bandOf.get(element.id) === band);
99310
- const declared = [
99311
- ...new Set(members.filter((m) => m.order).map((m) => m.order.value))
99312
- ].sort((a, b) => a - b);
99540
+ const declared = [...new Set(members.filter((m) => m.order).map((m) => m.order.value))].sort(
99541
+ (a, b) => a - b
99542
+ );
99313
99543
  const lowest = declared.length ? 1 : 0;
99314
99544
  lowestOf.set(band, lowest);
99315
99545
  for (const member of members)
99316
- slotOf.set(
99317
- member.id,
99318
- member.order ? declared.indexOf(member.order.value) + 1 : lowest
99319
- );
99546
+ slotOf.set(member.id, member.order ? declared.indexOf(member.order.value) + 1 : lowest);
99320
99547
  }
99321
99548
  const ordered = new Set(model.elements.filter((element) => element.order).map((e) => e.id));
99322
99549
  for (let pass = 0; pass < model.elements.length; pass++) {
@@ -99335,13 +99562,15 @@ function readingSlots(model, view, ingressExternal) {
99335
99562
  }
99336
99563
  return slotOf;
99337
99564
  }
99565
+ function elkEnds(flow) {
99566
+ return laidOutReversed(flow) ? { sources: [flow.to], targets: [flow.from] } : { sources: [flow.from], targets: [flow.to] };
99567
+ }
99338
99568
  function elkFlowEdge(flow, ctx, labelWrap) {
99339
99569
  const { compact, numbered, fonts, businessObjectName } = ctx;
99340
99570
  if (numbered)
99341
99571
  return {
99342
99572
  id: flow.id,
99343
- sources: [flow.from],
99344
- targets: [flow.to],
99573
+ ...elkEnds(flow),
99345
99574
  labels: [
99346
99575
  {
99347
99576
  text: String(parseInt(flow.id.slice(1), 10)),
@@ -99366,8 +99595,7 @@ function elkFlowEdge(flow, ctx, labelWrap) {
99366
99595
  });
99367
99596
  return {
99368
99597
  id: flow.id,
99369
- sources: [flow.from],
99370
- targets: [flow.to],
99598
+ ...elkEnds(flow),
99371
99599
  labels: text || chips.length ? [{ text, ...labelBox }] : []
99372
99600
  };
99373
99601
  }
@@ -99377,8 +99605,32 @@ var SIDE_TO_ELK = {
99377
99605
  top: "NORTH",
99378
99606
  bottom: "SOUTH"
99379
99607
  };
99380
- function applyDeclaredPorts(graph, model) {
99381
- const pinned = model.flows.filter((flow) => flow.fromSide || flow.toSide);
99608
+ function laidOutReversed(flow) {
99609
+ return flow.fromRole?.value === "consumer" || flow.toRole?.value === "producer";
99610
+ }
99611
+ function roleCap(role) {
99612
+ if (!role) return null;
99613
+ return role.value === "producer" ? "left" : "right";
99614
+ }
99615
+ function hubFlowSides(model, view) {
99616
+ const derived = /* @__PURE__ */ new Map();
99617
+ const hubKinds = new Set(view.hubKinds ?? []);
99618
+ if (!hubKinds.size) return derived;
99619
+ const hubs = new Set(
99620
+ indexElementsById(model.elements).filter(([, element]) => hubKinds.has(element.kind)).map(([id]) => id)
99621
+ );
99622
+ if (!hubs.size) return derived;
99623
+ for (const flow of model.flows) {
99624
+ const sides = {};
99625
+ if (hubs.has(flow.to) && !flow.toSide) sides.to = roleCap(flow.fromRole) ?? "left";
99626
+ if (hubs.has(flow.from) && !flow.fromSide) sides.from = roleCap(flow.toRole) ?? "right";
99627
+ if (sides.to || sides.from) derived.set(flow.id, sides);
99628
+ }
99629
+ return derived;
99630
+ }
99631
+ var sideRequest = (side) => side ? { value: side } : void 0;
99632
+ function applyDeclaredPorts(graph, model, derived) {
99633
+ const pinned = model.flows.filter((flow) => flow.fromSide || flow.toSide || derived.has(flow.id));
99382
99634
  if (!pinned.length) return;
99383
99635
  const elkById = /* @__PURE__ */ new Map();
99384
99636
  const register = (node) => {
@@ -99389,14 +99641,15 @@ function applyDeclaredPorts(graph, model) {
99389
99641
  for (const flow of pinned) {
99390
99642
  const elkEdge = (graph.edges ?? []).find((edge) => edge.id === flow.id);
99391
99643
  if (!elkEdge) continue;
99644
+ const hub = derived.get(flow.id);
99392
99645
  for (const [role, declared, nodeId] of [
99393
- ["out", flow.fromSide, flow.from],
99394
- ["in", flow.toSide, flow.to]
99646
+ ["out", flow.fromSide ?? sideRequest(hub?.from), flow.from],
99647
+ ["in", flow.toSide ?? sideRequest(hub?.to), flow.to]
99395
99648
  ]) {
99396
99649
  if (!declared) continue;
99397
99650
  const elkNode = elkById.get(nodeId);
99398
99651
  if (!elkNode) continue;
99399
- const portId = `${flow.id}#${role}`;
99652
+ const portId = `${flow.id}#${role === "out" !== laidOutReversed(flow) ? "out" : "in"}`;
99400
99653
  elkNode.ports = [
99401
99654
  ...elkNode.ports ?? [],
99402
99655
  {
@@ -99407,7 +99660,7 @@ function applyDeclaredPorts(graph, model) {
99407
99660
  }
99408
99661
  ];
99409
99662
  elkNode.layoutOptions = { ...elkNode.layoutOptions, "elk.portConstraints": "FIXED_SIDE" };
99410
- if (role === "out") elkEdge.sources = [portId];
99663
+ if (role === "out" !== laidOutReversed(flow)) elkEdge.sources = [portId];
99411
99664
  else elkEdge.targets = [portId];
99412
99665
  }
99413
99666
  }
@@ -99452,6 +99705,7 @@ function attachSideDiagnostics(scene, model) {
99452
99705
  }
99453
99706
  function buildElkGraph(ctx, direction, options) {
99454
99707
  const { model, view, ingressExternal, slotOf, compact, numbered, fonts } = ctx;
99708
+ const glyphKinds = new Set(view.glyphKinds ?? []);
99455
99709
  const graph = {
99456
99710
  id: "root",
99457
99711
  layoutOptions: {
@@ -99468,7 +99722,7 @@ function buildElkGraph(ctx, direction, options) {
99468
99722
  "elk.edgeRouting": "ORTHOGONAL",
99469
99723
  "elk.partitioning.activate": "true",
99470
99724
  "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
99471
- "elk.layered.compaction.postCompaction.strategy": "EDGE_LENGTH",
99725
+ "elk.layered.compaction.postCompaction.strategy": options?.postCompaction ?? "EDGE_LENGTH",
99472
99726
  "elk.layered.feedbackEdges": "true",
99473
99727
  "elk.layered.thoroughness": "30",
99474
99728
  "elk.separateConnectedComponents": "false",
@@ -99487,25 +99741,38 @@ function buildElkGraph(ctx, direction, options) {
99487
99741
  "elk.spacing.edgeNode": "18",
99488
99742
  "elk.layered.thoroughness": "80",
99489
99743
  "elk.layered.nodePlacement.favorStraightEdges": "true"
99490
- } : {}
99744
+ } : {},
99745
+ // Spread last, after every spacing default above, because overriding them
99746
+ // is the whole point — a density candidate that lands before them is
99747
+ // silently shadowed and lays out exactly like the layout it was meant to
99748
+ // beat.
99749
+ ...options?.dense ? {
99750
+ "elk.layered.spacing.nodeNodeBetweenLayers": "14",
99751
+ "elk.spacing.nodeNode": "10",
99752
+ "elk.spacing.edgeEdge": "8",
99753
+ "elk.spacing.edgeNode": "9"
99754
+ } : {},
99755
+ ...options?.placement ? { "elk.layered.nodePlacement.strategy": options.placement } : {}
99491
99756
  },
99492
99757
  children: model.elements.map((element, index) => {
99493
- const elkNode = toElkNode2(element, compact, fonts, true);
99758
+ const elkNode = toElkNode2(element, { compact, fonts, glyphKinds }, true);
99494
99759
  const band = elkPartitionOf(element, index, view, ingressExternal);
99495
99760
  const slot = slotOf.get(element.id);
99496
99761
  elkNode.layoutOptions = {
99497
99762
  ...elkNode.layoutOptions,
99498
99763
  // Without an `order:` anywhere the band is emitted as it always was, so
99499
99764
  // the drawing is byte-identical to one from before the hint existed.
99500
- "elk.partitioning.partition": String(
99501
- slot === void 0 ? band : band * SLOT_SCALE + slot
99502
- )
99765
+ "elk.partitioning.partition": String(slot === void 0 ? band : band * SLOT_SCALE + slot)
99503
99766
  };
99504
99767
  return elkNode;
99505
99768
  }),
99506
99769
  edges: model.flows.map((flow) => elkFlowEdge(flow, ctx, options?.labelWrap))
99507
99770
  };
99508
- applyDeclaredPorts(graph, model);
99771
+ applyDeclaredPorts(
99772
+ graph,
99773
+ model,
99774
+ options?.hubPorts === false ? /* @__PURE__ */ new Map() : hubFlowSides(model, view)
99775
+ );
99509
99776
  return graph;
99510
99777
  }
99511
99778
  function recordRepairs(scene, routesBefore) {
@@ -99518,8 +99785,127 @@ function recordRepairs(scene, routesBefore) {
99518
99785
  if (moved) edge.repairedFrom = original;
99519
99786
  }
99520
99787
  }
99788
+ function segmentsCross2(a1, a2, b1, b2) {
99789
+ const aH = Math.abs(a1.y - a2.y) < 0.5;
99790
+ const bH = Math.abs(b1.y - b2.y) < 0.5;
99791
+ if (aH === bH) return false;
99792
+ const [h1, h2, v1, v2] = aH ? [a1, a2, b1, b2] : [b1, b2, a1, a2];
99793
+ const hy = h1.y;
99794
+ const vx = v1.x;
99795
+ return vx > Math.min(h1.x, h2.x) + 0.5 && vx < Math.max(h1.x, h2.x) - 0.5 && hy > Math.min(v1.y, v2.y) + 0.5 && hy < Math.max(v1.y, v2.y) - 0.5;
99796
+ }
99797
+ function shortJogs(edges) {
99798
+ let total = 0;
99799
+ for (const edge of edges)
99800
+ for (let i = 1; i + 2 < edge.pts.length; i++) {
99801
+ const run = Math.abs(edge.pts[i + 1].x - edge.pts[i].x) + Math.abs(edge.pts[i + 1].y - edge.pts[i].y);
99802
+ if (run > 0 && run <= 20) total++;
99803
+ }
99804
+ return total;
99805
+ }
99806
+ function crossingsAround(scene, subject) {
99807
+ let total = 0;
99808
+ const subjectIds = new Set(subject.map((edge) => edge.id));
99809
+ for (const edge of subject)
99810
+ for (const other of scene.edges) {
99811
+ if (other.id === edge.id) continue;
99812
+ if (subjectIds.has(other.id) && other.id < edge.id) continue;
99813
+ for (let i = 1; i < edge.pts.length; i++)
99814
+ for (let j = 1; j < other.pts.length; j++)
99815
+ if (segmentsCross2(edge.pts[i - 1], edge.pts[i], other.pts[j - 1], other.pts[j])) total++;
99816
+ }
99817
+ return total;
99818
+ }
99819
+ function leafBoxesOf(scene) {
99820
+ return scene.nodes.filter((node) => !node.container);
99821
+ }
99822
+ function crossesLeaf(edge, leaves, attached) {
99823
+ for (let i = 1; i < edge.pts.length; i++) {
99824
+ const a = edge.pts[i - 1];
99825
+ const b = edge.pts[i];
99826
+ const horizontal = Math.abs(a.y - b.y) < 0.5;
99827
+ const vertical = Math.abs(a.x - b.x) < 0.5;
99828
+ if (!horizontal && !vertical) continue;
99829
+ const at = horizontal ? a.y : a.x;
99830
+ const lo = horizontal ? Math.min(a.x, b.x) : Math.min(a.y, b.y);
99831
+ const hi = horizontal ? Math.max(a.x, b.x) : Math.max(a.y, b.y);
99832
+ for (const node of leaves) {
99833
+ if (attached.has(node.id)) continue;
99834
+ const struck = horizontal ? at > node.y + 1 && at < node.y + node.height - 1 && lo < node.x + node.width - 1 && hi > node.x + 1 : at > node.x + 1 && at < node.x + node.width - 1 && lo < node.y + node.height - 1 && hi > node.y + 1;
99835
+ if (struck) return true;
99836
+ }
99837
+ }
99838
+ return false;
99839
+ }
99840
+ function snapLanes(scene, laneOf, axis) {
99841
+ if (!laneOf.size) return;
99842
+ const leaves = leafBoxesOf(scene);
99843
+ const tilted = (edges) => edges.reduce(
99844
+ (total, edge) => total + edge.pts.filter(
99845
+ (point, i) => i > 0 && Math.abs(point.x - edge.pts[i - 1].x) >= 0.5 && Math.abs(point.y - edge.pts[i - 1].y) >= 0.5
99846
+ ).length,
99847
+ 0
99848
+ );
99849
+ const touching = /* @__PURE__ */ new Map();
99850
+ const attachedTo = /* @__PURE__ */ new Map();
99851
+ for (const edge of scene.edges) {
99852
+ const ends = /* @__PURE__ */ new Set();
99853
+ for (const point of [edge.pts[0], edge.pts[edge.pts.length - 1]]) {
99854
+ if (!point) continue;
99855
+ for (const node of scene.nodes)
99856
+ if (point.x >= node.x - 1 && point.x <= node.x + node.width + 1 && point.y >= node.y - 1 && point.y <= node.y + node.height + 1) {
99857
+ ends.add(node.id);
99858
+ if (!touching.has(node.id)) touching.set(node.id, []);
99859
+ touching.get(node.id).push(edge);
99860
+ }
99861
+ }
99862
+ attachedTo.set(edge.id, ends);
99863
+ }
99864
+ const byLane = /* @__PURE__ */ new Map();
99865
+ for (const node of scene.nodes) {
99866
+ const lane = laneOf.get(node.id);
99867
+ if (lane === void 0) continue;
99868
+ if (!byLane.has(lane)) byLane.set(lane, []);
99869
+ byLane.get(lane).push(node);
99870
+ }
99871
+ for (const members of [...byLane.entries()].sort((a, b) => a[0] - b[0]).map((e) => e[1])) {
99872
+ if (members.length < 2) continue;
99873
+ const target = Math.max(...members.map((node) => node[axis]));
99874
+ for (const node of members) {
99875
+ const delta = target - node[axis];
99876
+ if (!delta) continue;
99877
+ const box = { x: node.x, y: node.y, w: node.width, h: node.height };
99878
+ const edges = touching.get(node.id) ?? [];
99879
+ const before = edges.map((edge) => edge.pts.map((point) => ({ ...point })));
99880
+ const crossingsBefore = crossingsAround(scene, edges);
99881
+ const jogsBefore = shortJogs(edges);
99882
+ const tiltsBefore = tilted(edges);
99883
+ node[axis] = target;
99884
+ for (const edge of edges)
99885
+ for (const point of edge.pts)
99886
+ if (point.x >= box.x - 1 && point.x <= box.x + box.w + 1 && point.y >= box.y - 1 && point.y <= box.y + box.h + 1)
99887
+ point[axis] += delta;
99888
+ const broke = crossingsAround(scene, edges) > crossingsBefore || shortJogs(edges) > jogsBefore || tilted(edges) > tiltsBefore || edges.some((edge) => crossesLeaf(edge, leaves, attachedTo.get(edge.id) ?? /* @__PURE__ */ new Set())) || // Containers included: a lane member is top-level, so it has no
99889
+ // legitimate container ancestor and may not land on one.
99890
+ scene.nodes.some(
99891
+ (other) => other !== node && !node.container && other.x < node.x + node.width && node.x < other.x + other.width && other.y < node.y + node.height && node.y < other.y + other.height
99892
+ );
99893
+ if (broke) {
99894
+ node[axis] = target - delta;
99895
+ edges.forEach((edge, i) => {
99896
+ edge.pts = before[i];
99897
+ });
99898
+ }
99899
+ }
99900
+ }
99901
+ const maxX = Math.max(...scene.nodes.map((node) => node.x + node.width));
99902
+ const maxY = Math.max(...scene.nodes.map((node) => node.y + node.height));
99903
+ scene.width = Math.max(scene.width, Math.ceil(maxX) + 10);
99904
+ scene.height = Math.max(scene.height, Math.ceil(maxY) + 10);
99905
+ }
99521
99906
  function runGeometryPasses(scene, model, options) {
99522
99907
  const { numbered, sideways } = options;
99908
+ if (options.laneOf) snapLanes(scene, options.laneOf, sideways ? "y" : "x");
99523
99909
  const titleBoxes = titleBoxesOf(scene, model);
99524
99910
  if (sideways) transpose(scene, titleBoxes);
99525
99911
  rerouteDetours(scene, model, numbered, titleBoxes);
@@ -99538,6 +99924,99 @@ function runGeometryPasses(scene, model, options) {
99538
99924
  clearSideHugs(scene, settledTitles);
99539
99925
  anchorFlowLabels(scene, settledTitles);
99540
99926
  swapCrossingSiblingSeats(scene);
99927
+ fitCanvas(scene);
99928
+ }
99929
+ function laneAssignment(model, view) {
99930
+ const laneOf = /* @__PURE__ */ new Map();
99931
+ const laneKinds = new Set(view.laneKinds ?? []);
99932
+ if (!laneKinds.size || model.style.compact) return laneOf;
99933
+ const rootOf = /* @__PURE__ */ new Map();
99934
+ const walk = (element, root) => {
99935
+ rootOf.set(element.id, root);
99936
+ for (const child of element.children ?? []) walk(child, root);
99937
+ };
99938
+ for (const element of model.elements) walk(element, element.id);
99939
+ const adjacency = /* @__PURE__ */ new Map();
99940
+ for (const flow of model.flows) {
99941
+ const from = rootOf.get(flow.from);
99942
+ const to = rootOf.get(flow.to);
99943
+ if (!from || !to || from === to) continue;
99944
+ if (!adjacency.has(from)) adjacency.set(from, /* @__PURE__ */ new Set());
99945
+ adjacency.get(from).add(to);
99946
+ }
99947
+ const reaches = (from, to) => {
99948
+ const seen = /* @__PURE__ */ new Set([from]);
99949
+ const queue = [from];
99950
+ while (queue.length) {
99951
+ for (const next of adjacency.get(queue.pop()) ?? []) {
99952
+ if (next === to) return true;
99953
+ if (!seen.has(next)) {
99954
+ seen.add(next);
99955
+ queue.push(next);
99956
+ }
99957
+ }
99958
+ }
99959
+ return false;
99960
+ };
99961
+ const byKind = /* @__PURE__ */ new Map();
99962
+ for (const element of model.elements) {
99963
+ if (!laneKinds.has(element.kind)) continue;
99964
+ if ((element.children ?? []).length) continue;
99965
+ if (!byKind.has(element.kind)) byKind.set(element.kind, []);
99966
+ byKind.get(element.kind).push(element);
99967
+ }
99968
+ const pinned = /* @__PURE__ */ new Set();
99969
+ for (const flow of model.flows) {
99970
+ if (!flow.fromSide && !flow.toSide) continue;
99971
+ pinned.add(rootOf.get(flow.from) ?? flow.from);
99972
+ pinned.add(rootOf.get(flow.to) ?? flow.to);
99973
+ }
99974
+ let laneId = 0;
99975
+ for (const members of byKind.values()) {
99976
+ if (members.length < 2) continue;
99977
+ if (members.some((member) => pinned.has(member.id))) continue;
99978
+ if (members.some((member) => member.order)) continue;
99979
+ if (members.some((a) => members.some((b) => a !== b && reaches(a.id, b.id)))) continue;
99980
+ laneId++;
99981
+ for (const member of members) laneOf.set(member.id, laneId);
99982
+ }
99983
+ return laneOf;
99984
+ }
99985
+ var ASPECT_TARGETS = {
99986
+ slide: 16 / 9,
99987
+ page: 0.71
99988
+ };
99989
+ async function withHubPortFallback(elk, makeGraph, direction, options) {
99990
+ const run = async (spec) => await elk.layout(makeGraph(direction, spec));
99991
+ const rungs = options?.hubPorts === false ? [] : [
99992
+ { ...options, postCompaction: "EDGE_LENGTH_CONSTRAINT_LOCKING" },
99993
+ { ...options, postCompaction: "NONE" },
99994
+ { ...options, hubPorts: false }
99995
+ ];
99996
+ try {
99997
+ return { result: await run(options), options };
99998
+ } catch (error) {
99999
+ for (const [index, rung] of rungs.entries()) {
100000
+ try {
100001
+ return { result: await run(rung), options: rung };
100002
+ } catch (rungError) {
100003
+ if (index === rungs.length - 1) throw rungError;
100004
+ }
100005
+ }
100006
+ throw error;
100007
+ }
100008
+ }
100009
+ function markDeclaredTerminals(edges, model, view, hubPorts) {
100010
+ const pinnedFlows = new Map(
100011
+ model.flows.filter((flow) => flow.fromSide || flow.toSide).map((flow) => [flow.id, { start: !!flow.fromSide, end: !!flow.toSide }])
100012
+ );
100013
+ const hubSides = hubPorts ? hubFlowSides(model, view) : /* @__PURE__ */ new Map();
100014
+ for (const edge of edges) {
100015
+ const pinned = pinnedFlows.get(edge.id);
100016
+ if (pinned) edge.pinned = { ...pinned };
100017
+ const hub = hubSides.get(edge.id);
100018
+ if (hub) edge.hubSided = { start: !!hub.from, end: !!hub.to };
100019
+ }
99541
100020
  }
99542
100021
  async function layout(model, view) {
99543
100022
  const elk = await getElk();
@@ -99551,10 +100030,6 @@ async function layout(model, view) {
99551
100030
  scale: fontScale
99552
100031
  } = fontSizes(model.style.font.size);
99553
100032
  const disposition = model.style.disposition;
99554
- const ASPECT_TARGETS = {
99555
- slide: 16 / 9,
99556
- page: 0.71
99557
- };
99558
100033
  const aspectTarget = ASPECT_TARGETS[disposition];
99559
100034
  const ingressExternalElements = view.partitionByOrder ? computeIngressExternalElements(model) : /* @__PURE__ */ new Set();
99560
100035
  const graphContext = {
@@ -99574,7 +100049,7 @@ async function layout(model, view) {
99574
100049
  };
99575
100050
  const makeGraph = (direction, options) => buildElkGraph(graphContext, direction, options);
99576
100051
  const kindOf = new Map(indexElementsById(model.elements));
99577
- const sceneFromResult = (result2, layoutMs2) => {
100052
+ const sceneFromResult = (result2, layoutMs2, laidOutWith, lanes = true) => {
99578
100053
  const origins = {
99579
100054
  root: { x: 0, y: 0 }
99580
100055
  };
@@ -99582,13 +100057,9 @@ async function layout(model, view) {
99582
100057
  const nodes = walkedNodes.map((walked) => walked.node);
99583
100058
  for (const walked of walkedNodes) origins[walked.id] = { x: walked.x, y: walked.y };
99584
100059
  const edges = collectSceneEdges(result2, origins, numbered, edgeFontSize);
99585
- const pinnedFlows = new Map(
99586
- model.flows.filter((flow) => flow.fromSide || flow.toSide).map((flow) => [flow.id, { start: !!flow.fromSide, end: !!flow.toSide }])
99587
- );
99588
- for (const edge of edges) {
99589
- const pinned = pinnedFlows.get(edge.id);
99590
- if (pinned) edge.pinned = { ...pinned };
99591
- }
100060
+ const reversed = new Set(model.flows.filter(laidOutReversed).map((flow) => flow.id));
100061
+ for (const edge of edges) if (reversed.has(edge.id)) edge.pts.reverse();
100062
+ markDeclaredTerminals(edges, model, view, laidOutWith?.hubPorts !== false);
99592
100063
  const scene = {
99593
100064
  width: Math.ceil(result2.width),
99594
100065
  height: Math.ceil(result2.height),
@@ -99598,11 +100069,14 @@ async function layout(model, view) {
99598
100069
  };
99599
100070
  runGeometryPasses(scene, model, {
99600
100071
  numbered,
99601
- sideways: disposition === "page" || disposition === "tall"
100072
+ sideways: disposition === "page" || disposition === "tall",
100073
+ laneOf: lanes ? laneOf : void 0
99602
100074
  });
99603
100075
  return scene;
99604
100076
  };
100077
+ const laneOf = laneAssignment(model, view);
99605
100078
  const startTime = Date.now();
100079
+ const layoutGraph = (direction, options) => withHubPortFallback(elk, makeGraph, direction, options);
99606
100080
  let result;
99607
100081
  let winnerDirection;
99608
100082
  let winnerOptions;
@@ -99613,9 +100087,10 @@ async function layout(model, view) {
99613
100087
  { direction: "RIGHT", options: { labelWrap: 14, tight: true } },
99614
100088
  { direction: "RIGHT", options: { labelWrap: 14, tight: true, minLayers: true } }
99615
100089
  ] : [{ direction: "DOWN" }, { direction: "DOWN", options: { labelWrap: 16 } }];
99616
- const candidates = await Promise.all(
99617
- graphSpecs.map((spec) => elk.layout(makeGraph(spec.direction, spec.options)))
100090
+ const laidOutSpecs = await Promise.all(
100091
+ graphSpecs.map((spec) => layoutGraph(spec.direction, spec.options))
99618
100092
  );
100093
+ const candidates = laidOutSpecs.map((laidOut) => laidOut.result);
99619
100094
  const preferWide = disposition === "slide";
99620
100095
  const orientedLayouts = candidates.map((layoutResult, index) => ({ layoutResult, index })).filter(
99621
100096
  ({ layoutResult }) => preferWide ? layoutResult.width >= layoutResult.height : layoutResult.height >= layoutResult.width
@@ -99628,7 +100103,7 @@ async function layout(model, view) {
99628
100103
  );
99629
100104
  result = winner.layoutResult;
99630
100105
  winnerDirection = graphSpecs[winner.index].direction;
99631
- winnerOptions = graphSpecs[winner.index].options;
100106
+ winnerOptions = laidOutSpecs[winner.index].options;
99632
100107
  if (disposition === "slide") {
99633
100108
  const folded = await foldedLayout(model, view, elk);
99634
100109
  if (folded && fitScore(result) >= fitScore(folded) * 1.1) {
@@ -99642,20 +100117,32 @@ async function layout(model, view) {
99642
100117
  }
99643
100118
  } else {
99644
100119
  winnerDirection = disposition === "tall" ? "DOWN" : "RIGHT";
99645
- winnerOptions = void 0;
99646
- result = await elk.layout(makeGraph(winnerDirection));
100120
+ ({ result, options: winnerOptions } = await layoutGraph(winnerDirection));
99647
100121
  }
99648
100122
  const layoutMs = Date.now() - startTime;
99649
- const base = sceneFromResult(result, layoutMs);
99650
- const skipPortPass = !!globalThis.process?.env?.CAIRN_NO_PORT_PASS;
99651
- if (skipPortPass) return base;
100123
+ let base = sceneFromResult(result, layoutMs, winnerOptions);
99652
100124
  const layoutProfile = (candidate) => {
99653
100125
  const everyEdge = new Set(candidate.edges.map((edge) => edge.id));
99654
100126
  const profile = inspect(candidate, titleBoxesOf(candidate, model)).local(everyEdge, /* @__PURE__ */ new Map());
99655
100127
  for (const [key, tier] of selectionExtras(candidate, model)) profile.set(key, tier);
99656
100128
  return profile;
99657
100129
  };
100130
+ if (!aspectTarget && nodeCoverage(base) < DENSE_ENOUGH) {
100131
+ const denser = await denserLayout(base, {
100132
+ layout: (spec) => elk.layout(makeGraph(winnerDirection, spec)),
100133
+ // Its specs carry the hub ports (none of them sets `hubPorts: false`), and
100134
+ // a candidate elk refuses is dropped rather than retried without them.
100135
+ toScene: (laidOut) => sceneFromResult(laidOut, Date.now() - startTime),
100136
+ profile: layoutProfile
100137
+ });
100138
+ if (denser) {
100139
+ base = denser.scene;
100140
+ winnerOptions = denser.options;
100141
+ }
100142
+ }
99658
100143
  const baseProfile = layoutProfile(base);
100144
+ const skipPortPass = !!globalThis.process?.env?.CAIRN_NO_PORT_PASS;
100145
+ if (skipPortPass) return base;
99659
100146
  let current = base;
99660
100147
  let best = null;
99661
100148
  for (let round = 0; round < PORT_PASS_ROUNDS; round++) {
@@ -99666,7 +100153,7 @@ async function layout(model, view) {
99666
100153
  const constrained = makeGraph(winnerDirection, winnerOptions);
99667
100154
  constrainPorts(constrained, current, flagged, model);
99668
100155
  const reresult = await elk.layout(constrained);
99669
- candidate = sceneFromResult(reresult, Date.now() - startTime);
100156
+ candidate = sceneFromResult(reresult, Date.now() - startTime, winnerOptions);
99670
100157
  } catch {
99671
100158
  break;
99672
100159
  }
@@ -99722,18 +100209,54 @@ var UI = {
99722
100209
  // src/xml-escape.ts
99723
100210
  var esc = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
99724
100211
  var escAttr = (text) => esc(text).replace(/"/g, "&quot;");
100212
+ var escComment = (text) => text.replace(/-{2,}/g, (run) => [...run].join(" ")).replace(/-$/, "- ");
100213
+
100214
+ // src/logo-attribution.ts
100215
+ var PREAMBLE = [
100216
+ "This diagram contains third-party artwork under its own licence.",
100217
+ "Keep this notice with the file \u2014 it is the attribution those licences ask for.",
100218
+ "",
100219
+ "Every mark below is modified from the original: simple-icons redrew it as a",
100220
+ "single monochrome path, and cairn paints that path in the diagram's colours."
100221
+ ];
100222
+ function logoAttributionComment(drawn) {
100223
+ const licensed = [...drawn].sort().map((slug) => ({ slug, logo: LOGOS[slug] })).filter((entry) => entry.logo?.license);
100224
+ if (licensed.length === 0) return "";
100225
+ const lines = [...PREAMBLE];
100226
+ for (const { slug, logo } of licensed) {
100227
+ lines.push("", `${logo.title} (${slug}) \u2014 ${logo.license}`);
100228
+ if (logo.copyright) lines.push(` ${logo.copyright}`);
100229
+ if (logo.source) lines.push(` artwork: ${logo.source}`);
100230
+ if (logo.licenseUrl) lines.push(` licence: ${logo.licenseUrl}`);
100231
+ }
100232
+ return `<!--
100233
+ ${lines.map((line) => escComment(` ${line}`).trimEnd()).join("\n")}
100234
+ -->
100235
+ `;
100236
+ }
99725
100237
 
99726
100238
  // src/svg-render.ts
99727
100239
  var HOP_RADIUS = 5;
99728
100240
  var LABEL_HALO = 4;
99729
100241
  var RENDER_CHAR_WIDTH = 0.52;
99730
- var SEC_LEVEL_FR = {
99731
- public: "public",
99732
- internal: "interne",
99733
- restricted: "restreint",
99734
- secret: "secret"
99735
- };
100242
+ var BAND_CONTENT_X = 150;
100243
+ var MIN_BAND_TEXT = 200;
100244
+ var bandRightMargin = (scene) => scene.width - 20;
100245
+ function bandLines(text, from, right, fontSize) {
100246
+ const chars = Math.floor((right - from) / (fontSize * RENDER_CHAR_WIDTH));
100247
+ return bandWrap(text, Math.max(8, chars));
100248
+ }
100249
+ function bandWrap(text, maxChars) {
100250
+ return wrapText(text, maxChars).split("\n").flatMap((line) => {
100251
+ if (line.length <= maxChars) return [line];
100252
+ const parts = [];
100253
+ for (let index = 0; index < line.length; index += maxChars)
100254
+ parts.push(line.slice(index, index + maxChars));
100255
+ return parts;
100256
+ });
100257
+ }
99736
100258
  var dashArray = (lineStyle) => lineStyle === "dashed" ? "5 3" : lineStyle === "dotted" ? "2 2.5" : void 0;
100259
+ var lineStyleOf = (flow, style) => flow?.style?.stroke?.style ?? flow?.lineStyle ?? style.flowStroke.style;
99737
100260
  var round1 = (n) => Math.round(n * 10) / 10;
99738
100261
  function assignSourceHues(model, hues) {
99739
100262
  const sourceHue = /* @__PURE__ */ new Map();
@@ -99745,7 +100268,7 @@ function assignSourceHues(model, hues) {
99745
100268
  }
99746
100269
  function collectElementStyles(elements) {
99747
100270
  return elements.flatMap((element) => [
99748
- { id: element.id, style: element.style, attrValue: element.attr?.value },
100271
+ { id: element.id, style: element.style, logo: element.logo },
99749
100272
  ...collectElementStyles(element.children)
99750
100273
  ]);
99751
100274
  }
@@ -99789,19 +100312,39 @@ function auditRouteRepairs(deps) {
99789
100312
  for (let tier = 0; tier < 5; tier++) if (a[tier] !== b[tier]) return a[tier] < b[tier];
99790
100313
  return false;
99791
100314
  };
100315
+ const flowOfBreach = (key) => {
100316
+ if (key.startsWith("adrift:"))
100317
+ return labels[Number.parseInt(key.slice("adrift:".length), 10)]?.flowId ?? null;
100318
+ const body = key.slice(key.indexOf(":") + 1);
100319
+ const cut = body.search(/[~:]/);
100320
+ return cut < 0 ? body : body.slice(0, cut);
100321
+ };
99792
100322
  const withRepair = stateHarm();
99793
100323
  const breachesWith = breaches();
99794
100324
  const repairedRoutes = repaired.map((edge) => edge.pts);
99795
- for (const edge of repaired) edge.pts = edge.repairedFrom;
99796
- resettle();
99797
- const withoutRepair = stateHarm();
99798
- const breachesWithout = breaches();
99799
- const breaksAPromise = [...breachesWith].some((key) => !breachesWithout.has(key));
99800
- if (!breaksAPromise && !lessDamaged(withoutRepair, withRepair)) {
100325
+ const replacedRoutes = repaired.map((edge) => edge.repairedFrom);
100326
+ const applyKeep = (keep2) => {
99801
100327
  repaired.forEach((edge, index) => {
99802
- edge.pts = repairedRoutes[index];
100328
+ edge.pts = keep2[index] ? repairedRoutes[index] : replacedRoutes[index];
99803
100329
  });
99804
100330
  resettle();
100331
+ };
100332
+ applyKeep(repaired.map(() => false));
100333
+ const withoutRepair = stateHarm();
100334
+ const breachesWithout = breaches();
100335
+ const broken = [...breachesWith].filter((key) => !breachesWithout.has(key));
100336
+ if (!broken.length) {
100337
+ if (!lessDamaged(withoutRepair, withRepair)) applyKeep(repaired.map(() => true));
100338
+ for (const edge of repaired) edge.repairedFrom = void 0;
100339
+ return;
100340
+ }
100341
+ const blamed = new Set(broken.map(flowOfBreach).filter((id) => id !== null));
100342
+ const keep = repaired.map((edge) => !blamed.has(edge.id));
100343
+ if (keep.some(Boolean) && keep.some((kept) => !kept)) {
100344
+ applyKeep(keep);
100345
+ const partly = stateHarm();
100346
+ const stillBroken = [...breaches()].some((key) => !breachesWithout.has(key));
100347
+ if (stillBroken || lessDamaged(withoutRepair, partly)) applyKeep(repaired.map(() => false));
99805
100348
  }
99806
100349
  for (const edge of repaired) edge.repairedFrom = void 0;
99807
100350
  }
@@ -99842,13 +100385,82 @@ function settleOneLabel(s, label) {
99842
100385
  label.x = origin.x;
99843
100386
  label.y = origin.y;
99844
100387
  }
99845
- function createNodeRenderers(paint) {
99846
- const { palette, style, annot, nodeFontSize, containerFontSize, resolveStyle, elementAttr } = paint;
100388
+ var GLYPH_BOX = { width: 18, height: 16, left: 6, top: 7 };
100389
+ var GLYPHS = {
100390
+ // Padlock: authentication is a check something must pass.
100391
+ auth: ({ x, y, r, line, stroke }) => `<rect x="${x(3)}" y="${y(7)}" width="${r(12)}" height="${r(9)}" rx="${r(2)}" ${line}/><path d="M ${x(6)} ${y(7)} v ${-r(3)} a ${r(3)} ${r(3)} 0 0 1 ${r(6)} 0 v ${r(3)}" ${line}/><circle cx="${x(9)}" cy="${y(11)}" r="${r(1.5)}" fill="${stroke}"/>`,
100392
+ // Two posts with traffic passing between them: a gateway routes, it does not block.
100393
+ gateway: ({ x, y, r, line }) => `<path d="M ${x(2)} ${y(1)} V ${y(15)} M ${x(16)} ${y(1)} V ${y(15)}" ${line}/><path d="M ${x(4)} ${y(8)} H ${x(14)}" ${line}/><path d="M ${x(11)} ${y(5)} l ${r(3)} ${r(3)} l ${-r(3)} ${r(3)}" ${line}/>`,
100394
+ // ID badge: an identity provider issues who-you-are, it does not check it.
100395
+ idp: ({ x, y, r, line }) => `<rect x="${x(3)}" y="${y(2)}" width="${r(12)}" height="${r(13)}" rx="${r(2)}" ${line}/><path d="M ${x(7)} ${y(2)} H ${x(11)}" ${line}/><circle cx="${x(9)}" cy="${y(7)}" r="${r(2)}" ${line}/><path d="M ${x(5)} ${y(13)} q ${r(4)} ${-r(4)} ${r(8)} 0" ${line}/>`,
100396
+ // Brick wall: a firewall is a barrier, and no other kind reads as one.
100397
+ firewall: ({ x, y, r, line }) => `<rect x="${x(2)}" y="${y(2)}" width="${r(14)}" height="${r(12)}" rx="${r(1)}" ${line}/><path d="M ${x(2)} ${y(6)} H ${x(16)} M ${x(2)} ${y(10)} H ${x(16)}" ${line}/><path d="M ${x(9)} ${y(2)} V ${y(6)} M ${x(6)} ${y(6)} V ${y(10)} M ${x(12)} ${y(6)} V ${y(10)} M ${x(9)} ${y(10)} V ${y(14)}" ${line}/>`,
100398
+ // Monitor on a stand: a device is the machine a person works at. The screen is
100399
+ // left empty on purpose — that is what separates it from the firewall's wall
100400
+ // at legend size, where both are a rectangle and little else survives.
100401
+ device: ({ x, y, r, line }) => `<rect x="${x(2)}" y="${y(1)}" width="${r(14)}" height="${r(9)}" rx="${r(1)}" ${line}/><path d="M ${x(9)} ${y(10)} V ${y(13)}" ${line}/><path d="M ${x(4)} ${y(13)} H ${x(14)}" ${line}/>`
100402
+ };
100403
+ function glyphSvg(kind, stroke, box) {
100404
+ const glyph = GLYPHS[kind];
100405
+ if (!glyph) return "";
100406
+ const { x, y, scale = 1 } = box;
100407
+ const width = round1(1.3 * scale);
100408
+ return glyph({
100409
+ x: (v) => round1(x + v * scale),
100410
+ y: (v) => round1(y + v * scale),
100411
+ r: (v) => round1(v * scale),
100412
+ line: `fill="none" stroke="${stroke}" stroke-width="${width}"`,
100413
+ stroke
100414
+ });
100415
+ }
100416
+ var LOGO_BOX = { size: 18, right: 7, top: 6 };
100417
+ function logoSvg(mark) {
100418
+ const { logo, resolved, node, stroke, drawn, inset = {} } = mark;
100419
+ if (!logo) return "";
100420
+ const x = round1(node.x + node.width - (inset.right ?? LOGO_BOX.right) - LOGO_BOX.size);
100421
+ const y = round1(node.y + (inset.top ?? LOGO_BOX.top));
100422
+ if (logo.source === "file") {
100423
+ const href = resolved?.get(node.id);
100424
+ if (!href) return "";
100425
+ return `<image x="${x}" y="${y}" width="${LOGO_BOX.size}" height="${LOGO_BOX.size}" href="${escAttr(href)}" preserveAspectRatio="xMidYMid meet"/>
100426
+ `;
100427
+ }
100428
+ const builtin = Object.hasOwn(LOGOS, logo.value) ? LOGOS[logo.value] : void 0;
100429
+ if (!builtin) return "";
100430
+ drawn.add(logo.value);
100431
+ const scale = Math.round(LOGO_BOX.size / 24 * 1e4) / 1e4;
100432
+ return `<g transform="translate(${x} ${y}) scale(${scale})" fill="${stroke}"><title>${esc(builtin.title)}</title><path d="${builtin.d}"/></g>
100433
+ `;
100434
+ }
100435
+ function createNodeLabelHelpers(nodeFontSize) {
99847
100436
  const centeredNodeLabel = (lines, centerX2, topBaseline, fill) => lines.map(
99848
100437
  (line, index) => `<text x="${centerX2}" y="${topBaseline + index * (nodeFontSize + 2)}" font-size="${nodeFontSize}" text-anchor="middle" fill="${fill}">${esc(line)}</text>
99849
100438
  `
99850
100439
  ).join("");
99851
100440
  const centerLinesY = (top, height, lineCount) => top + height / 2 - (lineCount - 1) * (nodeFontSize + 2) / 2 + 4;
100441
+ const glyphLabelCenterX = (node) => node.x + GLYPH_GUTTER + (node.width - GLYPH_GUTTER) / 2;
100442
+ const logoLabelCenterX = (node, hasLogo) => node.x + (node.width - (hasLogo ? LOGO_GUTTER : 0)) / 2;
100443
+ return { centeredNodeLabel, centerLinesY, glyphLabelCenterX, logoLabelCenterX };
100444
+ }
100445
+ function createNodeRenderers(paint) {
100446
+ const {
100447
+ palette,
100448
+ nodeFontSize,
100449
+ containerFontSize,
100450
+ resolveStyle,
100451
+ elementLogo,
100452
+ resolvedLogos,
100453
+ drawnLogos
100454
+ } = paint;
100455
+ const logoFor = (node, stroke, inset) => logoSvg({
100456
+ logo: elementLogo.get(node.id),
100457
+ resolved: resolvedLogos,
100458
+ node,
100459
+ stroke,
100460
+ drawn: drawnLogos,
100461
+ inset
100462
+ });
100463
+ const { centeredNodeLabel, centerLinesY, glyphLabelCenterX, logoLabelCenterX } = createNodeLabelHelpers(nodeFontSize);
99852
100464
  const renderContainerNode = (node) => {
99853
100465
  const nodeStyle = resolveStyle(node.kind, node.id);
99854
100466
  const fill = escAttr(nodeStyle.fill ?? palette.containerFill), stroke = escAttr(nodeStyle.stroke?.color ?? palette.containerStroke), text = escAttr(nodeStyle.text ?? palette.containerLabel);
@@ -99859,12 +100471,7 @@ function createNodeRenderers(paint) {
99859
100471
  svg += `<text x="${node.x + 10}" y="${node.y + 18 + index * 14}" font-size="${containerFontSize}" font-weight="bold" fill="${text}">${esc(line)}</text>
99860
100472
  `;
99861
100473
  });
99862
- const level = node.kind === "trust-zone" ? elementAttr.get(node.id) : void 0;
99863
- if (level) {
99864
- const word = (style.lang === "fr" ? SEC_LEVEL_FR[level] : level) ?? level;
99865
- svg += `<text x="${node.x + node.width - 9}" y="${node.y + node.height - 6}" font-size="${annot.tag}" text-anchor="end" font-weight="bold" fill="${stroke}" letter-spacing="0.5">${esc(word.toUpperCase())}</text>
99866
- `;
99867
- }
100474
+ svg += logoFor(node, stroke);
99868
100475
  return svg;
99869
100476
  };
99870
100477
  const renderActor = (node, nodeStyle, lines) => {
@@ -99887,9 +100494,12 @@ function createNodeRenderers(paint) {
99887
100494
  <ellipse cx="${node.x + node.width / 2}" cy="${node.y + ry}" rx="${node.width / 2}" ry="${ry}" fill="${fill}" stroke="${stroke}" stroke-width="1.3"/>
99888
100495
  `;
99889
100496
  const centerY2 = node.y + ry + (node.height - ry) / 2 - (lines.length - 1) * (nodeFontSize + 2) / 2 + 4;
99890
- return body + centeredNodeLabel(
100497
+ const logo = logoFor(node, stroke, {
100498
+ top: ry + (node.height - 2 * ry - LOGO_BOX.size) / 2
100499
+ });
100500
+ return body + logo + centeredNodeLabel(
99891
100501
  lines,
99892
- node.x + node.width / 2,
100502
+ logoLabelCenterX(node, logo !== ""),
99893
100503
  centerY2,
99894
100504
  escAttr(nodeStyle.text ?? palette.nodeText)
99895
100505
  );
@@ -99900,50 +100510,38 @@ function createNodeRenderers(paint) {
99900
100510
  const body = `<path d="M ${node.x + rx} ${node.y} h ${node.width - 2 * rx} a ${rx} ${node.height / 2} 0 0 1 0 ${node.height} h ${-(node.width - 2 * rx)} a ${rx} ${node.height / 2} 0 0 1 0 ${-node.height}" fill="${fill}" stroke="${stroke}" stroke-width="1.3"/>
99901
100511
  <ellipse cx="${node.x + rx}" cy="${node.y + node.height / 2}" rx="${rx}" ry="${node.height / 2}" fill="${fill}" stroke="${stroke}" stroke-width="1.3"/>
99902
100512
  `;
99903
- return body + centeredNodeLabel(
100513
+ const logo = logoFor(node, stroke, { right: LOGO_BOX.right + rx });
100514
+ return body + logo + centeredNodeLabel(
99904
100515
  lines,
99905
- node.x + rx + (node.width - rx) / 2,
100516
+ node.x + rx + (node.width - rx - (logo === "" ? 0 : LOGO_GUTTER)) / 2,
99906
100517
  centerLinesY(node.y, node.height, lines.length),
99907
100518
  text
99908
100519
  );
99909
100520
  };
99910
- const renderGateway = (node, nodeStyle, lines) => {
99911
- const centerX2 = node.x + node.width / 2;
99912
- const fill = escAttr(nodeStyle.fill ?? palette.nodeFill), stroke = escAttr(nodeStyle.stroke?.color ?? palette.nodeStroke), text = escAttr(nodeStyle.text ?? palette.nodeText);
99913
- const body = `<rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="4" fill="${fill}" stroke="${stroke}" stroke-width="1.3"/>
99914
- <path d="M ${round1(node.x + 8)} ${round1(node.y + 8)} L ${round1(node.x + 22)} ${round1(node.y + 8)} Q ${round1(node.x + 24)} ${round1(node.y + 13)} ${round1(node.x + 15)} ${round1(node.y + 20)} Q ${round1(node.x + 6)} ${round1(node.y + 13)} ${round1(node.x + 8)} ${round1(node.y + 8)}" fill="none" stroke="${stroke}" stroke-width="1.3"/>
99915
- `;
99916
- return body + centeredNodeLabel(lines, centerX2 + 10, centerLinesY(node.y, node.height, lines.length), text);
99917
- };
99918
- const renderAuth = (node, nodeStyle, lines) => {
99919
- const centerX2 = node.x + node.width / 2;
99920
- const fill = escAttr(nodeStyle.fill ?? palette.nodeFill), stroke = escAttr(nodeStyle.stroke?.color ?? palette.nodeStroke), text = escAttr(nodeStyle.text ?? palette.nodeText);
99921
- const body = `<rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="4" fill="${fill}" stroke="${stroke}" stroke-width="1.3"/>
99922
- <rect x="${node.x + 6}" y="${node.y + 6}" width="18" height="14" rx="3" fill="none" stroke="${stroke}" stroke-width="1.3"/>
99923
- <path d="M ${node.x + 10} ${node.y + 9} v -4 a 5 5 0 0 1 10 0 v 4" fill="none" stroke="${stroke}" stroke-width="1.3"/>
99924
- <circle cx="${node.x + 15}" cy="${node.y + 16}" r="2.5" fill="${stroke}"/>
99925
- `;
99926
- return body + centeredNodeLabel(lines, centerX2 + 10, centerLinesY(node.y, node.height, lines.length), text);
99927
- };
99928
- const renderPlainBox = (node, nodeStyle, lines) => {
100521
+ const renderGlyphBox = (node, nodeStyle, lines) => {
99929
100522
  const fill = escAttr(nodeStyle.fill ?? palette.nodeFill), stroke = escAttr(nodeStyle.stroke?.color ?? palette.nodeStroke), text = escAttr(nodeStyle.text ?? palette.nodeText);
99930
100523
  const dash = dashArray(nodeStyle.stroke?.style);
99931
100524
  const body = `<rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="4" fill="${fill}" stroke="${stroke}" stroke-width="${nodeStyle.stroke?.width ?? 1.3}"${dash ? ` stroke-dasharray="${dash}"` : ""}/>
99932
- `;
100525
+ ` + glyphSvg(node.kind, stroke, {
100526
+ x: node.x + GLYPH_BOX.left,
100527
+ y: node.y + GLYPH_BOX.top
100528
+ }) + "\n";
99933
100529
  return body + centeredNodeLabel(
99934
100530
  lines,
99935
- node.x + node.width / 2,
100531
+ glyphLabelCenterX(node),
99936
100532
  centerLinesY(node.y, node.height, lines.length),
99937
100533
  text
99938
100534
  );
99939
100535
  };
99940
- const renderIdp = (node, nodeStyle, lines) => {
100536
+ const renderPlainBox = (node, nodeStyle, lines) => {
99941
100537
  const fill = escAttr(nodeStyle.fill ?? palette.nodeFill), stroke = escAttr(nodeStyle.stroke?.color ?? palette.nodeStroke), text = escAttr(nodeStyle.text ?? palette.nodeText);
99942
- const body = `<rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="4" fill="${fill}" stroke="${stroke}" stroke-width="${nodeStyle.stroke?.width ?? 1.3}"/>
100538
+ const dash = dashArray(nodeStyle.stroke?.style);
100539
+ const logo = logoFor(node, stroke);
100540
+ const body = `<rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="4" fill="${fill}" stroke="${stroke}" stroke-width="${nodeStyle.stroke?.width ?? 1.3}"${dash ? ` stroke-dasharray="${dash}"` : ""}/>
99943
100541
  `;
99944
- return body + centeredNodeLabel(
100542
+ return body + logo + centeredNodeLabel(
99945
100543
  lines,
99946
- node.x + node.width / 2,
100544
+ logoLabelCenterX(node, logo !== ""),
99947
100545
  centerLinesY(node.y, node.height, lines.length),
99948
100546
  text
99949
100547
  );
@@ -99958,14 +100556,8 @@ function createNodeRenderers(paint) {
99958
100556
  return renderDatastore(node, nodeStyle, lines);
99959
100557
  case "queue":
99960
100558
  return renderQueue(node, nodeStyle, lines);
99961
- case "gateway":
99962
- return renderGateway(node, nodeStyle, lines);
99963
- case "auth":
99964
- return renderAuth(node, nodeStyle, lines);
99965
- case "idp":
99966
- return renderIdp(node, nodeStyle, lines);
99967
100559
  default:
99968
- return renderPlainBox(node, nodeStyle, lines);
100560
+ return GLYPHS[node.kind] ? renderGlyphBox(node, nodeStyle, lines) : renderPlainBox(node, nodeStyle, lines);
99969
100561
  }
99970
100562
  };
99971
100563
  return { renderContainerNode, renderLeafNode };
@@ -100032,15 +100624,20 @@ function createLabelSettler(deps) {
100032
100624
  const a = edge.pts[index];
100033
100625
  const b = edge.pts[index + 1];
100034
100626
  const vertical = Math.abs(a.x - b.x) < Math.abs(a.y - b.y);
100035
- const span = vertical ? Math.abs(b.y - a.y) : Math.abs(b.x - a.x);
100036
- const room = Math.max(0, (span - (vertical ? label.height : label.width)) / 2);
100037
- if (room === 0) continue;
100038
- for (const fraction of [-0.25, 0.25, -0.5, 0.5, -0.75, 0.75, -1, 1]) {
100039
- const shift = room * fraction;
100040
- seats.push(
100041
- vertical ? { x: (a.x + b.x) / 2 - label.width / 2, y: (a.y + b.y) / 2 + shift - lead } : { x: (a.x + b.x) / 2 + shift - label.width / 2, y: (a.y + b.y) / 2 - lead }
100042
- );
100043
- }
100627
+ const size = vertical ? label.height : label.width;
100628
+ const start = vertical ? Math.min(a.y, b.y) : Math.min(a.x, b.x);
100629
+ const end = vertical ? Math.max(a.y, b.y) : Math.max(a.x, b.x);
100630
+ const seat = vertical ? (a.y + b.y) / 2 - lead : (a.x + b.x) / 2 - label.width / 2;
100631
+ const back = Math.max(0, seat - start);
100632
+ const ahead = Math.max(0, end - size - seat);
100633
+ if (back === 0 && ahead === 0) continue;
100634
+ for (const fraction of [0.25, 0.5, 0.75, 1])
100635
+ for (const room of [-back, ahead]) {
100636
+ const shift = room * fraction;
100637
+ seats.push(
100638
+ vertical ? { x: (a.x + b.x) / 2 - label.width / 2, y: seat + shift } : { x: seat + shift, y: (a.y + b.y) / 2 - lead }
100639
+ );
100640
+ }
100044
100641
  }
100045
100642
  return seats;
100046
100643
  };
@@ -100087,27 +100684,106 @@ function createLabelSettler(deps) {
100087
100684
  settleLabelPositions
100088
100685
  };
100089
100686
  }
100687
+ var legendKinds = (paint) => [...new Set(paint.scene.nodes.map((node) => node.kind))].filter(
100688
+ (kind) => paint.legendNames[kind] && (kind !== "actor" || paint.view.actorLegend)
100689
+ );
100690
+ function kindKeysSvg(paint, kinds, y, x) {
100691
+ const { scene, palette, legendNames, scaled, resolveStyle } = paint;
100692
+ let svg = "";
100693
+ let keyX = x;
100694
+ let bandY = y;
100695
+ const LINE_H = scaled(12);
100696
+ for (const kind of kinds) {
100697
+ const nodeStyle = resolveStyle(kind, "");
100698
+ if (kind === "actor") {
100699
+ const stroke = nodeStyle.stroke?.color ?? palette.actorStroke;
100700
+ svg += `<circle cx="${keyX + scaled(13)}" cy="${bandY + scaled(5)}" r="${scaled(3)}" fill="none" stroke="${stroke}" stroke-width="1.2"/>
100701
+ `;
100702
+ svg += `<path d="M ${keyX + scaled(8)} ${bandY + scaled(15)} q ${scaled(5)} ${scaled(-7)} ${scaled(10)} 0" fill="none" stroke="${stroke}" stroke-width="1.2"/>
100703
+ `;
100704
+ } else {
100705
+ const dash = dashArray(nodeStyle.stroke?.style);
100706
+ const stroke = nodeStyle.stroke?.color ?? palette.nodeStroke;
100707
+ svg += `<rect x="${keyX}" y="${bandY + 2}" width="${scaled(26)}" height="${scaled(14)}" rx="3" fill="${nodeStyle.fill ?? palette.nodeFill}" stroke="${escAttr(stroke)}"${dash ? ` stroke-dasharray="${dash}"` : ""}/>
100708
+ `;
100709
+ const glyphScale = (scaled(14) - scaled(4)) / GLYPH_BOX.height;
100710
+ svg += glyphSvg(kind, escAttr(stroke), {
100711
+ x: keyX + (scaled(26) - GLYPH_BOX.width * glyphScale) / 2,
100712
+ y: bandY + 2 + (scaled(14) - GLYPH_BOX.height * glyphScale) / 2,
100713
+ scale: glyphScale
100714
+ });
100715
+ }
100716
+ const name = legendNames[kind];
100717
+ const lines = bandLines(name, keyX + scaled(32), bandRightMargin(scene), scaled(10));
100718
+ for (const [row, line] of lines.entries())
100719
+ svg += `<text x="${keyX + scaled(32)}" y="${bandY + scaled(13) + row * LINE_H}" font-size="${scaled(10)}" fill="${palette.bandText}">${esc(line)}</text>
100720
+ `;
100721
+ keyX += scaled(40) + Math.ceil(name.length * scaled(10) * RENDER_CHAR_WIDTH) + scaled(24);
100722
+ bandY += (lines.length - 1) * LINE_H;
100723
+ if (lines.length > 1 || keyX > scene.width - 220) {
100724
+ keyX = x;
100725
+ bandY += scaled(22);
100726
+ }
100727
+ }
100728
+ return { svg, bandY: kinds.length ? bandY + scaled(24) : bandY };
100729
+ }
100730
+ function lineStyleKeysSvg(paint, y, x) {
100731
+ const { scene, model, style, palette, legendLineStyles, scaled, defaultEdgeColor, markerName } = paint;
100732
+ const styles = LINE_STYLES2.filter(
100733
+ (lineStyle) => model.flows.some((flow) => lineStyleOf(flow, style) === lineStyle)
100734
+ );
100735
+ if (styles.length < 2) return { svg: "", bandY: y };
100736
+ const maxX = scene.width - 20;
100737
+ let svg = "";
100738
+ let keyX = x;
100739
+ let bandY = y;
100740
+ const LINE_H = scaled(12);
100741
+ for (const [index, lineStyle] of styles.entries()) {
100742
+ const meaning = legendLineStyles[lineStyle];
100743
+ const keyWidth = scaled(40) + Math.ceil(meaning.length * scaled(10) * RENDER_CHAR_WIDTH) + scaled(24);
100744
+ if (keyX > x && keyX + keyWidth > maxX) {
100745
+ keyX = x;
100746
+ bandY += scaled(22);
100747
+ }
100748
+ const dash = dashArray(lineStyle);
100749
+ svg += `<line x1="${keyX}" y1="${bandY + 8}" x2="${keyX + scaled(26)}" y2="${bandY + 8}" stroke="${escAttr(defaultEdgeColor)}" stroke-width="1.3"${dash ? ` stroke-dasharray="${dash}"` : ""} marker-end="url(#${markerName(defaultEdgeColor)})"/>
100750
+ `;
100751
+ const lines = bandLines(meaning, keyX + scaled(40), maxX, scaled(10));
100752
+ for (const [row, line] of lines.entries())
100753
+ svg += `<text x="${keyX + scaled(32)}" y="${bandY + scaled(12) + row * LINE_H}" font-size="${scaled(10)}" fill="${palette.bandText}">${esc(line)}</text>
100754
+ `;
100755
+ if (lines.length === 1) {
100756
+ keyX += keyWidth;
100757
+ continue;
100758
+ }
100759
+ bandY += (lines.length - 1) * LINE_H;
100760
+ if (index < styles.length - 1) {
100761
+ bandY += scaled(22);
100762
+ keyX = x;
100763
+ }
100764
+ }
100765
+ return { svg, bandY: bandY + scaled(24) };
100766
+ }
100090
100767
  function createBandRenderers(paint) {
100091
100768
  const {
100092
100769
  scene,
100093
100770
  model,
100094
- view,
100095
100771
  style,
100096
100772
  palette,
100097
100773
  annot,
100098
100774
  ui,
100099
- legendNames,
100100
100775
  legendFlowLabel,
100101
100776
  scaled,
100102
100777
  objectName,
100103
- resolveStyle,
100104
100778
  defaultEdgeColor,
100105
100779
  markerName,
100106
100780
  numbered
100107
100781
  } = paint;
100108
100782
  let bandY = scene.height;
100109
100783
  let bandsSvg = "";
100110
- const contentX = 150;
100784
+ const rightMargin = bandRightMargin(scene);
100785
+ const stackedTitle = rightMargin - BAND_CONTENT_X < MIN_BAND_TEXT;
100786
+ const contentX = stackedTitle ? 20 : BAND_CONTENT_X;
100111
100787
  const chip = (x, y, name) => {
100112
100788
  const width = chipW(name, annot.scale);
100113
100789
  return {
@@ -100122,7 +100798,7 @@ function createBandRenderers(paint) {
100122
100798
  `;
100123
100799
  bandsSvg += `<text x="20" y="${bandY + scaled(32)}" font-size="${scaled(11)}" font-weight="bold" fill="${palette.bandTitle}">${esc(title)}</text>
100124
100800
  `;
100125
- bandY += scaled(20);
100801
+ bandY += scaled(stackedTitle ? 34 : 20);
100126
100802
  };
100127
100803
  const flowChipsSvg = (flow, startX, startY) => {
100128
100804
  if (!flow.objects?.length) return "";
@@ -100154,7 +100830,7 @@ function createBandRenderers(paint) {
100154
100830
  const textW = Math.max(60, colW - BADGE - (chipsW ? chipsW + 6 : 0));
100155
100831
  const maxChars = Math.max(6, Math.floor(textW / (scaled(10) * RENDER_CHAR_WIDTH)));
100156
100832
  const raw = (flow.label ?? "") + (tech ? " " + tech : "");
100157
- const lines = raw.split("\n").flatMap((segment) => wrapText(segment, maxChars).split("\n"));
100833
+ const lines = raw.split("\n").flatMap((segment) => bandWrap(segment, maxChars));
100158
100834
  return { flow, lines };
100159
100835
  });
100160
100836
  const rows = Math.ceil(entries.length / cols);
@@ -100181,59 +100857,50 @@ function createBandRenderers(paint) {
100181
100857
  };
100182
100858
  const renderObjectsBand = () => {
100183
100859
  beginBand(ui.objects);
100184
- for (const bo of model.businessObjects) {
100185
- const chipResult = chip(contentX, bandY + 2, bo.name);
100860
+ const LINE_H = scaled(12);
100861
+ const chipRow = (name, reading) => {
100862
+ const chipResult = chip(contentX, bandY + 2, name);
100186
100863
  bandsSvg += chipResult.svg;
100187
- if (bo.description)
100188
- bandsSvg += `<text x="${contentX + chipResult.width + 10}" y="${bandY + scaled(13)}" font-size="${scaled(10)}" fill="${palette.bandMuted}">\u2014 ${esc(bo.description)}</text>
100864
+ const textX = contentX + chipResult.width + 10;
100865
+ const lines = reading ? bandLines(reading, textX, rightMargin, scaled(10)) : [];
100866
+ for (const [row, line] of lines.entries())
100867
+ bandsSvg += `<text x="${textX}" y="${bandY + scaled(13) + row * LINE_H}" font-size="${scaled(10)}" fill="${palette.bandMuted}">${esc(line)}</text>
100189
100868
  `;
100190
- bandY += scaled(24);
100191
- }
100192
- const keyChip = chip(contentX, bandY + 2, ui.businessObject);
100193
- bandsSvg += keyChip.svg;
100194
- bandsSvg += `<text x="${contentX + keyChip.width + 10}" y="${bandY + scaled(13)}" font-size="${scaled(10)}" fill="${palette.bandMuted}">${esc(ui.carriedByFlow)}</text>
100195
- `;
100196
- bandY += scaled(24) + 6;
100869
+ bandY += scaled(24) + Math.max(0, lines.length - 1) * LINE_H;
100870
+ };
100871
+ for (const bo of model.businessObjects)
100872
+ chipRow(bo.name, bo.description ? `\u2014 ${bo.description}` : "");
100873
+ if (model.flows.some((flow) => flow.objects?.length))
100874
+ chipRow(ui.businessObject, ui.carriedByFlow);
100875
+ bandY += 6;
100197
100876
  };
100198
100877
  const renderLegendBand = () => {
100878
+ const kinds = legendKinds(paint);
100879
+ if (!kinds.length && !model.flows.length && !model.legendNotes.length) return;
100199
100880
  beginBand(ui.legend);
100200
- let lx = contentX;
100201
- const kindsUsed = [...new Set(scene.nodes.map((node) => node.kind))].filter(
100202
- (kind) => legendNames[kind] && (kind !== "actor" || view.actorLegend)
100203
- );
100204
- for (const kind of kindsUsed) {
100205
- const nodeStyle = resolveStyle(kind, "");
100206
- if (kind === "actor") {
100207
- const stroke = nodeStyle.stroke?.color ?? palette.actorStroke;
100208
- bandsSvg += `<circle cx="${lx + scaled(13)}" cy="${bandY + scaled(5)}" r="${scaled(3)}" fill="none" stroke="${stroke}" stroke-width="1.2"/>
100881
+ const kindKeys = kindKeysSvg(paint, kinds, bandY, contentX);
100882
+ bandsSvg += kindKeys.svg;
100883
+ bandY = kindKeys.bandY;
100884
+ if (model.flows.length) {
100885
+ bandsSvg += `<line x1="${contentX}" y1="${bandY + 8}" x2="${contentX + scaled(26)}" y2="${bandY + 8}" stroke="${escAttr(defaultEdgeColor)}" stroke-width="1.3" marker-end="url(#${markerName(defaultEdgeColor)})"/>
100209
100886
  `;
100210
- bandsSvg += `<path d="M ${lx + scaled(8)} ${bandY + scaled(15)} q ${scaled(5)} ${scaled(-7)} ${scaled(10)} 0" fill="none" stroke="${stroke}" stroke-width="1.2"/>
100887
+ const flowLabelText = (numbered ? legendFlowLabel + " \u2014 " + ui.numberedSuffix : legendFlowLabel) + (style.flowColor === "by-source" ? style.lang === "fr" ? " \u2014 couleur = source" : " \u2014 colour = source" : "");
100888
+ const flowKeyX = contentX + scaled(32);
100889
+ const flowKeyLines = bandLines(flowLabelText, flowKeyX, rightMargin, scaled(10));
100890
+ for (const [row, line] of flowKeyLines.entries())
100891
+ bandsSvg += `<text x="${flowKeyX}" y="${bandY + scaled(12) + row * scaled(12)}" font-size="${scaled(10)}" fill="${palette.bandText}">${esc(line)}</text>
100211
100892
  `;
100212
- } else {
100213
- const dash = dashArray(nodeStyle.stroke?.style);
100214
- bandsSvg += `<rect x="${lx}" y="${bandY + 2}" width="${scaled(26)}" height="${scaled(14)}" rx="3" fill="${nodeStyle.fill ?? palette.nodeFill}" stroke="${nodeStyle.stroke?.color ?? palette.nodeStroke}"${dash ? ` stroke-dasharray="${dash}"` : ""}/>
100215
- `;
100216
- }
100217
- const name = legendNames[kind];
100218
- bandsSvg += `<text x="${lx + scaled(32)}" y="${bandY + scaled(13)}" font-size="${scaled(10)}" fill="${palette.bandText}">${esc(name)}</text>
100219
- `;
100220
- lx += scaled(40) + Math.ceil(name.length * scaled(10) * RENDER_CHAR_WIDTH) + scaled(24);
100221
- if (lx > scene.width - 220) {
100222
- lx = contentX;
100223
- bandY += scaled(22);
100224
- }
100893
+ bandY += scaled(24) + (flowKeyLines.length - 1) * scaled(12);
100894
+ const lineStyleKeys = lineStyleKeysSvg(paint, bandY, contentX);
100895
+ bandsSvg += lineStyleKeys.svg;
100896
+ bandY = lineStyleKeys.bandY;
100225
100897
  }
100226
- bandY += scaled(24);
100227
- bandsSvg += `<line x1="${contentX}" y1="${bandY + 8}" x2="${contentX + scaled(26)}" y2="${bandY + 8}" stroke="${escAttr(defaultEdgeColor)}" stroke-width="1.3" marker-end="url(#${markerName(defaultEdgeColor)})"/>
100228
- `;
100229
- const flowLabelText = (numbered ? legendFlowLabel + " \u2014 " + ui.numberedSuffix : legendFlowLabel) + (style.flowColor === "by-source" ? style.lang === "fr" ? " \u2014 couleur = source" : " \u2014 colour = source" : "");
100230
- bandsSvg += `<text x="${contentX + scaled(32)}" y="${bandY + scaled(12)}" font-size="${scaled(10)}" fill="${palette.bandText}">${esc(flowLabelText)}</text>
100231
- `;
100232
- bandY += scaled(24);
100233
100898
  for (const note of model.legendNotes) {
100234
- bandsSvg += `<text x="${contentX}" y="${bandY + scaled(12)}" font-size="${scaled(10)}" fill="${palette.bandText}" font-style="italic">${esc(note)}</text>
100899
+ const lines = bandLines(note, contentX, rightMargin, scaled(10));
100900
+ for (const [row, line] of lines.entries())
100901
+ bandsSvg += `<text x="${contentX}" y="${bandY + scaled(12) + row * scaled(12)}" font-size="${scaled(10)}" fill="${palette.bandText}" font-style="italic">${esc(line)}</text>
100235
100902
  `;
100236
- bandY += scaled(20);
100903
+ bandY += scaled(20) + (lines.length - 1) * scaled(12);
100237
100904
  }
100238
100905
  };
100239
100906
  return {
@@ -100332,7 +100999,7 @@ function createEdgePainter(paint) {
100332
100999
  const flowStyle = flow?.style;
100333
101000
  const color = flowColorOf(flow);
100334
101001
  const headColor = style.flowColor === "by-source" ? color : defaultEdgeColor;
100335
- const dash = dashArray(flowStyle?.stroke?.style ?? flow?.lineStyle ?? style.flowStroke.style);
101002
+ const dash = dashArray(lineStyleOf(flow, style));
100336
101003
  const width = flowStyle?.stroke?.width ?? style.flowStroke.width;
100337
101004
  return `<path d="${edgePath(edge.pts)}" fill="none" stroke="${escAttr(color)}" stroke-width="${width}"${dash ? ` stroke-dasharray="${dash}"` : ""} marker-end="url(#${markerName(headColor)})"/>
100338
101005
  `;
@@ -100348,7 +101015,28 @@ function createEdgePainter(paint) {
100348
101015
  };
100349
101016
  return { renderEdgePath, renderEdgeLabels };
100350
101017
  }
100351
- function render(model, view, scene) {
101018
+ function svgDocument(args) {
101019
+ const {
101020
+ width,
101021
+ height,
101022
+ fontFamily,
101023
+ background,
101024
+ arrowMarkers,
101025
+ markerSize,
101026
+ body,
101027
+ bandsSvg,
101028
+ attribution
101029
+ } = args;
101030
+ const markers = [...arrowMarkers].map(
101031
+ ([color, markerName]) => `<marker id="${markerName}" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="${markerSize}" markerHeight="${markerSize}" orient="auto-start-reverse">
101032
+ <path d="M0,0 L10,5 L0,10 z" fill="${escAttr(color)}"/></marker>`
101033
+ ).join("\n");
101034
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" font-family="${escAttr(fontFamily)},Arial,sans-serif">
101035
+ ${attribution}<defs>${markers}</defs>
101036
+ <rect width="${width}" height="${height}" fill="${escAttr(background)}"/>
101037
+ ` + body + bandsSvg + "</svg>\n";
101038
+ }
101039
+ function render(model, view, scene, options) {
100352
101040
  const style = model.style;
100353
101041
  const fonts = fontSizes(style.font.size);
100354
101042
  const { edge: edgeFontSize, node: nodeFontSize, cont: containerFontSize } = fonts;
@@ -100364,10 +101052,10 @@ function render(model, view, scene) {
100364
101052
  chipTextDy: round1(11 * fonts.scale)
100365
101053
  };
100366
101054
  const scaled = (n) => round1(n * fonts.scale);
100367
- const { palette, kinds: kindDefaults, levels: levelDefaults } = themeFor(style.theme, view);
100368
- const isDarkTheme = ["dark", "nord", "classic-dark"].includes(style.theme);
101055
+ const { palette, kinds: kindDefaults } = options?.theme ? themeFromSpec(options.theme, view) : themeFor(style.theme, view);
101056
+ const onDarkGround = options?.theme ? options.theme.dark === true : isDarkTheme(style.theme);
100369
101057
  const defaultEdgeColor = style.flowStrokeColorSet ? style.flowStroke.color : style.accent ?? palette.edge;
100370
- const sourceHue = style.flowColor === "by-source" ? assignSourceHues(model, flowPalette[isDarkTheme ? "dark" : "light"]) : /* @__PURE__ */ new Map();
101058
+ const sourceHue = style.flowColor === "by-source" ? assignSourceHues(model, flowPalette[onDarkGround ? "dark" : "light"]) : /* @__PURE__ */ new Map();
100371
101059
  const flowColorOf = (flow) => flow?.style?.stroke?.color ?? (style.flowColor === "by-source" ? sourceHue.get(flow?.from ?? "") ?? defaultEdgeColor : defaultEdgeColor);
100372
101060
  const arrowMarkers = /* @__PURE__ */ new Map();
100373
101061
  const markerName = (color) => {
@@ -100384,16 +101072,15 @@ function render(model, view, scene) {
100384
101072
  const ui = UI[style.lang] ?? UI.en;
100385
101073
  const legendNames = style.lang === "fr" ? view.legendNamesFr : view.legendNames;
100386
101074
  const legendFlowLabel = style.lang === "fr" ? view.legendFlowLabelFr : view.legendFlowLabel;
101075
+ const legendLineStyles = style.lang === "fr" ? view.legendLineStylesFr : view.legendLineStyles;
100387
101076
  const elementStyle = /* @__PURE__ */ new Map();
100388
- const elementAttr = /* @__PURE__ */ new Map();
101077
+ const elementLogo = /* @__PURE__ */ new Map();
100389
101078
  for (const entry of collectElementStyles(model.elements)) {
100390
101079
  elementStyle.set(entry.id, entry.style);
100391
- elementAttr.set(entry.id, entry.attrValue);
101080
+ if (entry.logo) elementLogo.set(entry.id, entry.logo);
100392
101081
  }
100393
101082
  const resolveStyle = (kind, id) => {
100394
- let base = kindDefaults[kind] ?? {};
100395
- const level = elementAttr.get(id);
100396
- if (kind === "trust-zone" && level && levelDefaults?.[level]) base = levelDefaults[level];
101083
+ const base = kindDefaults[kind] ?? {};
100397
101084
  const perKind = style.kind[kind] ?? {};
100398
101085
  const inline = elementStyle.get(id) ?? {};
100399
101086
  return {
@@ -100428,14 +101115,16 @@ function render(model, view, scene) {
100428
101115
  });
100429
101116
  const overlapsAfter = countLabelOverlaps();
100430
101117
  compactVertical(scene);
101118
+ fitCanvas(scene);
101119
+ const drawnLogos = /* @__PURE__ */ new Set();
100431
101120
  const { renderContainerNode, renderLeafNode } = createNodeRenderers({
100432
101121
  palette,
100433
- style,
100434
- annot,
100435
101122
  nodeFontSize,
100436
101123
  containerFontSize,
100437
101124
  resolveStyle,
100438
- elementAttr
101125
+ elementLogo,
101126
+ resolvedLogos: options?.logos,
101127
+ drawnLogos
100439
101128
  });
100440
101129
  const { renderEdgePath, renderEdgeLabels } = createEdgePainter({
100441
101130
  scene,
@@ -100466,6 +101155,7 @@ function render(model, view, scene) {
100466
101155
  ui,
100467
101156
  legendNames,
100468
101157
  legendFlowLabel,
101158
+ legendLineStyles,
100469
101159
  scaled,
100470
101160
  objectName,
100471
101161
  resolveStyle,
@@ -100478,21 +101168,255 @@ function render(model, view, scene) {
100478
101168
  if (style.legend === "auto") bands.renderLegendBand();
100479
101169
  const bandsSvg = bands.bandsSvg();
100480
101170
  const bandY = bands.bandY();
100481
- const viewWidth = scene.width, viewHeight = scene.height;
100482
- const totalHeight = bandY > viewHeight ? bandY + 14 : viewHeight;
100483
- const markerSize = style.arrows === "large" ? round1(11 * fonts.scale) : 7;
101171
+ const viewHeight = scene.height;
100484
101172
  if (arrowMarkers.size === 0) markerName(defaultEdgeColor);
100485
- const markers = [...arrowMarkers].map(
100486
- ([color, markerName2]) => `<marker id="${markerName2}" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="${markerSize}" markerHeight="${markerSize}" orient="auto-start-reverse">
100487
- <path d="M0,0 L10,5 L0,10 z" fill="${escAttr(color)}"/></marker>`
100488
- ).join("\n");
100489
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${viewWidth} ${totalHeight}" font-family="${escAttr(style.font.family)},Arial,sans-serif">
100490
- <defs>${markers}</defs>
100491
- <rect width="${viewWidth}" height="${totalHeight}" fill="${escAttr(style.background ?? palette.background)}"/>
100492
- ` + body + bandsSvg + "</svg>\n";
101173
+ const svg = svgDocument({
101174
+ width: scene.width,
101175
+ height: bandY > viewHeight ? bandY + 14 : viewHeight,
101176
+ fontFamily: style.font.family,
101177
+ background: style.background ?? palette.background,
101178
+ arrowMarkers,
101179
+ markerSize: style.arrows === "large" ? round1(11 * fonts.scale) : 7,
101180
+ body,
101181
+ bandsSvg,
101182
+ // Read after the body is built, so it names the marks that were painted
101183
+ // rather than the ones the model asked for.
101184
+ attribution: logoAttributionComment(drawnLogos)
101185
+ });
100493
101186
  return { svg, overlapsBefore, overlapsAfter };
100494
101187
  }
100495
101188
 
101189
+ // src/css-colours.ts
101190
+ var HEX = "#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})";
101191
+ var NUMBER = String.raw`[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?`;
101192
+ var PERCENT = `${NUMBER}%`;
101193
+ var AMOUNT = `${NUMBER}%?`;
101194
+ var ANGLE = `${NUMBER}(?:deg|grad|rad|turn)?`;
101195
+ var commaForm = (first, second, third) => `${first}\\s*,\\s*${second}\\s*,\\s*${third}(?:\\s*,\\s*${AMOUNT})?`;
101196
+ var spaceForm = (first, second, third) => `${first}\\s+${second}\\s+${third}(?:\\s*/\\s*${AMOUNT})?`;
101197
+ var RGB = `rgba?\\(\\s*(?:${commaForm(NUMBER, NUMBER, NUMBER)}|${commaForm(PERCENT, PERCENT, PERCENT)}|${spaceForm(AMOUNT, AMOUNT, AMOUNT)})\\s*\\)`;
101198
+ var HSL = `hsla?\\(\\s*(?:${commaForm(ANGLE, PERCENT, PERCENT)}|${spaceForm(ANGLE, AMOUNT, AMOUNT)})\\s*\\)`;
101199
+ var NUMERIC_COLOUR = new RegExp(`^(?:${HEX}|${RGB}|${HSL})$`, "i");
101200
+ var COLOUR_KEYWORDS = /* @__PURE__ */ new Set([
101201
+ "currentcolor",
101202
+ "transparent",
101203
+ "aliceblue",
101204
+ "antiquewhite",
101205
+ "aqua",
101206
+ "aquamarine",
101207
+ "azure",
101208
+ "beige",
101209
+ "bisque",
101210
+ "black",
101211
+ "blanchedalmond",
101212
+ "blue",
101213
+ "blueviolet",
101214
+ "brown",
101215
+ "burlywood",
101216
+ "cadetblue",
101217
+ "chartreuse",
101218
+ "chocolate",
101219
+ "coral",
101220
+ "cornflowerblue",
101221
+ "cornsilk",
101222
+ "crimson",
101223
+ "cyan",
101224
+ "darkblue",
101225
+ "darkcyan",
101226
+ "darkgoldenrod",
101227
+ "darkgray",
101228
+ "darkgreen",
101229
+ "darkgrey",
101230
+ "darkkhaki",
101231
+ "darkmagenta",
101232
+ "darkolivegreen",
101233
+ "darkorange",
101234
+ "darkorchid",
101235
+ "darkred",
101236
+ "darksalmon",
101237
+ "darkseagreen",
101238
+ "darkslateblue",
101239
+ "darkslategray",
101240
+ "darkslategrey",
101241
+ "darkturquoise",
101242
+ "darkviolet",
101243
+ "deeppink",
101244
+ "deepskyblue",
101245
+ "dimgray",
101246
+ "dimgrey",
101247
+ "dodgerblue",
101248
+ "firebrick",
101249
+ "floralwhite",
101250
+ "forestgreen",
101251
+ "fuchsia",
101252
+ "gainsboro",
101253
+ "ghostwhite",
101254
+ "gold",
101255
+ "goldenrod",
101256
+ "gray",
101257
+ "green",
101258
+ "greenyellow",
101259
+ "grey",
101260
+ "honeydew",
101261
+ "hotpink",
101262
+ "indianred",
101263
+ "indigo",
101264
+ "ivory",
101265
+ "khaki",
101266
+ "lavender",
101267
+ "lavenderblush",
101268
+ "lawngreen",
101269
+ "lemonchiffon",
101270
+ "lightblue",
101271
+ "lightcoral",
101272
+ "lightcyan",
101273
+ "lightgoldenrodyellow",
101274
+ "lightgray",
101275
+ "lightgreen",
101276
+ "lightgrey",
101277
+ "lightpink",
101278
+ "lightsalmon",
101279
+ "lightseagreen",
101280
+ "lightskyblue",
101281
+ "lightslategray",
101282
+ "lightslategrey",
101283
+ "lightsteelblue",
101284
+ "lightyellow",
101285
+ "lime",
101286
+ "limegreen",
101287
+ "linen",
101288
+ "magenta",
101289
+ "maroon",
101290
+ "mediumaquamarine",
101291
+ "mediumblue",
101292
+ "mediumorchid",
101293
+ "mediumpurple",
101294
+ "mediumseagreen",
101295
+ "mediumslateblue",
101296
+ "mediumspringgreen",
101297
+ "mediumturquoise",
101298
+ "mediumvioletred",
101299
+ "midnightblue",
101300
+ "mintcream",
101301
+ "mistyrose",
101302
+ "moccasin",
101303
+ "navajowhite",
101304
+ "navy",
101305
+ "oldlace",
101306
+ "olive",
101307
+ "olivedrab",
101308
+ "orange",
101309
+ "orangered",
101310
+ "orchid",
101311
+ "palegoldenrod",
101312
+ "palegreen",
101313
+ "paleturquoise",
101314
+ "palevioletred",
101315
+ "papayawhip",
101316
+ "peachpuff",
101317
+ "peru",
101318
+ "pink",
101319
+ "plum",
101320
+ "powderblue",
101321
+ "purple",
101322
+ "rebeccapurple",
101323
+ "red",
101324
+ "rosybrown",
101325
+ "royalblue",
101326
+ "saddlebrown",
101327
+ "salmon",
101328
+ "sandybrown",
101329
+ "seagreen",
101330
+ "seashell",
101331
+ "sienna",
101332
+ "silver",
101333
+ "skyblue",
101334
+ "slateblue",
101335
+ "slategray",
101336
+ "slategrey",
101337
+ "snow",
101338
+ "springgreen",
101339
+ "steelblue",
101340
+ "tan",
101341
+ "teal",
101342
+ "thistle",
101343
+ "tomato",
101344
+ "turquoise",
101345
+ "violet",
101346
+ "wheat",
101347
+ "white",
101348
+ "whitesmoke",
101349
+ "yellow",
101350
+ "yellowgreen"
101351
+ ]);
101352
+ var isColour = (value) => NUMERIC_COLOUR.test(value) || COLOUR_KEYWORDS.has(value.toLowerCase());
101353
+
101354
+ // src/theme-spec.ts
101355
+ var DEFAULT_BASE = "light";
101356
+ var ThemeSpecError = class extends Error {
101357
+ };
101358
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
101359
+ var merge = (base, patch) => {
101360
+ const merged = { ...base };
101361
+ for (const [key, value] of Object.entries(patch)) {
101362
+ if (key === "extends") continue;
101363
+ const current = merged[key];
101364
+ merged[key] = isObject(value) && isObject(current) ? { ...current, ...value } : value;
101365
+ }
101366
+ return merged;
101367
+ };
101368
+ var SECTIONS = ["pal", "accentColors"];
101369
+ var arityOf = (section, key) => {
101370
+ if (section === "pal") return key === "chip" ? 3 : key === "badge" ? 2 : 1;
101371
+ return 1;
101372
+ };
101373
+ var describe = (value) => Array.isArray(value) ? `an array of ${value.length}` : typeof value;
101374
+ function checkEntry(path, value, arity) {
101375
+ if (arity === 1) {
101376
+ if (typeof value !== "string") {
101377
+ throw new ThemeSpecError(`\`${path}\` must be a colour, got ${describe(value)}`);
101378
+ }
101379
+ } else if (!Array.isArray(value) || value.length !== arity) {
101380
+ throw new ThemeSpecError(`\`${path}\` must be ${arity} colours, got ${describe(value)}`);
101381
+ }
101382
+ for (const colour of Array.isArray(value) ? value : [value]) {
101383
+ if (typeof colour !== "string" || !isColour(colour)) {
101384
+ throw new ThemeSpecError(`\`${path}\`: \`${String(colour)}\` is not a colour`);
101385
+ }
101386
+ }
101387
+ }
101388
+ function resolveThemeSpec(input) {
101389
+ if (!isObject(input)) throw new ThemeSpecError("a theme must be an object");
101390
+ const base = input.extends ?? DEFAULT_BASE;
101391
+ if (typeof base !== "string") throw new ThemeSpecError("`extends` must be a theme name");
101392
+ const baseSpec = Object.hasOwn(THEME_SPECS, base) ? THEME_SPECS[base] : void 0;
101393
+ if (!baseSpec) {
101394
+ throw new ThemeSpecError(
101395
+ `extends unknown theme \`${base}\` \u2014 pick one of: ${Object.keys(THEME_SPECS).join(", ")}`
101396
+ );
101397
+ }
101398
+ if ("dark" in input && typeof input.dark !== "boolean") {
101399
+ throw new ThemeSpecError("`dark` must be true or false");
101400
+ }
101401
+ for (const [section, value] of Object.entries(input)) {
101402
+ if (section === "extends" || section === "dark") continue;
101403
+ if (!SECTIONS.includes(section)) {
101404
+ throw new ThemeSpecError(
101405
+ `unknown section \`${section}\` \u2014 a theme overrides ${SECTIONS.join(", ")}`
101406
+ );
101407
+ }
101408
+ if (!isObject(value)) {
101409
+ throw new ThemeSpecError(
101410
+ `\`${section}\` must be an object of colours, got ${describe(value)}`
101411
+ );
101412
+ }
101413
+ for (const [key, colour] of Object.entries(value)) {
101414
+ checkEntry(`${section}.${key}`, colour, arityOf(section, key));
101415
+ }
101416
+ }
101417
+ return merge(baseSpec, input);
101418
+ }
101419
+
100496
101420
  // src/flow-matrix.ts
100497
101421
  var COLUMN_PRESENTATION = {
100498
101422
  num: { align: "middle", maxWidth: 220 },
@@ -100587,8 +101511,9 @@ function matrixMd(matrix) {
100587
101511
  for (const row of matrix.rows) outLines.push("| " + row.cells.map(mdCell).join(" | ") + " |");
100588
101512
  return outLines.join("\n") + "\n";
100589
101513
  }
100590
- function matrixSvg(matrix) {
100591
- const pal = palettes[matrix.style.theme] ?? lightPalette;
101514
+ function matrixSvg(matrix, options) {
101515
+ const requested = options?.theme;
101516
+ const pal = typeof requested === "object" ? paletteFromSpec(resolveThemeSpec(requested)) : paletteFor(requested ?? matrix.style.theme);
100592
101517
  const title = matrix.heading;
100593
101518
  const headers = matrix.columns.map((column) => column.label);
100594
101519
  const cells = matrix.rows.map((row) => row.cells);
@@ -100654,7 +101579,8 @@ function matrixSvg(matrix) {
100654
101579
  // src/compile.ts
100655
101580
  async function compile(source, options) {
100656
101581
  const { model, diags } = parse(source);
100657
- if (options?.theme) model.style.theme = options.theme;
101582
+ const theme = typeof options?.theme === "object" ? resolveThemeSpec(options.theme) : void 0;
101583
+ if (typeof options?.theme === "string") model.style.theme = options.theme;
100658
101584
  diags.push(...validate(model));
100659
101585
  const errors = diags.filter((diagnostic) => diagnostic.severity === "error");
100660
101586
  if (errors.length || !model.type || !views[model.type]) {
@@ -100664,7 +101590,7 @@ async function compile(source, options) {
100664
101590
  const matrix = options?.matrix ? buildFlowMatrix(model, view) : null;
100665
101591
  const scene = await layout(model, view);
100666
101592
  diags.push(...attachSideDiagnostics(scene, model));
100667
- const { svg, overlapsAfter } = render(model, view, scene);
101593
+ const { svg, overlapsAfter } = render(model, view, scene, { logos: options?.logos, theme });
100668
101594
  return {
100669
101595
  svg,
100670
101596
  diagnostics: diags,
@@ -100679,16 +101605,18 @@ async function compile(source, options) {
100679
101605
  }
100680
101606
 
100681
101607
  // src/api.ts
100682
- var version = true ? "1.0.0-RC13" : pkg.version;
101608
+ var version = true ? "1.0.0-RC15" : pkg.version;
100683
101609
 
100684
101610
  // src/playground.ts
100685
101611
  var ElkClass = import_elk_bundled.default;
100686
101612
  setElkFactory(() => new ElkClass());
100687
101613
  export {
101614
+ ThemeSpecError,
100688
101615
  compile,
100689
101616
  matrixCsv,
100690
101617
  matrixMd,
100691
101618
  matrixSvg,
101619
+ resolveThemeSpec,
100692
101620
  themeNames,
100693
101621
  version
100694
101622
  };