@likec4/language-server 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/browser.cjs +5 -1
  2. package/dist/browser.d.cts +9 -8
  3. package/dist/browser.d.mts +9 -8
  4. package/dist/browser.d.ts +9 -8
  5. package/dist/browser.mjs +3 -2
  6. package/dist/index.cjs +15 -2
  7. package/dist/index.d.cts +14 -11
  8. package/dist/index.d.mts +14 -11
  9. package/dist/index.d.ts +14 -11
  10. package/dist/index.mjs +17 -1
  11. package/dist/likec4lib.cjs +949 -952
  12. package/dist/likec4lib.d.cts +1 -1
  13. package/dist/likec4lib.d.mts +1 -1
  14. package/dist/likec4lib.d.ts +1 -1
  15. package/dist/likec4lib.mjs +949 -952
  16. package/dist/model-graph/index.cjs +1 -1
  17. package/dist/model-graph/index.mjs +1 -1
  18. package/dist/protocol.cjs +1 -1
  19. package/dist/protocol.d.cts +3 -1
  20. package/dist/protocol.d.mts +3 -1
  21. package/dist/protocol.d.ts +3 -1
  22. package/dist/protocol.mjs +1 -1
  23. package/dist/shared/{language-server.JWkqVjGv.cjs → language-server.C8lV6gDw.cjs} +120 -118
  24. package/dist/shared/{language-server.DBJJUUgF.mjs → language-server.CCOotWDz.mjs} +120 -119
  25. package/dist/shared/{language-server.DtBRb9os.mjs → language-server.CbqwHp7Q.mjs} +23 -9
  26. package/dist/shared/{language-server.CtKHXJDD.d.ts → language-server.Cyw-bCtc.d.ts} +145 -138
  27. package/dist/shared/{language-server.D-84I33F.d.mts → language-server.DGjTE7xL.d.mts} +145 -138
  28. package/dist/shared/{language-server.DwyCJvXm.cjs → language-server.DJhoJBWh.cjs} +17 -3
  29. package/dist/shared/{language-server.CjFzaJwI.d.cts → language-server.Ol32Kygo.d.cts} +145 -138
  30. package/package.json +34 -29
  31. package/src/Rpc.ts +10 -6
  32. package/src/ast.ts +1 -1
  33. package/src/browser.ts +5 -0
  34. package/src/formatting/LikeC4Formatter.ts +4 -2
  35. package/src/generated/ast.ts +99 -1
  36. package/src/generated/grammar.ts +1 -1
  37. package/src/generated/module.ts +1 -1
  38. package/src/generated-lib/icons.ts +949 -952
  39. package/src/index.ts +23 -2
  40. package/src/likec4lib.ts +1 -1
  41. package/src/logger.ts +16 -16
  42. package/src/lsp/CompletionProvider.ts +1 -1
  43. package/src/model/model-builder.ts +12 -12
  44. package/src/model-graph/compute-view/compute.ts +9 -4
  45. package/src/model-graph/utils/applyCustomRelationProperties.ts +1 -1
  46. package/src/model-graph/utils/uniqueTags.test.ts +42 -0
  47. package/src/model-graph/utils/uniqueTags.ts +19 -0
  48. package/src/protocol.ts +5 -1
  49. package/src/shared/WorkspaceManager.ts +1 -1
  50. package/dist/node.cjs +0 -18
  51. package/dist/node.d.cts +0 -20
  52. package/dist/node.d.mts +0 -20
  53. package/dist/node.d.ts +0 -20
  54. package/dist/node.mjs +0 -16
  55. package/src/node.ts +0 -20
@@ -1,8 +1,8 @@
1
1
  import { AbstractAstReflection, loadGrammarFromJson, DocumentState, AstUtils, GrammarUtils, MultiMap, CstUtils, stream, StreamImpl, DONE_RESULT, interruptAndCheck, Disposable, DefaultScopeComputation, DefaultScopeProvider, EMPTY_STREAM, StreamScope, MapScope, EMPTY_SCOPE, URI, UriUtils, isAstNode, DefaultWorkspaceManager, WorkspaceCache, inject, EmptyFileSystem } from 'langium';
2
2
  import { DefaultCompletionProvider, DefaultDocumentHighlightProvider, AstNodeHoverProvider, AbstractSemanticTokenProvider, DefaultWorkspaceSymbolProvider, Formatting, AbstractFormatter, createDefaultModule, createDefaultSharedModule } from 'langium/lsp';
3
3
  import { rootLogger, LogLevels, logger as logger$1 } from '@likec4/log';
4
+ import { isError, isNullish, isDefined, clamp, isTruthy, pipe, map, filter, isEmpty, find, values, first, last, unique, hasAtLeast, mapToObj, groupBy, forEach, prop, mapValues, flatMap, reduce, sort, reverse, indexBy, isNumber, isNonNullish, findLast, entries, debounce } from 'remeda';
4
5
  import { nonexhaustive, DefaultRelationshipColor, DefaultLineStyle, DefaultArrowType, AsFqn, parentFqn, nameFromFqn, isComputedElementView, isExtendsElementView, invariant, compareNatural, isElementView as isElementView$1, isScopedElementView, computeColorValues, sortByFqnHierarchically, compareRelations, isOrOperator, isNonEmptyArray, isAndOperator, isAncestor, hasAtLeast as hasAtLeast$1, isSameHierarchy } from '@likec4/core';
5
- import { isNullish, isDefined, clamp, isTruthy, pipe, map, filter, isEmpty, find, values, first, last, unique, hasAtLeast, mapToObj, groupBy, forEach, prop, mapValues, flatMap, reduce, sort, reverse, indexBy, isNumber, isNonNullish, findLast, entries, debounce } from 'remeda';
6
6
  import { DiagnosticSeverity, SymbolKind, SemanticTokenTypes, SemanticTokenModifiers, TextEdit, CompletionItemKind } from 'vscode-languageserver-types';
7
7
  import indentString from 'indent-string';
8
8
  import { decode, encode } from '@msgpack/msgpack';
@@ -13,100 +13,11 @@ import { URI as URI$1 } from 'vscode-uri';
13
13
  import stripIndent from 'strip-indent';
14
14
  import hash from 'string-hash';
15
15
  import { deepEqual } from 'fast-equals';
16
- import { i as isAcyclic, f as findCycles, p as postorder, L as LikeC4ModelGraph, a as computeView, b as computeDynamicView } from './language-server.DtBRb9os.mjs';
16
+ import { i as isAcyclic, f as findCycles, p as postorder, L as LikeC4ModelGraph, a as computeView, b as computeDynamicView } from './language-server.CbqwHp7Q.mjs';
17
17
  import { hasProtocol, hasLeadingSlash, isRelative, withoutLeadingSlash, withoutBase, parsePath } from 'ufo';
18
18
  import { Graph } from '@dagrejs/graphlib';
19
19
  import { DocumentHighlight, DocumentHighlightKind } from 'vscode-languageserver';
20
20
 
21
- const logger = rootLogger.withTag("lsp");
22
- function logError(err) {
23
- logger.error(err);
24
- }
25
- function logWarnError(err) {
26
- if (err instanceof Error) {
27
- logger.warn(err.stack ?? err.message);
28
- return;
29
- }
30
- logger.warn(err);
31
- }
32
- function setLogLevel(level) {
33
- logger.level = LogLevels[level];
34
- }
35
- function logErrorToTelemetry(connection) {
36
- const reporter = {
37
- log: ({ level, ...logObj }, ctx) => {
38
- if (level !== LogLevels.error && level !== LogLevels.fatal) {
39
- return;
40
- }
41
- const tag = logObj.tag || "";
42
- const parts = logObj.args.map((arg) => {
43
- if (arg && typeof arg.stack === "string") {
44
- return arg.message + "\n" + arg.stack;
45
- }
46
- if (typeof arg === "string") {
47
- return arg;
48
- }
49
- return String(arg);
50
- });
51
- if (tag) {
52
- parts.unshift(`[${tag}]`);
53
- }
54
- const message = parts.join(" ");
55
- connection.telemetry.logEvent({ eventName: "error", error: message });
56
- }
57
- };
58
- rootLogger.addReporter(reporter);
59
- logger.setReporters(rootLogger.options.reporters);
60
- }
61
- function logToLspConnection(connection) {
62
- const reporter = {
63
- log: ({ level, ...logObj }, ctx) => {
64
- const tag = logObj.tag || "";
65
- const parts = logObj.args.map((arg) => {
66
- if (arg && typeof arg.stack === "string") {
67
- return arg.message + "\n" + arg.stack;
68
- }
69
- if (typeof arg === "string") {
70
- return arg;
71
- }
72
- return String(arg);
73
- });
74
- if (tag) {
75
- parts.unshift(`[${tag}]`);
76
- }
77
- const message = parts.join(" ");
78
- switch (true) {
79
- case level >= LogLevels.trace: {
80
- connection.tracer.log(message);
81
- break;
82
- }
83
- case level >= LogLevels.debug: {
84
- connection.console.debug(message);
85
- break;
86
- }
87
- case level >= LogLevels.info: {
88
- connection.console.info(message);
89
- break;
90
- }
91
- case level >= LogLevels.log: {
92
- connection.console.log(message);
93
- break;
94
- }
95
- case level >= LogLevels.warn: {
96
- connection.console.warn(message);
97
- break;
98
- }
99
- case level >= LogLevels.fatal: {
100
- connection.console.error(message);
101
- break;
102
- }
103
- }
104
- }
105
- };
106
- rootLogger.addReporter(reporter);
107
- logger.setReporters(rootLogger.options.reporters);
108
- }
109
-
110
21
  const DynamicViewRule = "DynamicViewRule";
111
22
  function isDynamicViewRule(item) {
112
23
  return reflection.isInstance(item, DynamicViewRule);
@@ -1488,6 +1399,94 @@ const LikeC4GeneratedModule = {
1488
1399
  }
1489
1400
  };
1490
1401
 
1402
+ const logger = rootLogger.withTag("lsp");
1403
+ function logError(err) {
1404
+ logger.error(err);
1405
+ }
1406
+ function logWarnError(err) {
1407
+ if (err instanceof Error) {
1408
+ logger.warn(err.stack ?? err.message);
1409
+ return;
1410
+ }
1411
+ logger.warn(err);
1412
+ }
1413
+ function setLogLevel(level) {
1414
+ logger.level = LogLevels[level];
1415
+ }
1416
+ function logErrorToTelemetry(connection) {
1417
+ const reporter = {
1418
+ log: ({ level, ...logObj }, ctx) => {
1419
+ if (level !== LogLevels.error && level !== LogLevels.fatal) {
1420
+ return;
1421
+ }
1422
+ const tag = logObj.tag || "";
1423
+ const parts = logObj.args.map((arg) => {
1424
+ if (isError(arg)) {
1425
+ return arg.stack ?? arg.message;
1426
+ }
1427
+ if (typeof arg === "string") {
1428
+ return arg;
1429
+ }
1430
+ return "" + arg;
1431
+ });
1432
+ if (tag) {
1433
+ parts.unshift(`[${tag}]`);
1434
+ }
1435
+ const message = parts.join(" ");
1436
+ connection.telemetry.logEvent({ eventName: "error", error: message });
1437
+ }
1438
+ };
1439
+ rootLogger.addReporter(reporter);
1440
+ logger.setReporters(rootLogger.options.reporters);
1441
+ }
1442
+ function logToLspConnection(connection) {
1443
+ const reporter = {
1444
+ log: ({ level, ...logObj }, ctx) => {
1445
+ const tag = logObj.tag || "";
1446
+ const parts = logObj.args.map((arg) => {
1447
+ if (isError(arg)) {
1448
+ return arg.stack ?? arg.message;
1449
+ }
1450
+ if (typeof arg === "string") {
1451
+ return arg;
1452
+ }
1453
+ return "" + arg;
1454
+ });
1455
+ if (tag) {
1456
+ parts.unshift(`[${tag}]`);
1457
+ }
1458
+ const message = parts.join(" ");
1459
+ switch (true) {
1460
+ case level >= LogLevels.debug: {
1461
+ connection.console.debug(message);
1462
+ break;
1463
+ }
1464
+ // case level >= LogLevels.info: {
1465
+ // connection.console.info(message)
1466
+ // break
1467
+ // }
1468
+ case level >= LogLevels.log: {
1469
+ connection.console.info(message);
1470
+ break;
1471
+ }
1472
+ case level >= LogLevels.warn: {
1473
+ connection.console.warn(message);
1474
+ break;
1475
+ }
1476
+ case level >= LogLevels.fatal: {
1477
+ connection.console.error(message);
1478
+ break;
1479
+ }
1480
+ default: {
1481
+ connection.console.log(message);
1482
+ }
1483
+ }
1484
+ }
1485
+ };
1486
+ rootLogger.setReporters([reporter]);
1487
+ logger.setReporters(rootLogger.options.reporters);
1488
+ }
1489
+
1491
1490
  function elementRef(node) {
1492
1491
  return node.el.ref;
1493
1492
  }
@@ -1609,7 +1608,7 @@ function checksFromDiagnostics(doc) {
1609
1608
  };
1610
1609
  }
1611
1610
  function* streamModel(doc, isValid) {
1612
- const traverseStack = doc.parseResult.value.models.flatMap((m) => isValid(m) ? m.elements : []);
1611
+ const traverseStack = doc.parseResult.value.models.flatMap((m) => m.elements);
1613
1612
  const relations = [];
1614
1613
  let el;
1615
1614
  while (el = traverseStack.shift()) {
@@ -2540,8 +2539,6 @@ class FqnIndex {
2540
2539
  }
2541
2540
  }
2542
2541
 
2543
- const printDocs = (docs) => docs.map((d) => " - " + d.uri.toString(true)).join("\n");
2544
-
2545
2542
  function assignNavigateTo(views) {
2546
2543
  const allElementViews = /* @__PURE__ */ new Map();
2547
2544
  for (const v of views) {
@@ -2935,8 +2932,7 @@ class LikeC4ModelBuilder {
2935
2932
  async (docs, _cancelToken) => {
2936
2933
  let parsed = [];
2937
2934
  try {
2938
- logger.debug(`[ModelBuilder] onValidated (${docs.length} docs)
2939
- ${printDocs(docs)}`);
2935
+ logger.debug(`[ModelBuilder] onValidated (${docs.length} docs)`);
2940
2936
  for (const doc of parser.parse(docs)) {
2941
2937
  parsed.push(doc.uri);
2942
2938
  }
@@ -2966,8 +2962,7 @@ ${printDocs(docs)}`);
2966
2962
  logger.debug("[ModelBuilder] No documents to build model from");
2967
2963
  return null;
2968
2964
  }
2969
- logger.debug(`[ModelBuilder] buildModel from ${docs.length} docs:
2970
- ${printDocs(docs)}`);
2965
+ logger.debug(`[ModelBuilder] onValidated (${docs.length} docs)`);
2971
2966
  return buildModel(this.services, docs);
2972
2967
  });
2973
2968
  }
@@ -3024,14 +3019,14 @@ ${printDocs(docs)}`);
3024
3019
  if (cache.has(CACHE_KEY_COMPUTED_MODEL)) {
3025
3020
  return cache.get(CACHE_KEY_COMPUTED_MODEL);
3026
3021
  }
3027
- const model = await this.buildModel(cancelToken);
3028
- if (!model) {
3029
- return null;
3030
- }
3031
3022
  return await this.services.shared.workspace.WorkspaceLock.read(async () => {
3032
3023
  if (cancelToken) {
3033
3024
  await interruptAndCheck(cancelToken);
3034
3025
  }
3026
+ const model = this.unsafeSyncBuildModel();
3027
+ if (!model) {
3028
+ return null;
3029
+ }
3035
3030
  return this.unsafeSyncBuildComputedModel(model);
3036
3031
  });
3037
3032
  }
@@ -3041,17 +3036,17 @@ ${printDocs(docs)}`);
3041
3036
  if (cache.has(cacheKey)) {
3042
3037
  return cache.get(cacheKey);
3043
3038
  }
3044
- const model = await this.buildModel(cancelToken);
3045
- const view = model?.views[viewId];
3046
- if (!view) {
3047
- logger.warn(`[ModelBuilder] Cannot find view ${viewId}`);
3048
- return null;
3049
- }
3050
3039
  return await this.services.shared.workspace.WorkspaceLock.read(async () => {
3051
3040
  if (cancelToken) {
3052
3041
  await interruptAndCheck(cancelToken);
3053
3042
  }
3054
3043
  return cache.get(cacheKey, () => {
3044
+ const model = this.unsafeSyncBuildModel();
3045
+ const view = model?.views[viewId];
3046
+ if (!view) {
3047
+ logger.warn(`[ModelBuilder] Cannot find view ${viewId}`);
3048
+ return null;
3049
+ }
3055
3050
  const index = new LikeC4ModelGraph(model);
3056
3051
  const result = isElementView$1(view) ? computeView(view, index) : computeDynamicView(view, index);
3057
3052
  if (!result.isSuccess) {
@@ -4737,14 +4732,16 @@ class Rpc {
4737
4732
  const LangiumDocuments = this.services.shared.workspace.LangiumDocuments;
4738
4733
  const DocumentBuilder = this.services.shared.workspace.DocumentBuilder;
4739
4734
  const notifyModelParsed = debounce(
4740
- () => void connection.sendNotification(onDidChangeModel, "").catch((e) => {
4741
- logger.error(`[ServerRpc] error sending onDidChangeModel: ${e}`);
4742
- return Promise.resolve();
4743
- }),
4735
+ () => {
4736
+ connection.sendNotification(onDidChangeModel, "").catch((e) => {
4737
+ logger.error(`[ServerRpc] error sending onDidChangeModel: ${e}`);
4738
+ return Promise.resolve();
4739
+ });
4740
+ },
4744
4741
  {
4745
4742
  timing: "both",
4746
- waitMs: 350,
4747
- maxWaitMs: 1e3
4743
+ waitMs: 300,
4744
+ maxWaitMs: 600
4748
4745
  }
4749
4746
  );
4750
4747
  let isFirstBuild = true;
@@ -4753,7 +4750,10 @@ class Rpc {
4753
4750
  notifyModelParsed.cancel();
4754
4751
  }),
4755
4752
  modelBuilder.onModelParsed(() => notifyModelParsed.call()),
4756
- connection.onRequest(fetchComputedModel, async (cancelToken) => {
4753
+ connection.onRequest(fetchComputedModel, async ({ cleanCaches }, cancelToken) => {
4754
+ if (cleanCaches) {
4755
+ this.services.WorkspaceCache.clear();
4756
+ }
4757
4757
  const model = await modelBuilder.buildComputedModel(cancelToken);
4758
4758
  return { model };
4759
4759
  }),
@@ -4929,8 +4929,8 @@ class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
4929
4929
  * your language, which can be either loaded from provided files or constructed in memory.
4930
4930
  */
4931
4931
  async loadAdditionalDocuments(folders, collector) {
4932
- await super.loadAdditionalDocuments(folders, collector);
4933
4932
  collector(this.documentFactory.fromString(LibIcons, URI$1.parse(Uri)));
4933
+ await super.loadAdditionalDocuments(folders, collector);
4934
4934
  }
4935
4935
  workspace() {
4936
4936
  if (this.folders && hasAtLeast$1(this.folders, 1)) {
@@ -5450,7 +5450,8 @@ class LikeC4Formatter extends AbstractFormatter {
5450
5450
  }
5451
5451
  formatRelation(node) {
5452
5452
  this.on(node, isRelation, (n, f) => {
5453
- f.property("source").append(FormattingOptions.oneSpace);
5453
+ const sourceNodes = n?.source?.$cstNode ? [n?.source?.$cstNode] : [];
5454
+ f.cst(sourceNodes).append(FormattingOptions.oneSpace);
5454
5455
  f.keywords("]->").prepend(FormattingOptions.noSpace);
5455
5456
  f.keywords("-[").append(FormattingOptions.noSpace);
5456
5457
  f.properties("target", "title", "technology", "tags").prepend(FormattingOptions.oneSpace);
@@ -5468,7 +5469,7 @@ class LikeC4Formatter extends AbstractFormatter {
5468
5469
  f.keywords("-[").append(FormattingOptions.noSpace);
5469
5470
  });
5470
5471
  this.on(node, isIncomingRelationExpression)?.keywords("->").append(FormattingOptions.oneSpace);
5471
- this.on(node, isInOutRelationExpression)?.property("inout").append(FormattingOptions.oneSpace);
5472
+ this.on(node, isInOutRelationExpression)?.keyword("->").prepend(FormattingOptions.oneSpace);
5472
5473
  }
5473
5474
  removeIndentFromTopLevelStatements(node) {
5474
5475
  if (isModel(node) || isSpecificationRule(node) || isModelViews(node) || isLikeC4Lib(node)) {
@@ -5734,4 +5735,4 @@ function _merge(target, source) {
5734
5735
  return target;
5735
5736
  }
5736
5737
 
5737
- export { LikeC4Module as L, createLanguageServices as a, createCustomLanguageServices as c, setLogLevel as s };
5738
+ export { LikeC4Module as L, createCustomLanguageServices as a, createLanguageServices as c, logger as l, setLogLevel as s };
@@ -1,5 +1,5 @@
1
- import { Expr, whereOperatorAsPredicate, parentFqn, nonexhaustive, isViewRuleStyle, compareByFqnHierarchically, DefaultThemeColor, DefaultElementShape, compareRelations, invariant, nonNullable, isAncestor, isViewRulePredicate, ancestorsFqn, isViewRuleAutoLayout, isScopedElementView, commonAncestor, StepEdgeId, isDynamicViewParallelSteps, isDynamicViewIncludeRule, DefaultRelationshipColor, DefaultLineStyle, DefaultArrowType, isSameHierarchy } from '@likec4/core';
2
- import { pipe, map, pick, mapToObj, isTruthy, isNullish, omitBy, isEmpty, filter, anyPass, isDefined, groupBy, prop, mapValues, piped, unique, entries, flatMap, sortBy, sort, difference, allPass, omit, hasAtLeast, reduce, only, isNonNull, isString, isArray } from 'remeda';
1
+ import { Expr, whereOperatorAsPredicate, parentFqn, nonexhaustive, isViewRuleStyle, compareByFqnHierarchically, DefaultThemeColor, DefaultElementShape, compareRelations, invariant, nonNullable, compareNatural, hasAtLeast, isAncestor, isViewRulePredicate, ancestorsFqn, isViewRuleAutoLayout, isScopedElementView, commonAncestor, StepEdgeId, isDynamicViewParallelSteps, isDynamicViewIncludeRule, DefaultRelationshipColor, DefaultLineStyle, DefaultArrowType, isSameHierarchy } from '@likec4/core';
2
+ import { pipe, map, pick, mapToObj, isTruthy, isNullish, omitBy, isEmpty, filter, anyPass, isDefined, groupBy, prop, mapValues, piped, unique, entries, flatMap, sortBy, sort, difference, allPass, omit, hasAtLeast as hasAtLeast$1, only, reduce, isNonNull, isString, isArray } from 'remeda';
3
3
  import graphlib, { Graph } from '@dagrejs/graphlib';
4
4
  import objectHash from 'object-hash';
5
5
 
@@ -366,6 +366,16 @@ function sortNodes({
366
366
  return sorted;
367
367
  }
368
368
 
369
+ function uniqueTags(elements) {
370
+ const tags = pipe(
371
+ elements,
372
+ flatMap((e) => e.tags ?? []),
373
+ unique(),
374
+ sort(compareNatural)
375
+ );
376
+ return hasAtLeast(tags, 1) ? tags : null;
377
+ }
378
+
369
379
  const NoFilter = () => true;
370
380
  const Identity = (x) => x;
371
381
  const filterBy = (pred) => pred === NoFilter ? Identity : filter(pred);
@@ -865,7 +875,7 @@ class ComputeCtx {
865
875
  }
866
876
  computeEdges() {
867
877
  return this.ctxEdges.map((e) => {
868
- invariant(hasAtLeast(e.relations, 1), "Edge must have at least one relation");
878
+ invariant(hasAtLeast$1(e.relations, 1), "Edge must have at least one relation");
869
879
  const relations = sort(e.relations, compareRelations);
870
880
  const source = e.source.id;
871
881
  const target = e.target.id;
@@ -878,7 +888,11 @@ class ComputeCtx {
878
888
  relations: relations.map((r) => r.id)
879
889
  };
880
890
  let relation;
881
- relation = relations.length === 1 ? relations[0] : relations.find((r) => r.source === source && r.target === target);
891
+ relation = only(relations) ?? pipe(
892
+ relations,
893
+ filter((r) => r.source === source && r.target === target),
894
+ only()
895
+ );
882
896
  if (!relation) {
883
897
  const allprops = pipe(
884
898
  relations,
@@ -935,7 +949,7 @@ class ComputeCtx {
935
949
  navigateTo: only(allprops.navigateTo)
936
950
  };
937
951
  }
938
- const tags = unique(flatMap(relations, (r) => r.tags ?? []));
952
+ const tags = uniqueTags(relations);
939
953
  return Object.assign(
940
954
  edge,
941
955
  this.getEdgeLabel(relation),
@@ -947,7 +961,7 @@ class ComputeCtx {
947
961
  relation.head && { head: relation.head },
948
962
  relation.tail && { tail: relation.tail },
949
963
  relation.navigateTo && { navigateTo: relation.navigateTo },
950
- hasAtLeast(tags, 1) && { tags }
964
+ tags && { tags }
951
965
  );
952
966
  });
953
967
  }
@@ -969,7 +983,7 @@ class ComputeCtx {
969
983
  }
970
984
  addEdges(edges) {
971
985
  for (const e of edges) {
972
- if (!hasAtLeast(e.relations, 1)) {
986
+ if (!hasAtLeast$1(e.relations, 1)) {
973
987
  continue;
974
988
  }
975
989
  const existing = this.ctxEdges.find(
@@ -1357,8 +1371,8 @@ class DynamicViewComputeCtx {
1357
1371
  filter(isTruthy),
1358
1372
  unique()
1359
1373
  );
1360
- const tags = hasAtLeast(alltags, 1) ? alltags : null;
1361
- const relations = hasAtLeast(relationships, 1) ? map(relationships, (r) => r.id) : null;
1374
+ const tags = hasAtLeast$1(alltags, 1) ? alltags : null;
1375
+ const relations = hasAtLeast$1(relationships, 1) ? map(relationships, (r) => r.id) : null;
1362
1376
  const relation = only(relationships) || relationships.find((r) => r.source === source.id && r.target === target.id);
1363
1377
  const title = isTruthy(relation?.title) ? relation.title : pipe(
1364
1378
  relationships,