@likec4/language-server 1.14.0 → 1.15.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 (43) hide show
  1. package/contrib/likec4.tmLanguage.json +1 -1
  2. package/dist/browser.cjs +1 -1
  3. package/dist/browser.d.cts +2 -2
  4. package/dist/browser.d.mts +2 -2
  5. package/dist/browser.d.ts +2 -2
  6. package/dist/browser.mjs +2 -2
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.d.cts +2 -2
  9. package/dist/index.d.mts +2 -2
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.mjs +2 -2
  12. package/dist/likec4lib.cjs +0 -1
  13. package/dist/likec4lib.mjs +0 -1
  14. package/dist/model-graph/index.cjs +1 -1
  15. package/dist/model-graph/index.mjs +1 -1
  16. package/dist/shared/{language-server.CbDa016p.cjs → language-server.80ITEDo5.cjs} +272 -64
  17. package/dist/shared/{language-server.CITj0XN3.cjs → language-server.BUtiWTKg.cjs} +188 -30
  18. package/dist/shared/{language-server.DFLaUdYu.mjs → language-server.DXC9g4_f.mjs} +274 -66
  19. package/dist/shared/{language-server.CO6aEDQm.d.mts → language-server.DfMwkd2l.d.mts} +46 -15
  20. package/dist/shared/{language-server.Cqyh6-9S.d.cts → language-server.U2piOAVt.d.cts} +46 -15
  21. package/dist/shared/{language-server.BI99piRy.d.ts → language-server.j-ShR6as.d.ts} +46 -15
  22. package/dist/shared/{language-server.DZYziEuF.mjs → language-server.zY53FGJE.mjs} +189 -31
  23. package/package.json +9 -9
  24. package/src/ast.ts +1 -0
  25. package/src/generated/ast.ts +103 -19
  26. package/src/generated/grammar.ts +1 -1
  27. package/src/generated-lib/icons.ts +0 -1
  28. package/src/like-c4.langium +32 -9
  29. package/src/lsp/CompletionProvider.ts +70 -2
  30. package/src/model/model-builder.ts +0 -1
  31. package/src/model/model-parser.ts +71 -20
  32. package/src/model-graph/compute-view/__test__/fixture.ts +45 -4
  33. package/src/model-graph/compute-view/compute.ts +223 -40
  34. package/src/model-graph/compute-view/predicates.ts +12 -6
  35. package/src/model-graph/utils/applyCustomElementProperties.ts +18 -4
  36. package/src/model-graph/utils/applyCustomRelationProperties.ts +2 -1
  37. package/src/model-graph/utils/applyViewRuleStyles.ts +30 -25
  38. package/src/model-graph/utils/buildComputeNodes.ts +69 -17
  39. package/src/model-graph/utils/elementExpressionToPredicate.ts +3 -1
  40. package/src/model-graph/utils/sortNodes.ts +11 -7
  41. package/src/references/scope-computation.ts +3 -2
  42. package/src/validation/index.ts +2 -2
  43. package/src/validation/specification.ts +4 -4
@@ -1,5 +1,5 @@
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';
1
+ import { Expr, whereOperatorAsPredicate, parentFqn, nonexhaustive, ComputedNode, isViewRuleGroup, isViewRulePredicate, isViewRuleStyle, nonNullable, ElementKind, compareByFqnHierarchically, DefaultThemeColor, DefaultElementShape, compareRelations, invariant, compareNatural, hasAtLeast, isAncestor, commonHead, isViewRuleAutoLayout, isScopedElementView, commonAncestor, StepEdgeId, isDynamicViewParallelSteps, isDynamicViewIncludeRule, DefaultRelationshipColor, DefaultLineStyle, DefaultArrowType, ancestorsFqn, isSameHierarchy } from '@likec4/core';
2
+ import { pipe, map, pick, mapToObj, isTruthy, isNullish, omitBy, isEmpty, filter, isNot, anyPass, forEach, isDefined, groupBy, prop, mapValues, piped, unique, entries, flatMap, sortBy, sort, tap, difference, allPass, last, reverse, concat, omit, hasAtLeast as hasAtLeast$1, only, reduce, isNonNull, first, isString, isArray } from 'remeda';
3
3
  import dagre from '@dagrejs/dagre';
4
4
  import objectHash from 'object-hash';
5
5
 
@@ -54,8 +54,19 @@ function elementExprToPredicate(target) {
54
54
  nonexhaustive(target);
55
55
  }
56
56
 
57
+ function flattenGroupRules(guard) {
58
+ return (rule) => {
59
+ if (isViewRuleGroup(rule)) {
60
+ return rule.groupRules.flatMap(flattenGroupRules(guard));
61
+ }
62
+ if (isViewRulePredicate(rule)) {
63
+ return "include" in rule ? rule.include.filter(guard) : [];
64
+ }
65
+ return [];
66
+ };
67
+ }
57
68
  function applyCustomElementProperties(_rules, _nodes) {
58
- const rules = _rules.flatMap((r) => "include" in r ? r.include.filter(Expr.isCustomElement) : []);
69
+ const rules = _rules.flatMap(flattenGroupRules(Expr.isCustomElement));
59
70
  if (rules.length === 0) {
60
71
  return _nodes;
61
72
  }
@@ -67,7 +78,7 @@ function applyCustomElementProperties(_rules, _nodes) {
67
78
  const notEmpty = !isEmpty(rest);
68
79
  const satisfies = elementExprToPredicate(expr);
69
80
  nodes.forEach((node, i) => {
70
- if (!satisfies(node)) {
81
+ if (ComputedNode.isNodesGroup(node) || !satisfies(node)) {
71
82
  return;
72
83
  }
73
84
  if (notEmpty) {
@@ -128,7 +139,7 @@ function relationExpressionToPredicates(expr) {
128
139
  }
129
140
  }
130
141
  function applyCustomRelationProperties(_rules, nodes, _edges) {
131
- const rules = _rules.flatMap((r) => "include" in r ? r.include.filter(Expr.isCustomRelationExpr) : []);
142
+ const rules = _rules.flatMap(flattenGroupRules(Expr.isCustomRelationExpr));
132
143
  const edges = Array.from(_edges);
133
144
  if (rules.length === 0 || edges.length === 0) {
134
145
  return edges;
@@ -171,29 +182,34 @@ function applyViewRuleStyles(_rules, nodes) {
171
182
  }
172
183
  predicates.push(elementExprToPredicate(target));
173
184
  }
174
- filter(nodes, anyPass(predicates)).forEach((n) => {
175
- n.shape = rule.style.shape ?? n.shape;
176
- n.color = rule.style.color ?? n.color;
177
- if (isDefined(rule.style.icon)) {
178
- n.icon = rule.style.icon;
179
- }
180
- if (isDefined(rule.notation)) {
181
- n.notation = rule.notation;
182
- }
183
- let styleOverride;
184
- if (isDefined(rule.style.border)) {
185
- styleOverride = { border: rule.style.border };
186
- }
187
- if (isDefined(rule.style.opacity)) {
188
- styleOverride = { ...styleOverride, opacity: rule.style.opacity };
189
- }
190
- if (styleOverride) {
191
- n.style = {
192
- ...n.style,
193
- ...styleOverride
194
- };
195
- }
196
- });
185
+ pipe(
186
+ nodes,
187
+ filter(isNot(ComputedNode.isNodesGroup)),
188
+ filter(anyPass(predicates)),
189
+ forEach((n) => {
190
+ n.shape = rule.style.shape ?? n.shape;
191
+ n.color = rule.style.color ?? n.color;
192
+ if (isDefined(rule.style.icon)) {
193
+ n.icon = rule.style.icon;
194
+ }
195
+ if (isDefined(rule.notation)) {
196
+ n.notation = rule.notation;
197
+ }
198
+ let styleOverride;
199
+ if (isDefined(rule.style.border)) {
200
+ styleOverride = { border: rule.style.border };
201
+ }
202
+ if (isDefined(rule.style.opacity)) {
203
+ styleOverride = { ...styleOverride, opacity: rule.style.opacity };
204
+ }
205
+ if (styleOverride) {
206
+ n.style = {
207
+ ...n.style,
208
+ ...styleOverride
209
+ };
210
+ }
211
+ })
212
+ );
197
213
  }
198
214
  return nodes;
199
215
  }
@@ -209,40 +225,80 @@ function updateDepthOfAncestors(node, nodes) {
209
225
  node = parentNd;
210
226
  }
211
227
  }
212
- function buildComputeNodes(elements) {
213
- return Array.from(elements).sort(compareByFqnHierarchically).reduce((map, { id, color, shape, style, ...el }) => {
228
+ function buildComputeNodes(elements, groups) {
229
+ const nodesMap = /* @__PURE__ */ new Map();
230
+ const elementToGroup = /* @__PURE__ */ new Map();
231
+ groups?.forEach(({ id, parent, viewRule, explicits }) => {
232
+ if (parent) {
233
+ nonNullable(nodesMap.get(parent), `Parent group node ${parent} not found`).children.push(id);
234
+ }
235
+ nodesMap.set(id, {
236
+ id,
237
+ parent,
238
+ kind: ElementKind.Group,
239
+ title: viewRule.title ?? "",
240
+ color: viewRule.color ?? "muted",
241
+ shape: "rectangle",
242
+ children: [],
243
+ inEdges: [],
244
+ outEdges: [],
245
+ level: 0,
246
+ depth: 0,
247
+ description: null,
248
+ technology: null,
249
+ tags: null,
250
+ links: null,
251
+ style: {
252
+ border: viewRule.border ?? "dashed",
253
+ opacity: viewRule.opacity ?? 0
254
+ }
255
+ });
256
+ for (const e of explicits) {
257
+ elementToGroup.set(e.id, id);
258
+ }
259
+ });
260
+ return Array.from(elements).sort(compareByFqnHierarchically).reduce((map, { id, color, shape, style, kind, title, ...el }) => {
214
261
  let parent = parentFqn(id);
215
262
  let level = 0;
263
+ let parentNd;
216
264
  while (parent) {
217
- const parentNd = map.get(parent);
265
+ parentNd = map.get(parent);
218
266
  if (parentNd) {
219
- if (parentNd.children.length == 0) {
220
- parentNd.depth = 1;
221
- updateDepthOfAncestors(parentNd, map);
222
- }
223
- parentNd.children.push(id);
224
- level = parentNd.level + 1;
225
267
  break;
226
268
  }
227
269
  parent = parentFqn(parent);
228
270
  }
271
+ if (!parentNd && elementToGroup.has(id)) {
272
+ parent = elementToGroup.get(id);
273
+ parentNd = map.get(parent);
274
+ }
275
+ if (parentNd) {
276
+ if (parentNd.children.length == 0) {
277
+ parentNd.depth = 1;
278
+ updateDepthOfAncestors(parentNd, map);
279
+ }
280
+ parentNd.children.push(id);
281
+ level = parentNd.level + 1;
282
+ }
229
283
  const node = {
230
- ...el,
231
284
  id,
232
285
  parent,
233
- level,
286
+ kind,
287
+ title,
234
288
  color: color ?? DefaultThemeColor,
235
289
  shape: shape ?? DefaultElementShape,
236
290
  children: [],
237
291
  inEdges: [],
238
292
  outEdges: [],
293
+ level,
294
+ ...el,
239
295
  style: {
240
296
  ...style
241
297
  }
242
298
  };
243
299
  map.set(id, node);
244
300
  return map;
245
- }, /* @__PURE__ */ new Map());
301
+ }, nodesMap);
246
302
  }
247
303
 
248
304
  function buildElementNotations(nodes) {
@@ -314,9 +370,16 @@ function sortNodes({
314
370
  nodes,
315
371
  edges
316
372
  }) {
317
- if (edges.length === 0) {
373
+ if (nodes.length < 2) {
318
374
  return nodes;
319
375
  }
376
+ if (edges.length === 0) {
377
+ return pipe(
378
+ nodes,
379
+ sort(compareByFqnHierarchically),
380
+ tap(sortChildren)
381
+ );
382
+ }
320
383
  const g = new Graph({
321
384
  compound: false,
322
385
  directed: true,
@@ -351,15 +414,15 @@ function sortNodes({
351
414
  if (sources.length === 0) {
352
415
  sources = pipe(
353
416
  nodes,
354
- sort(compareByFqnHierarchically),
355
417
  filter((n) => n.inEdges.length === 0 || n.parent === null),
418
+ sort(compareByFqnHierarchically),
356
419
  map((n) => n.id)
357
420
  );
358
421
  }
359
422
  const orderedIds = postorder(g, sources).reverse();
360
423
  const sorted = orderedIds.map(getNode);
361
424
  if (sorted.length < nodes.length) {
362
- const unsorted = difference(nodes, sorted);
425
+ const unsorted = difference(nodes, sorted).sort(compareByFqnHierarchically);
363
426
  sorted.push(...unsorted);
364
427
  }
365
428
  invariant(sorted.length === nodes.length, "Not all nodes were processed by graphlib");
@@ -446,7 +509,9 @@ function includeWildcardRef(_expr, where = NoFilter) {
446
509
  ])
447
510
  ];
448
511
  for (const el of children) {
449
- this.addEdges(this.graph.anyEdgesBetween(el, neighbours));
512
+ this.addEdges(this.graph.anyEdgesBetween(el, neighbours)).forEach((edge) => {
513
+ this.addImplicit(edge.source, edge.target);
514
+ });
450
515
  }
451
516
  if (!hasChildren && _elRoot) {
452
517
  const edgesWithSiblings = this.graph.anyEdgesBetween(_elRoot, this.graph.siblings(root));
@@ -637,8 +702,9 @@ function includeIncomingExpr(expr, where) {
637
702
  if (edges.length === 0) {
638
703
  return;
639
704
  }
640
- this.addEdges(edges);
641
- this.addImplicit(...edges.map((e) => e.target));
705
+ this.addEdges(edges).forEach((edge) => {
706
+ this.addImplicit(edge.target);
707
+ });
642
708
  }
643
709
  function excludeIncomingExpr(expr, where) {
644
710
  let relations = filterRelations(edgesIncomingExpr.call(this, expr.incoming), where);
@@ -678,8 +744,9 @@ function includeOutgoingExpr(expr, where) {
678
744
  if (edges.length === 0) {
679
745
  return;
680
746
  }
681
- this.addEdges(edges);
682
- this.addImplicit(...edges.map((e) => e.source));
747
+ this.addEdges(edges).forEach((edge) => {
748
+ this.addImplicit(edge.source);
749
+ });
683
750
  }
684
751
  function excludeOutgoingExpr(expr, where) {
685
752
  const relations = filterRelations(edgesOutgoingExpr.call(this, expr.outgoing), where);
@@ -764,7 +831,9 @@ function includeRelationExpr(expr, where) {
764
831
  if (expr.isBidirectional === true) {
765
832
  edges.push(...this.graph.edgesBetween(targets, sources));
766
833
  }
767
- this.addEdges(filterEdges(edges, where));
834
+ this.addEdges(filterEdges(edges, where)).forEach((edge) => {
835
+ this.activeGroup.addImplicit(edge.source, edge.target);
836
+ });
768
837
  }
769
838
  function excludeRelationExpr(expr, where) {
770
839
  const isSource = elementExprToPredicate(expr.source);
@@ -787,6 +856,50 @@ function compareEdges(a, b) {
787
856
  { source: b.source.id, target: b.target.id }
788
857
  );
789
858
  }
859
+ class NodesGroup {
860
+ constructor(id, viewRule, parent = null) {
861
+ this.id = id;
862
+ this.viewRule = viewRule;
863
+ this.parent = parent;
864
+ }
865
+ static kind = ElementKind.Group;
866
+ static root() {
867
+ return new NodesGroup("@root", { title: null, groupRules: [] });
868
+ }
869
+ static is(node) {
870
+ return node.kind === NodesGroup.kind;
871
+ }
872
+ explicits = /* @__PURE__ */ new Set();
873
+ implicits = /* @__PURE__ */ new Set();
874
+ /**
875
+ * Add element explicitly
876
+ * Included even without relationships
877
+ */
878
+ addElement(...el) {
879
+ for (const r of el) {
880
+ this.explicits.add(r);
881
+ this.implicits.add(r);
882
+ }
883
+ }
884
+ /**
885
+ * Add element implicitly
886
+ * Included if only has relationships
887
+ */
888
+ addImplicit(...el) {
889
+ for (const r of el) {
890
+ this.implicits.add(r);
891
+ }
892
+ }
893
+ excludeElement(...excludes) {
894
+ for (const el of excludes) {
895
+ this.explicits.delete(el);
896
+ this.implicits.delete(el);
897
+ }
898
+ }
899
+ isEmpty() {
900
+ return this.explicits.size === 0 && this.implicits.size === 0;
901
+ }
902
+ }
790
903
  class ComputeCtx {
791
904
  constructor(view, graph) {
792
905
  this.view = view;
@@ -796,6 +909,16 @@ class ComputeCtx {
796
909
  explicits = /* @__PURE__ */ new Set();
797
910
  implicits = /* @__PURE__ */ new Set();
798
911
  ctxEdges = [];
912
+ /**
913
+ * Root group - not included in the groups
914
+ * But used to accumulate elements that are not in any group
915
+ */
916
+ __rootGroup = NodesGroup.root();
917
+ groups = [];
918
+ activeGroupStack = [];
919
+ get activeGroup() {
920
+ return this.activeGroupStack[0] ?? this.__rootGroup;
921
+ }
799
922
  static elementView(view, graph) {
800
923
  return new ComputeCtx(view, graph).compute();
801
924
  }
@@ -807,14 +930,30 @@ class ComputeCtx {
807
930
  rules,
808
931
  ...view
809
932
  } = this.view;
810
- const viewPredicates = rules.filter(isViewRulePredicate);
933
+ const viewPredicates = rules.filter(anyPass([isViewRulePredicate, isViewRuleGroup]));
811
934
  if (this.root && viewPredicates.length == 0) {
812
935
  this.addElement(this.graph.element(this.root));
813
936
  }
814
937
  this.processPredicates(viewPredicates);
815
938
  this.removeRedundantImplicitEdges();
939
+ if (this.groups.length > 0) {
940
+ this.cleanGroupElements();
941
+ }
816
942
  const elements = [...this.includedElements];
817
- const nodesMap = buildComputeNodes(elements);
943
+ const nodesMap = buildComputeNodes(elements, this.groups);
944
+ const ancestorsOf = (node) => {
945
+ const ancestors = [];
946
+ let parent = node.parent;
947
+ while (parent) {
948
+ const parentNode = nodesMap.get(parent);
949
+ if (!parentNode) {
950
+ break;
951
+ }
952
+ ancestors.push(parentNode);
953
+ parent = parentNode.parent;
954
+ }
955
+ return ancestors;
956
+ };
818
957
  const edgesMap = /* @__PURE__ */ new Map();
819
958
  const edges = this.computeEdges();
820
959
  for (const edge of edges) {
@@ -823,25 +962,37 @@ class ComputeCtx {
823
962
  const target = nodesMap.get(edge.target);
824
963
  invariant(source, `Source node ${edge.source} not found`);
825
964
  invariant(target, `Target node ${edge.target} not found`);
826
- while (edge.parent && !nodesMap.has(edge.parent)) {
827
- edge.parent = parentFqn(edge.parent);
828
- }
965
+ const sourceAncestors = ancestorsOf(source);
966
+ const targetAncestors = ancestorsOf(target);
967
+ const edgeParent = last(
968
+ commonHead(
969
+ reverse(sourceAncestors),
970
+ reverse(targetAncestors)
971
+ )
972
+ );
973
+ edge.parent = edgeParent?.id ?? null;
829
974
  source.outEdges.push(edge.id);
830
975
  target.inEdges.push(edge.id);
831
- for (const sourceAncestor of ancestorsFqn(edge.source)) {
832
- if (sourceAncestor === edge.parent) {
976
+ for (const sourceAncestor of sourceAncestors) {
977
+ if (sourceAncestor === edgeParent) {
833
978
  break;
834
979
  }
835
- nodesMap.get(sourceAncestor)?.outEdges.push(edge.id);
980
+ sourceAncestor.outEdges.push(edge.id);
836
981
  }
837
- for (const targetAncestor of ancestorsFqn(edge.target)) {
838
- if (targetAncestor === edge.parent) {
982
+ for (const targetAncestor of targetAncestors) {
983
+ if (targetAncestor === edgeParent) {
839
984
  break;
840
985
  }
841
- nodesMap.get(targetAncestor)?.inEdges.push(edge.id);
986
+ targetAncestor.inEdges.push(edge.id);
842
987
  }
843
988
  }
844
- const initialSort = elements.flatMap((e) => nodesMap.get(e.id) ?? []);
989
+ let initialSort = elements.map((e) => nonNullable(nodesMap.get(e.id), `Node ${e.id} not found in nodesMap`));
990
+ if (this.groups.length > 0) {
991
+ initialSort = concat(
992
+ this.groups.map((g) => nonNullable(nodesMap.get(g.id), `Node ${g.id} not found in nodesMap`)),
993
+ initialSort
994
+ );
995
+ }
845
996
  const nodes = applyCustomElementProperties(
846
997
  rules,
847
998
  applyViewRuleStyles(
@@ -987,6 +1138,7 @@ class ComputeCtx {
987
1138
  return this.ctxEdges;
988
1139
  }
989
1140
  addEdges(edges) {
1141
+ const added = [];
990
1142
  for (const e of edges) {
991
1143
  if (!hasAtLeast$1(e.relations, 1)) {
992
1144
  continue;
@@ -996,10 +1148,13 @@ class ComputeCtx {
996
1148
  );
997
1149
  if (existing) {
998
1150
  existing.relations = unique([...existing.relations, ...e.relations]);
1151
+ added.push(existing);
999
1152
  continue;
1000
1153
  }
1154
+ added.push(e);
1001
1155
  this.ctxEdges.push(e);
1002
1156
  }
1157
+ return added;
1003
1158
  }
1004
1159
  /**
1005
1160
  * Add element explicitly
@@ -1007,6 +1162,14 @@ class ComputeCtx {
1007
1162
  */
1008
1163
  addElement(...el) {
1009
1164
  for (const r of el) {
1165
+ if (!this.explicits.has(r)) {
1166
+ this.activeGroup.addElement(r);
1167
+ } else if (this.activeGroup !== this.__rootGroup) {
1168
+ this.groups.forEach((g) => {
1169
+ g.implicits.delete(r);
1170
+ });
1171
+ this.activeGroup.addImplicit(r);
1172
+ }
1010
1173
  this.explicits.add(r);
1011
1174
  this.implicits.add(r);
1012
1175
  }
@@ -1018,7 +1181,13 @@ class ComputeCtx {
1018
1181
  addImplicit(...el) {
1019
1182
  for (const r of el) {
1020
1183
  this.implicits.add(r);
1184
+ if (this.activeGroup !== this.__rootGroup) {
1185
+ this.groups.forEach((g) => {
1186
+ g.implicits.delete(r);
1187
+ });
1188
+ }
1021
1189
  }
1190
+ this.activeGroup.addImplicit(...el);
1022
1191
  }
1023
1192
  excludeElement(...excludes) {
1024
1193
  for (const el of excludes) {
@@ -1026,12 +1195,11 @@ class ComputeCtx {
1026
1195
  this.explicits.delete(el);
1027
1196
  this.implicits.delete(el);
1028
1197
  }
1198
+ this.__rootGroup.excludeElement(...excludes);
1199
+ this.groups.forEach((g) => {
1200
+ g.excludeElement(...excludes);
1201
+ });
1029
1202
  }
1030
- // protected excludeImplicit(...excludes: Element[]) {
1031
- // for (const el of excludes) {
1032
- // this.implicits.delete(el)
1033
- // }
1034
- // }
1035
1203
  excludeRelation(...relations) {
1036
1204
  if (relations.length === 0) {
1037
1205
  return;
@@ -1063,11 +1231,15 @@ class ComputeCtx {
1063
1231
  const remaining = this.includedElements;
1064
1232
  if (remaining.size === 0) {
1065
1233
  this.implicits.clear();
1234
+ this.__rootGroup.implicits.clear();
1235
+ this.groups.forEach((g) => g.implicits.clear());
1066
1236
  return;
1067
1237
  }
1068
1238
  for (const el of excludedImplicits) {
1069
1239
  if (!remaining.has(el)) {
1070
1240
  this.implicits.delete(el);
1241
+ this.__rootGroup.implicits.delete(el);
1242
+ this.groups.forEach((g) => g.implicits.delete(el));
1071
1243
  }
1072
1244
  }
1073
1245
  }
@@ -1075,6 +1247,9 @@ class ComputeCtx {
1075
1247
  this.explicits.clear();
1076
1248
  this.implicits.clear();
1077
1249
  this.ctxEdges = [];
1250
+ this.__rootGroup = NodesGroup.root();
1251
+ this.groups = [];
1252
+ this.activeGroupStack = [];
1078
1253
  }
1079
1254
  // Filter out edges if there are edges between descendants
1080
1255
  // i.e. remove implicit edges, derived from childs
@@ -1117,8 +1292,41 @@ class ComputeCtx {
1117
1292
  return acc;
1118
1293
  }, []);
1119
1294
  }
1295
+ cleanGroupElements() {
1296
+ const unprocessed = new Set(difference(
1297
+ [...this.includedElements],
1298
+ [...this.__rootGroup.explicits]
1299
+ ));
1300
+ for (const group of this.groups) {
1301
+ const explicits = [...group.explicits];
1302
+ group.explicits.clear();
1303
+ for (const el of explicits) {
1304
+ if (unprocessed.delete(el)) {
1305
+ group.explicits.add(el);
1306
+ }
1307
+ }
1308
+ }
1309
+ for (const group of this.groups) {
1310
+ for (const el of group.implicits) {
1311
+ if (unprocessed.delete(el)) {
1312
+ group.explicits.add(el);
1313
+ }
1314
+ }
1315
+ group.implicits.clear();
1316
+ }
1317
+ }
1120
1318
  processPredicates(viewRules) {
1121
1319
  for (const rule of viewRules) {
1320
+ if (isViewRuleGroup(rule)) {
1321
+ const parent = first(this.activeGroupStack);
1322
+ const groupId = `@gr${this.groups.length + 1}`;
1323
+ const group = new NodesGroup(groupId, rule, parent?.id ?? null);
1324
+ this.groups.push(group);
1325
+ this.activeGroupStack.unshift(group);
1326
+ this.processPredicates(rule.groupRules);
1327
+ this.activeGroupStack.shift();
1328
+ continue;
1329
+ }
1122
1330
  const isInclude = "include" in rule;
1123
1331
  const exprs = rule.include ?? rule.exclude;
1124
1332
  for (const expr of exprs) {