@agilewallaby/c4-model 2.6.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -162,12 +162,16 @@ var Element = class {
162
162
  get relationships() {
163
163
  return this._relationships;
164
164
  }
165
+ with(callback) {
166
+ const children = callback(this);
167
+ return Object.assign(this, children);
168
+ }
165
169
  getRelationshipsInHierarchy() {
166
170
  return this._relationships.concat(this.getChildElements().flatMap((element) => element.getRelationshipsInHierarchy()));
167
171
  }
168
- getChildElementNames(path2) {
172
+ getChildElementNames(path4) {
169
173
  const result = Array.from(this.getChildElements()).flatMap((reference) => {
170
- const currentPath = `${path2 ? path2 : "" + this.name}.${reference.name}`;
174
+ const currentPath = `${path4 ? path4 : "" + this.name}.${reference.name}`;
171
175
  return [currentPath, ...reference.getChildElementNames(currentPath)];
172
176
  });
173
177
  return result;
@@ -200,6 +204,13 @@ var Group = class {
200
204
  constructor(name) {
201
205
  this.name = name;
202
206
  }
207
+ get canonicalName() {
208
+ return camelCase(this.name);
209
+ }
210
+ with(callback) {
211
+ const children = callback(this);
212
+ return Object.assign(this, children);
213
+ }
203
214
  // TODO: Implement this in some useful way?
204
215
  // public addToGroup(groupCollection: string, groupMember: T) {}
205
216
  };
@@ -216,21 +227,46 @@ var Component = class extends TechnicalElement {
216
227
  };
217
228
 
218
229
  // libs/c4-model/src/container.ts
219
- var ContainerGroup = class extends Group {
220
- constructor(name, container) {
230
+ var ContainerGroup = class _ContainerGroup extends Group {
231
+ constructor(name, container, pathSegments = []) {
221
232
  super(name);
222
233
  this.name = name;
223
234
  this.container = container;
235
+ this.pathSegments = pathSegments;
224
236
  }
225
237
  _components = /* @__PURE__ */ new Map();
238
+ _groups = /* @__PURE__ */ new Map();
239
+ get canonicalName() {
240
+ return camelCase([...this.pathSegments, this.name].join(" "));
241
+ }
242
+ get dslName() {
243
+ return [...this.pathSegments, this.name].join("/");
244
+ }
226
245
  component(name, archetypeOrDef, override) {
227
246
  const component = this.container.component(name, archetypeOrDef, override);
228
247
  this._components.set(name, component);
229
248
  return component;
230
249
  }
250
+ group(groupName) {
251
+ let group = this._groups.get(groupName);
252
+ if (!group) {
253
+ group = new _ContainerGroup(groupName, this.container, [...this.pathSegments, this.name]);
254
+ this._groups.set(groupName, group);
255
+ }
256
+ return group;
257
+ }
258
+ getGroups() {
259
+ return Array.from(this._groups.values());
260
+ }
231
261
  getComponents() {
232
262
  return Array.from(this._components.values());
233
263
  }
264
+ getAllComponents() {
265
+ return [
266
+ ...this._components.values(),
267
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllComponents())
268
+ ];
269
+ }
234
270
  };
235
271
  var Container = class extends TechnicalElement {
236
272
  constructor(name, definition, archetype, overrideDefinition) {
@@ -255,7 +291,7 @@ var Container = class extends TechnicalElement {
255
291
  this._components.set(name, component);
256
292
  return component;
257
293
  }
258
- addGroup(groupName) {
294
+ group(groupName) {
259
295
  let group = this._groups.get(groupName);
260
296
  if (!group) {
261
297
  group = new ContainerGroup(groupName, this);
@@ -267,7 +303,7 @@ var Container = class extends TechnicalElement {
267
303
  return Array.from(this._groups.values());
268
304
  }
269
305
  getComponentsNotInGroups() {
270
- const componentsInGroups = this.getGroups().flatMap((group) => group.getComponents());
306
+ const componentsInGroups = this.getGroups().flatMap((group) => group.getAllComponents());
271
307
  return Array.from(this._components.values()).filter((component) => !componentsInGroups.includes(component));
272
308
  }
273
309
  getChildElements() {
@@ -276,21 +312,46 @@ var Container = class extends TechnicalElement {
276
312
  };
277
313
 
278
314
  // libs/c4-model/src/softwareSystem.ts
279
- var SoftwareSystemGroup = class extends Group {
280
- constructor(name, softwareSystem) {
315
+ var SoftwareSystemGroup = class _SoftwareSystemGroup extends Group {
316
+ constructor(name, softwareSystem, pathSegments = []) {
281
317
  super(name);
282
318
  this.name = name;
283
319
  this.softwareSystem = softwareSystem;
320
+ this.pathSegments = pathSegments;
284
321
  }
285
322
  _containers = /* @__PURE__ */ new Map();
323
+ _groups = /* @__PURE__ */ new Map();
324
+ get canonicalName() {
325
+ return camelCase([...this.pathSegments, this.name].join(" "));
326
+ }
327
+ get dslName() {
328
+ return [...this.pathSegments, this.name].join("/");
329
+ }
286
330
  container(name, archetypeOrDef, override) {
287
331
  const container = this.softwareSystem.container(name, archetypeOrDef, override);
288
332
  this._containers.set(name, container);
289
333
  return container;
290
334
  }
335
+ group(groupName) {
336
+ let group = this._groups.get(groupName);
337
+ if (!group) {
338
+ group = new _SoftwareSystemGroup(groupName, this.softwareSystem, [...this.pathSegments, this.name]);
339
+ this._groups.set(groupName, group);
340
+ }
341
+ return group;
342
+ }
343
+ getGroups() {
344
+ return Array.from(this._groups.values());
345
+ }
291
346
  getContainers() {
292
347
  return Array.from(this._containers.values());
293
348
  }
349
+ getAllContainers() {
350
+ return [
351
+ ...this._containers.values(),
352
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllContainers())
353
+ ];
354
+ }
294
355
  };
295
356
  var SoftwareSystem = class extends Element {
296
357
  constructor(name, definition, archetype, overrideDefinition) {
@@ -315,7 +376,7 @@ var SoftwareSystem = class extends Element {
315
376
  this._containers.set(name, container);
316
377
  return container;
317
378
  }
318
- addGroup(groupName) {
379
+ group(groupName) {
319
380
  let group = this._groups.get(groupName);
320
381
  if (!group) {
321
382
  group = new SoftwareSystemGroup(groupName, this);
@@ -330,7 +391,7 @@ var SoftwareSystem = class extends Element {
330
391
  return Array.from(this._containers.values());
331
392
  }
332
393
  getContainersNotInGroups() {
333
- const containersInGroups = Array.from(this._groups.values()).flatMap((group) => group.getContainers());
394
+ const containersInGroups = Array.from(this._groups.values()).flatMap((group) => group.getAllContainers());
334
395
  return Array.from(this._containers.values()).filter((container) => !containersInGroups.includes(container));
335
396
  }
336
397
  };
@@ -347,14 +408,22 @@ var Person = class extends Element {
347
408
  };
348
409
 
349
410
  // libs/c4-model/src/model.ts
350
- var ModelGroup = class extends Group {
351
- constructor(name, model) {
411
+ var ModelGroup = class _ModelGroup extends Group {
412
+ constructor(name, model, pathSegments = []) {
352
413
  super(name);
353
414
  this.name = name;
354
415
  this.model = model;
416
+ this.pathSegments = pathSegments;
355
417
  }
356
418
  softwareSystems = /* @__PURE__ */ new Map();
357
419
  people = /* @__PURE__ */ new Map();
420
+ _groups = /* @__PURE__ */ new Map();
421
+ get canonicalName() {
422
+ return camelCase([...this.pathSegments, this.name].join(" "));
423
+ }
424
+ get dslName() {
425
+ return [...this.pathSegments, this.name].join("/");
426
+ }
358
427
  softwareSystem(name, archetypeOrDef, override) {
359
428
  const softwareSystem = this.model.softwareSystem(name, archetypeOrDef, override);
360
429
  this.softwareSystems.set(name, softwareSystem);
@@ -365,12 +434,35 @@ var ModelGroup = class extends Group {
365
434
  this.people.set(name, person);
366
435
  return person;
367
436
  }
437
+ group(groupName) {
438
+ let group = this._groups.get(groupName);
439
+ if (!group) {
440
+ group = new _ModelGroup(groupName, this.model, [...this.pathSegments, this.name]);
441
+ this._groups.set(groupName, group);
442
+ }
443
+ return group;
444
+ }
445
+ getGroups() {
446
+ return Array.from(this._groups.values());
447
+ }
368
448
  getSoftwareSystems() {
369
449
  return Array.from(this.softwareSystems.values());
370
450
  }
371
451
  getPeople() {
372
452
  return Array.from(this.people.values());
373
453
  }
454
+ getAllSoftwareSystems() {
455
+ return [
456
+ ...this.softwareSystems.values(),
457
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllSoftwareSystems())
458
+ ];
459
+ }
460
+ getAllPeople() {
461
+ return [
462
+ ...this.people.values(),
463
+ ...Array.from(this._groups.values()).flatMap((g) => g.getAllPeople())
464
+ ];
465
+ }
374
466
  };
375
467
  var Model = class {
376
468
  constructor(name) {
@@ -395,8 +487,7 @@ var Model = class {
395
487
  this.softwareSystems.set(name, system);
396
488
  return system;
397
489
  }
398
- // TODO:Should be a Group<SoftwareSystem | Person> if that is added back in
399
- addGroup(groupName) {
490
+ group(groupName) {
400
491
  let group = this.groups.get(groupName);
401
492
  if (!group) {
402
493
  group = new ModelGroup(groupName, this);
@@ -420,6 +511,7 @@ var Model = class {
420
511
  this.people.set(name, person);
421
512
  return person;
422
513
  }
514
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
423
515
  validate() {
424
516
  }
425
517
  getPeople() {
@@ -429,11 +521,11 @@ var Model = class {
429
521
  return Array.from(this.softwareSystems.values());
430
522
  }
431
523
  getPeopleNotInGroups() {
432
- const peopleInGroups = Array.from(this.groups.values()).flatMap((group) => group.getPeople());
524
+ const peopleInGroups = Array.from(this.groups.values()).flatMap((group) => group.getAllPeople());
433
525
  return Array.from(this.people.values()).filter((person) => !peopleInGroups.includes(person));
434
526
  }
435
527
  getSoftwareSystemsNotInGroups() {
436
- const systemsInGroups = Array.from(this.groups.values()).flatMap((group) => group.getSoftwareSystems());
528
+ const systemsInGroups = Array.from(this.groups.values()).flatMap((group) => group.getAllSoftwareSystems());
437
529
  return Array.from(this.softwareSystems.values()).filter((system) => !systemsInGroups.includes(system));
438
530
  }
439
531
  getGroups() {
@@ -453,6 +545,9 @@ var View = class {
453
545
  description;
454
546
  title;
455
547
  _scopes = [];
548
+ _autoLayout;
549
+ _isDefault = false;
550
+ _properties = /* @__PURE__ */ new Map();
456
551
  includeAll() {
457
552
  this._scopes.push("include *");
458
553
  }
@@ -471,15 +566,37 @@ var View = class {
471
566
  excludeExpression(expression) {
472
567
  this._scopes.push(`exclude ${expression}`);
473
568
  }
569
+ autoLayout(direction, rankSeparation, nodeSeparation) {
570
+ this._autoLayout = { direction, rankSeparation, nodeSeparation };
571
+ }
572
+ setDefault() {
573
+ this._isDefault = true;
574
+ }
575
+ addProperty(name, value) {
576
+ this._properties.set(name, value);
577
+ }
474
578
  get scopes() {
475
579
  return this._scopes;
476
580
  }
581
+ get autoLayoutConfig() {
582
+ return this._autoLayout;
583
+ }
584
+ get isDefault() {
585
+ return this._isDefault;
586
+ }
587
+ get properties() {
588
+ return this._properties;
589
+ }
477
590
  };
478
591
  var Views = class {
479
592
  _systemLandscapeViews = /* @__PURE__ */ new Map();
480
593
  _systemContextViews = /* @__PURE__ */ new Map();
481
594
  _containerViews = /* @__PURE__ */ new Map();
482
595
  _componentViews = /* @__PURE__ */ new Map();
596
+ _elementStyles = [];
597
+ _relationshipStyles = [];
598
+ _themes = [];
599
+ _properties = /* @__PURE__ */ new Map();
483
600
  addSystemLandscapeView(key, definition) {
484
601
  const view = new View(key, { subject: void 0, description: definition.description, title: definition.title });
485
602
  this._systemLandscapeViews.set(key, view);
@@ -500,6 +617,18 @@ var Views = class {
500
617
  this._componentViews.set(key, view);
501
618
  return view;
502
619
  }
620
+ addElementStyle(tag, definition) {
621
+ this._elementStyles.push({ tag, definition });
622
+ }
623
+ addRelationshipStyle(tag, definition) {
624
+ this._relationshipStyles.push({ tag, definition });
625
+ }
626
+ addTheme(url) {
627
+ this._themes.push(url);
628
+ }
629
+ addProperty(name, value) {
630
+ this._properties.set(name, value);
631
+ }
503
632
  get systemLandscapeViews() {
504
633
  return Array.from(this._systemLandscapeViews.values());
505
634
  }
@@ -512,6 +641,18 @@ var Views = class {
512
641
  get componentViews() {
513
642
  return Array.from(this._componentViews.values());
514
643
  }
644
+ get elementStyles() {
645
+ return this._elementStyles;
646
+ }
647
+ get relationshipStyles() {
648
+ return this._relationshipStyles;
649
+ }
650
+ get themes() {
651
+ return this._themes;
652
+ }
653
+ get properties() {
654
+ return this._properties;
655
+ }
515
656
  };
516
657
 
517
658
  // libs/c4-model/src/structurizrDslWriter.ts
@@ -646,11 +787,17 @@ var StructurizrDSLWriter = class {
646
787
  }
647
788
  writeContainerGroup(group, level) {
648
789
  let containerGroupDsl = "";
649
- containerGroupDsl += this.writeLine(`${group.name} = group "${group.name}" {`, level);
650
- group.getComponents().forEach((component) => {
651
- containerGroupDsl += this.writeComponent(component, level + 1);
790
+ const hasDirect = group.getComponents().length > 0;
791
+ if (hasDirect) {
792
+ containerGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.dslName}" {`, level);
793
+ group.getComponents().forEach((component) => {
794
+ containerGroupDsl += this.writeComponent(component, level + 1);
795
+ });
796
+ containerGroupDsl += this.writeLine(`}`, level);
797
+ }
798
+ group.getGroups().forEach((nested) => {
799
+ containerGroupDsl += this.writeContainerGroup(nested, level);
652
800
  });
653
- containerGroupDsl += this.writeLine(`}`, level);
654
801
  return containerGroupDsl;
655
802
  }
656
803
  writeContainer(container, level) {
@@ -670,11 +817,17 @@ var StructurizrDSLWriter = class {
670
817
  }
671
818
  writeSoftwareSystemGroup(group, level) {
672
819
  let softwareSystemGroupDsl = "";
673
- softwareSystemGroupDsl += this.writeLine(`${group.name} = group "${group.name}" {`, level);
674
- group.getContainers().forEach((container) => {
675
- softwareSystemGroupDsl += this.writeContainer(container, level + 1);
820
+ const hasDirect = group.getContainers().length > 0;
821
+ if (hasDirect) {
822
+ softwareSystemGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.dslName}" {`, level);
823
+ group.getContainers().forEach((container) => {
824
+ softwareSystemGroupDsl += this.writeContainer(container, level + 1);
825
+ });
826
+ softwareSystemGroupDsl += this.writeLine(`}`, level);
827
+ }
828
+ group.getGroups().forEach((nested) => {
829
+ softwareSystemGroupDsl += this.writeSoftwareSystemGroup(nested, level);
676
830
  });
677
- softwareSystemGroupDsl += this.writeLine(`}`, level);
678
831
  return softwareSystemGroupDsl;
679
832
  }
680
833
  writeSoftwareSystem(softwareSystem, level) {
@@ -726,21 +879,51 @@ var StructurizrDSLWriter = class {
726
879
  });
727
880
  return relationshipsDsl;
728
881
  }
882
+ hasNestedModelGroups(groups) {
883
+ return groups.some((g) => g.getGroups().length > 0 || this.hasNestedModelGroups(g.getGroups()));
884
+ }
885
+ hasNestedSoftwareSystemGroups(groups) {
886
+ return groups.some((g) => g.getGroups().length > 0 || this.hasNestedSoftwareSystemGroups(g.getGroups()));
887
+ }
888
+ hasNestedContainerGroups(groups) {
889
+ return groups.some((g) => g.getGroups().length > 0 || this.hasNestedContainerGroups(g.getGroups()));
890
+ }
891
+ hasNestedGroups() {
892
+ if (this.hasNestedModelGroups(this.model.getGroups())) return true;
893
+ for (const ss of this.model.getSoftwareSystems()) {
894
+ if (this.hasNestedSoftwareSystemGroups(ss.getGroups())) return true;
895
+ for (const c of ss.getGroups().flatMap((g) => g.getAllContainers())) {
896
+ if (this.hasNestedContainerGroups(c.getGroups())) return true;
897
+ }
898
+ }
899
+ return false;
900
+ }
729
901
  writeModelGroup(group, level) {
730
902
  let modelGroupDsl = "";
731
- modelGroupDsl += this.writeLine(`${group.name} = group "${group.name}" {`, level);
732
- group.getPeople().forEach((person) => {
733
- modelGroupDsl += this.writeElement("person", person, level + 1);
734
- });
735
- group.getSoftwareSystems().forEach((softwareSystem) => {
736
- modelGroupDsl += this.writeSoftwareSystem(softwareSystem, level + 1);
903
+ const hasDirect = group.getPeople().length > 0 || group.getSoftwareSystems().length > 0;
904
+ if (hasDirect) {
905
+ modelGroupDsl += this.writeLine(`${group.canonicalName} = group "${group.dslName}" {`, level);
906
+ group.getPeople().forEach((person) => {
907
+ modelGroupDsl += this.writeElement("person", person, level + 1);
908
+ });
909
+ group.getSoftwareSystems().forEach((softwareSystem) => {
910
+ modelGroupDsl += this.writeSoftwareSystem(softwareSystem, level + 1);
911
+ });
912
+ modelGroupDsl += this.writeLine(`}`, level);
913
+ }
914
+ group.getGroups().forEach((nested) => {
915
+ modelGroupDsl += this.writeModelGroup(nested, level);
737
916
  });
738
- modelGroupDsl += this.writeLine(`}`, level);
739
917
  return modelGroupDsl;
740
918
  }
741
919
  writeModel(model, level) {
742
920
  let modelDsl = "";
743
921
  modelDsl += this.writeLine(`model {`, level);
922
+ if (this.hasNestedGroups()) {
923
+ modelDsl += this.writeLine(`properties {`, level + 1);
924
+ modelDsl += this.writeLine(`"structurizr.groupSeparator" "/"`, level + 2);
925
+ modelDsl += this.writeLine(`}`, level + 1);
926
+ }
744
927
  modelDsl += this.writeArchetypes(level + 1);
745
928
  modelDsl += this.writeLine("// Elements", level + 1);
746
929
  model.getPeopleNotInGroups().forEach((person) => {
@@ -763,12 +946,66 @@ var StructurizrDSLWriter = class {
763
946
  if (view.title) {
764
947
  viewDsl += this.writeLine(`title "${view.title}"`, level + 1);
765
948
  }
949
+ if (view.isDefault) {
950
+ viewDsl += this.writeLine("default", level + 1);
951
+ }
766
952
  view.scopes.forEach((scope) => {
767
953
  viewDsl += this.writeLine(`${scope}`, level + 1);
768
954
  });
955
+ if (view.autoLayoutConfig) {
956
+ const { direction, rankSeparation, nodeSeparation } = view.autoLayoutConfig;
957
+ let line = "autoLayout";
958
+ if (direction) line += ` ${direction}`;
959
+ if (rankSeparation !== void 0) line += ` ${rankSeparation}`;
960
+ if (nodeSeparation !== void 0) line += ` ${nodeSeparation}`;
961
+ viewDsl += this.writeLine(line, level + 1);
962
+ }
963
+ if (view.properties.size > 0) {
964
+ viewDsl += this.writeLine("properties {", level + 1);
965
+ for (const [name, value] of view.properties) {
966
+ viewDsl += this.writeLine(`"${name}" "${value}"`, level + 2);
967
+ }
968
+ viewDsl += this.writeLine("}", level + 1);
969
+ }
769
970
  viewDsl += this.writeLine(`}`, level);
770
971
  return viewDsl;
771
972
  }
973
+ writeStyles(views, level) {
974
+ const { elementStyles, relationshipStyles } = views;
975
+ if (elementStyles.length === 0 && relationshipStyles.length === 0) return "";
976
+ let dsl = this.writeLine("styles {", level);
977
+ for (const { tag, definition: d } of elementStyles) {
978
+ dsl += this.writeLine(`element "${tag}" {`, level + 1);
979
+ if (d.shape) dsl += this.writeLine(`shape ${d.shape}`, level + 2);
980
+ if (d.icon) dsl += this.writeLine(`icon "${d.icon}"`, level + 2);
981
+ if (d.width) dsl += this.writeLine(`width ${d.width}`, level + 2);
982
+ if (d.height) dsl += this.writeLine(`height ${d.height}`, level + 2);
983
+ if (d.background) dsl += this.writeLine(`background "${d.background}"`, level + 2);
984
+ if (d.color) dsl += this.writeLine(`color "${d.color}"`, level + 2);
985
+ if (d.stroke) dsl += this.writeLine(`stroke "${d.stroke}"`, level + 2);
986
+ if (d.strokeWidth) dsl += this.writeLine(`strokeWidth ${d.strokeWidth}`, level + 2);
987
+ if (d.fontSize) dsl += this.writeLine(`fontSize ${d.fontSize}`, level + 2);
988
+ if (d.border) dsl += this.writeLine(`border ${d.border}`, level + 2);
989
+ if (d.opacity !== void 0) dsl += this.writeLine(`opacity ${d.opacity}`, level + 2);
990
+ if (d.metadata !== void 0) dsl += this.writeLine(`metadata ${d.metadata}`, level + 2);
991
+ if (d.description !== void 0) dsl += this.writeLine(`description ${d.description}`, level + 2);
992
+ dsl += this.writeLine("}", level + 1);
993
+ }
994
+ for (const { tag, definition: d } of relationshipStyles) {
995
+ dsl += this.writeLine(`relationship "${tag}" {`, level + 1);
996
+ if (d.thickness) dsl += this.writeLine(`thickness ${d.thickness}`, level + 2);
997
+ if (d.color) dsl += this.writeLine(`color "${d.color}"`, level + 2);
998
+ if (d.style) dsl += this.writeLine(`style ${d.style}`, level + 2);
999
+ if (d.routing) dsl += this.writeLine(`routing ${d.routing}`, level + 2);
1000
+ if (d.fontSize) dsl += this.writeLine(`fontSize ${d.fontSize}`, level + 2);
1001
+ if (d.width) dsl += this.writeLine(`width ${d.width}`, level + 2);
1002
+ if (d.position !== void 0) dsl += this.writeLine(`position ${d.position}`, level + 2);
1003
+ if (d.opacity !== void 0) dsl += this.writeLine(`opacity ${d.opacity}`, level + 2);
1004
+ dsl += this.writeLine("}", level + 1);
1005
+ }
1006
+ dsl += this.writeLine("}", level);
1007
+ return dsl;
1008
+ }
772
1009
  writeViews(views, level) {
773
1010
  let viewDsl = "";
774
1011
  viewDsl += this.writeLine(`views {`, level);
@@ -788,6 +1025,19 @@ var StructurizrDSLWriter = class {
788
1025
  views.componentViews.forEach((view) => {
789
1026
  viewDsl += this.writeView(view, "component", level + 1);
790
1027
  });
1028
+ viewDsl += this.writeStyles(views, level + 1);
1029
+ if (views.themes.length === 1) {
1030
+ viewDsl += this.writeLine(`theme ${views.themes[0]}`, level + 1);
1031
+ } else if (views.themes.length > 1) {
1032
+ viewDsl += this.writeLine(`themes ${views.themes.join(" ")}`, level + 1);
1033
+ }
1034
+ if (views.properties.size > 0) {
1035
+ viewDsl += this.writeLine("properties {", level + 1);
1036
+ for (const [name, value] of views.properties) {
1037
+ viewDsl += this.writeLine(`"${name}" "${value}"`, level + 2);
1038
+ }
1039
+ viewDsl += this.writeLine("}", level + 1);
1040
+ }
791
1041
  viewDsl += this.writeLine(`}`, level);
792
1042
  return viewDsl;
793
1043
  }
@@ -812,8 +1062,8 @@ import { glob } from "glob";
812
1062
  import { join, dirname } from "path";
813
1063
  import { fileURLToPath } from "url";
814
1064
  var _dirname = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
815
- async function buildModelWithCatalog(options = {}) {
816
- const { modelName = "model", modules: explicitModules, archetypes = {} } = options;
1065
+ async function buildModel(options = {}) {
1066
+ const { modelName = "model", modules: explicitModules, archetypes = {}, addViews } = options;
817
1067
  const model = new Model(modelName);
818
1068
  let c4Modules;
819
1069
  if (explicitModules) {
@@ -837,14 +1087,16 @@ async function buildModelWithCatalog(options = {}) {
837
1087
  rootCatalog[instance.key] = local;
838
1088
  registrations.push({ instance, key: instance.key, local });
839
1089
  }
1090
+ const dependenciesFor = (key) => Object.fromEntries(Object.entries(rootCatalog).filter(([k]) => k !== key));
840
1091
  for (const { instance, key, local } of registrations) {
841
- const dependencies = Object.fromEntries(Object.entries(rootCatalog).filter(([k]) => k !== key));
842
- instance.buildRelationships(local, dependencies, archetypes);
1092
+ instance.addRelationships(local, dependenciesFor(key), archetypes);
843
1093
  }
844
- return { model, catalog: rootCatalog };
845
- }
846
- async function buildModel(options = {}) {
847
- return (await buildModelWithCatalog(options)).model;
1094
+ const views = new Views();
1095
+ addViews?.(views, rootCatalog);
1096
+ for (const { instance, key, local } of registrations) {
1097
+ instance.addViews?.(views, local, dependenciesFor(key));
1098
+ }
1099
+ return { model, catalog: rootCatalog, views };
848
1100
  }
849
1101
 
850
1102
  // libs/c4-model/src/generateDiagrams.ts
@@ -853,9 +1105,8 @@ import * as os from "os";
853
1105
  import * as path from "path";
854
1106
  import { GenericContainer, Wait } from "testcontainers";
855
1107
  async function generateDiagrams(options) {
856
- const { views: viewsFactory, outputDir, ...buildOptions } = options;
857
- const { model, catalog } = await buildModelWithCatalog(buildOptions);
858
- const views = viewsFactory(catalog);
1108
+ const { outputDir, ...buildOptions } = options;
1109
+ const { model, views } = await buildModel(buildOptions);
859
1110
  const dsl = new StructurizrDSLWriter(model, views).write();
860
1111
  const tmpDir = await fs.promises.mkdtemp(path.join(fs.realpathSync(os.tmpdir()), "c4-diagrams-"));
861
1112
  await fs.promises.writeFile(path.join(tmpDir, "workspace.dsl"), dsl, "utf8");
@@ -876,6 +1127,60 @@ async function generateDiagrams(options) {
876
1127
  }
877
1128
  return generatedFiles;
878
1129
  }
1130
+
1131
+ // libs/c4-model/src/validateModel.ts
1132
+ import * as fs2 from "fs";
1133
+ import * as os2 from "os";
1134
+ import * as path2 from "path";
1135
+ import { GenericContainer as GenericContainer2, Wait as Wait2 } from "testcontainers";
1136
+ async function validateModel(model, views) {
1137
+ const dsl = new StructurizrDSLWriter(model, views).write();
1138
+ const tmpDir = await fs2.promises.mkdtemp(path2.join(fs2.realpathSync(os2.tmpdir()), "c4-validate-"));
1139
+ try {
1140
+ await fs2.promises.writeFile(path2.join(tmpDir, "workspace.dsl"), dsl, "utf8");
1141
+ const logs = [];
1142
+ try {
1143
+ await new GenericContainer2("structurizr/structurizr").withBindMounts([{ source: tmpDir, target: "/workspace", mode: "rw" }]).withCommand(["validate", "-workspace", "/workspace/workspace.dsl"]).withWaitStrategy(Wait2.forOneShotStartup()).withLogConsumer((stream) => stream.on("data", (chunk) => logs.push(chunk.toString()))).start();
1144
+ } catch {
1145
+ throw new Error(`Structurizr validation failed:
1146
+ ${logs.join("")}`);
1147
+ }
1148
+ } finally {
1149
+ await fs2.promises.rm(tmpDir, { recursive: true, force: true });
1150
+ }
1151
+ }
1152
+
1153
+ // libs/c4-model/src/exportWorkspaceJson.ts
1154
+ import * as fs3 from "fs";
1155
+ import * as os3 from "os";
1156
+ import * as path3 from "path";
1157
+ import { GenericContainer as GenericContainer3, Wait as Wait3 } from "testcontainers";
1158
+ async function exportWorkspaceJsonFromDsl(dsl) {
1159
+ const tmpDir = await fs3.promises.mkdtemp(path3.join(fs3.realpathSync(os3.tmpdir()), "c4-export-"));
1160
+ try {
1161
+ await fs3.promises.writeFile(path3.join(tmpDir, "workspace.dsl"), dsl, "utf8");
1162
+ const logs = [];
1163
+ try {
1164
+ await new GenericContainer3("structurizr/structurizr").withBindMounts([{ source: tmpDir, target: "/workspace", mode: "rw" }]).withCommand(["export", "-w", "/workspace/workspace.dsl", "-f", "json", "-o", "/workspace"]).withWaitStrategy(Wait3.forOneShotStartup()).withLogConsumer((stream) => stream.on("data", (chunk) => logs.push(chunk.toString()))).start();
1165
+ } catch {
1166
+ throw new Error(`Structurizr JSON export failed:
1167
+ ${logs.join("")}`);
1168
+ }
1169
+ const files = await fs3.promises.readdir(tmpDir);
1170
+ const jsonFile = files.find((f) => f.endsWith(".json"));
1171
+ if (!jsonFile) {
1172
+ throw new Error(`Structurizr JSON export produced no .json file. Files: ${files.join(", ")}`);
1173
+ }
1174
+ const jsonContent = await fs3.promises.readFile(path3.join(tmpDir, jsonFile), "utf8");
1175
+ return JSON.parse(jsonContent);
1176
+ } finally {
1177
+ await fs3.promises.rm(tmpDir, { recursive: true, force: true });
1178
+ }
1179
+ }
1180
+ async function exportWorkspaceJson(model, views) {
1181
+ const dsl = new StructurizrDSLWriter(model, views).write();
1182
+ return exportWorkspaceJsonFromDsl(dsl);
1183
+ }
879
1184
  export {
880
1185
  Component,
881
1186
  Container,
@@ -891,7 +1196,9 @@ export {
891
1196
  View,
892
1197
  Views,
893
1198
  buildModel,
894
- buildModelWithCatalog,
1199
+ exportWorkspaceJson,
1200
+ exportWorkspaceJsonFromDsl,
895
1201
  generateDiagrams,
896
- mergeArchetypeWithOverride
1202
+ mergeArchetypeWithOverride,
1203
+ validateModel
897
1204
  };