@agilewallaby/c4-model 2.1.0 → 2.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agilewallaby/c4-model",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -11,8 +11,7 @@
11
11
  "dependencies": {
12
12
  "change-case": "^5.4.4",
13
13
  "glob": "^10.3.10",
14
- "testcontainers": "^10.28.0",
15
- "tslib": "^2.3.0"
14
+ "testcontainers": "^10.28.0"
16
15
  },
17
16
  "type": "module",
18
17
  "main": "./src/index.js",
@@ -0,0 +1,30 @@
1
+ import { TechnologyDefinition } from './core';
2
+ export type ElementKind = 'person' | 'softwareSystem' | 'container' | 'component';
3
+ export declare class ElementArchetype {
4
+ readonly name: string;
5
+ readonly elementKind: ElementKind;
6
+ readonly parent?: ElementArchetype | undefined;
7
+ readonly ownDescription?: string;
8
+ readonly ownTechnology?: string;
9
+ readonly ownTags: ReadonlyArray<string>;
10
+ readonly description?: string;
11
+ readonly technology?: string;
12
+ readonly tags: ReadonlyArray<string>;
13
+ constructor(name: string, elementKind: ElementKind, definition?: TechnologyDefinition, parent?: ElementArchetype | undefined);
14
+ }
15
+ export declare class RelationshipArchetype {
16
+ readonly name: string;
17
+ readonly parent?: RelationshipArchetype | undefined;
18
+ readonly ownDescription?: string;
19
+ readonly ownTechnology?: string;
20
+ readonly ownTags: ReadonlyArray<string>;
21
+ readonly description?: string;
22
+ readonly technology?: string;
23
+ readonly tags: ReadonlyArray<string>;
24
+ constructor(name: string, definition?: TechnologyDefinition, parent?: RelationshipArchetype | undefined);
25
+ }
26
+ export declare function mergeArchetypeWithOverride(archetype: {
27
+ description?: string;
28
+ technology?: string;
29
+ tags: ReadonlyArray<string>;
30
+ }, override?: TechnologyDefinition): TechnologyDefinition;
@@ -0,0 +1,20 @@
1
+ import { Model } from './model';
2
+ type Catalog = Record<string, unknown>;
3
+ type RootCatalog = Record<string, Catalog>;
4
+ export interface AnyC4Module {
5
+ readonly key: string;
6
+ registerDefinitions(model: Model): Catalog;
7
+ buildRelationships(local: Catalog, dependencies: RootCatalog): void;
8
+ }
9
+ export interface BuildModelOptions {
10
+ modelName?: string;
11
+ modules?: ReadonlyArray<AnyC4Module>;
12
+ globPath?: string;
13
+ searchRoot?: string;
14
+ }
15
+ export declare function buildModelWithCatalog<TRoot>(options?: BuildModelOptions): Promise<{
16
+ model: Model;
17
+ catalog: TRoot;
18
+ }>;
19
+ export declare function buildModel(options?: BuildModelOptions): Promise<Model>;
20
+ export {};
@@ -1,7 +1,8 @@
1
1
  import { Element, TechnicalElement, TechnologyDefinition } from './core';
2
+ import { ElementArchetype } from './archetype';
2
3
  export type ComponentDefinition = TechnologyDefinition;
3
4
  export declare class Component extends TechnicalElement {
4
5
  readonly name: string;
5
- constructor(name: string, definition?: ComponentDefinition);
6
+ constructor(name: string, definition?: ComponentDefinition, archetype?: ElementArchetype, overrideDefinition?: TechnologyDefinition);
6
7
  getChildElements(): ReadonlyArray<Element>;
7
8
  }
@@ -1,23 +1,24 @@
1
1
  import { Element, Group, TechnicalElement, TechnologyDefinition } from './core';
2
2
  import { Component, ComponentDefinition } from './component';
3
+ import { ElementArchetype } from './archetype';
3
4
  export type ContainerDefinition = TechnologyDefinition;
4
5
  interface DefineComponent {
5
- defineComponent(name: string, definition?: ComponentDefinition): Component;
6
+ defineComponent(name: string, archetypeOrDef?: ElementArchetype | ComponentDefinition, override?: ComponentDefinition): Component;
6
7
  }
7
8
  export declare class ContainerGroup extends Group implements DefineComponent {
8
9
  readonly name: string;
9
10
  private readonly container;
10
11
  private _components;
11
12
  constructor(name: string, container: DefineComponent);
12
- defineComponent(name: string, definition?: ComponentDefinition): Component;
13
+ defineComponent(name: string, archetypeOrDef?: ElementArchetype | ComponentDefinition, override?: ComponentDefinition): Component;
13
14
  getComponents(): ReadonlyArray<Component>;
14
15
  }
15
16
  export declare class Container extends TechnicalElement implements DefineComponent {
16
17
  readonly name: string;
17
18
  private _components;
18
19
  private _groups;
19
- constructor(name: string, definition?: ContainerDefinition);
20
- defineComponent(name: string, definition?: ComponentDefinition): Component;
20
+ constructor(name: string, definition?: ContainerDefinition, archetype?: ElementArchetype, overrideDefinition?: TechnologyDefinition);
21
+ defineComponent(name: string, archetypeOrDef?: ElementArchetype | ComponentDefinition, override?: ComponentDefinition): Component;
21
22
  addGroup(groupName: string): ContainerGroup;
22
23
  getGroups(): ReadonlyArray<ContainerGroup>;
23
24
  getComponentsNotInGroups(): ReadonlyArray<Component>;
package/src/core.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ElementArchetype, RelationshipArchetype } from './archetype';
1
2
  export interface Definition {
2
3
  description?: string;
3
4
  tags?: string[];
@@ -9,10 +10,12 @@ export declare abstract class Element {
9
10
  readonly name: string;
10
11
  readonly description?: string;
11
12
  readonly tags: ReadonlyArray<string>;
13
+ readonly archetype?: ElementArchetype;
14
+ readonly overrideDefinition?: TechnologyDefinition;
12
15
  private _relationships;
13
- constructor(name: string, defaultTags?: string[], definition?: Definition);
16
+ constructor(name: string, defaultTags?: string[], definition?: Definition, archetype?: ElementArchetype, overrideDefinition?: TechnologyDefinition);
14
17
  get canonicalName(): string;
15
- uses(otherElement: Element, definition?: TechnologyDefinition): void;
18
+ uses(otherElement: Element, archetypeOrDef?: RelationshipArchetype | TechnologyDefinition, override?: TechnologyDefinition): void;
16
19
  get relationships(): ReadonlyArray<Relationship>;
17
20
  abstract getChildElements(): ReadonlyArray<Element>;
18
21
  getRelationshipsInHierarchy(): ReadonlyArray<Relationship>;
@@ -20,7 +23,7 @@ export declare abstract class Element {
20
23
  }
21
24
  export declare abstract class TechnicalElement extends Element {
22
25
  readonly technology?: string;
23
- constructor(name: string, defaultTags?: string[], definition?: TechnologyDefinition);
26
+ constructor(name: string, defaultTags?: string[], definition?: TechnologyDefinition, archetype?: ElementArchetype, overrideDefinition?: TechnologyDefinition);
24
27
  }
25
28
  export declare class Relationship {
26
29
  readonly source: Element;
@@ -28,7 +31,9 @@ export declare class Relationship {
28
31
  readonly description?: string;
29
32
  readonly tags: ReadonlyArray<string>;
30
33
  readonly technology?: string;
31
- constructor(source: Element, destination: Element, definition?: TechnologyDefinition);
34
+ readonly archetype?: RelationshipArchetype;
35
+ readonly overrideDefinition?: TechnologyDefinition;
36
+ constructor(source: Element, destination: Element, definition?: TechnologyDefinition, archetype?: RelationshipArchetype, overrideDefinition?: TechnologyDefinition);
32
37
  }
33
38
  export declare class Group {
34
39
  readonly name: string;
@@ -1,4 +1,4 @@
1
- import { BuildModelOptions } from './model';
1
+ import { BuildModelOptions } from './buildModel';
2
2
  import { Views } from './views';
3
3
  export interface GenerateDiagramsOptions<TRoot> extends BuildModelOptions {
4
4
  views: (catalog: TRoot) => Views;
package/src/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './archetype';
1
2
  export * from './model';
2
3
  export * from './person';
3
4
  export * from './softwareSystem';
@@ -5,4 +6,5 @@ export * from './container';
5
6
  export * from './component';
6
7
  export * from './views';
7
8
  export * from './structurizrDslWriter';
9
+ export * from './buildModel';
8
10
  export * from './generateDiagrams';
package/src/index.js CHANGED
@@ -1,24 +1,77 @@
1
- // libs/c4-model/src/model.ts
2
- import { glob } from "glob";
3
- import { join, dirname } from "path";
4
- import { fileURLToPath } from "url";
1
+ // libs/c4-model/src/archetype.ts
2
+ var ElementArchetype = class {
3
+ constructor(name, elementKind, definition, parent) {
4
+ this.name = name;
5
+ this.elementKind = elementKind;
6
+ this.parent = parent;
7
+ this.ownDescription = definition?.description;
8
+ this.ownTechnology = definition?.technology;
9
+ this.ownTags = definition?.tags ?? [];
10
+ this.description = this.ownDescription ?? parent?.description;
11
+ this.technology = this.ownTechnology ?? parent?.technology;
12
+ this.tags = [...parent?.tags ?? [], ...this.ownTags];
13
+ }
14
+ ownDescription;
15
+ ownTechnology;
16
+ ownTags;
17
+ description;
18
+ technology;
19
+ tags;
20
+ };
21
+ var RelationshipArchetype = class {
22
+ constructor(name, definition, parent) {
23
+ this.name = name;
24
+ this.parent = parent;
25
+ this.ownDescription = definition?.description;
26
+ this.ownTechnology = definition?.technology;
27
+ this.ownTags = definition?.tags ?? [];
28
+ this.description = this.ownDescription ?? parent?.description;
29
+ this.technology = this.ownTechnology ?? parent?.technology;
30
+ this.tags = [...parent?.tags ?? [], ...this.ownTags];
31
+ }
32
+ ownDescription;
33
+ ownTechnology;
34
+ ownTags;
35
+ description;
36
+ technology;
37
+ tags;
38
+ };
39
+ function mergeArchetypeWithOverride(archetype, override) {
40
+ return {
41
+ description: override?.description ?? archetype.description,
42
+ technology: override?.technology ?? archetype.technology,
43
+ tags: [...archetype.tags, ...override?.tags ?? []]
44
+ };
45
+ }
5
46
 
6
47
  // libs/c4-model/src/core.ts
7
48
  import { camelCase } from "change-case";
8
49
  var Element = class {
9
- constructor(name, defaultTags = [], definition) {
50
+ constructor(name, defaultTags = [], definition, archetype, overrideDefinition) {
10
51
  this.name = name;
11
52
  this.description = definition?.description;
12
53
  this.tags = (definition?.tags ?? []).concat(["Element"]).concat(defaultTags);
54
+ this.archetype = archetype;
55
+ this.overrideDefinition = overrideDefinition;
13
56
  }
14
57
  description;
15
58
  tags;
59
+ archetype;
60
+ overrideDefinition;
16
61
  _relationships = [];
17
62
  get canonicalName() {
18
63
  return camelCase(this.name);
19
64
  }
20
- uses(otherElement, definition) {
21
- const relationship = new Relationship(this, otherElement, definition);
65
+ uses(otherElement, archetypeOrDef, override) {
66
+ let definition;
67
+ let archetype;
68
+ if (archetypeOrDef instanceof RelationshipArchetype) {
69
+ archetype = archetypeOrDef;
70
+ definition = mergeArchetypeWithOverride(archetypeOrDef, override);
71
+ } else {
72
+ definition = archetypeOrDef;
73
+ }
74
+ const relationship = new Relationship(this, otherElement, definition, archetype, override);
22
75
  this._relationships.push(relationship);
23
76
  }
24
77
  get relationships() {
@@ -37,22 +90,26 @@ var Element = class {
37
90
  };
38
91
  var TechnicalElement = class extends Element {
39
92
  technology;
40
- constructor(name, defaultTags = [], definition) {
41
- super(name, defaultTags, definition);
93
+ constructor(name, defaultTags = [], definition, archetype, overrideDefinition) {
94
+ super(name, defaultTags, definition, archetype, overrideDefinition);
42
95
  this.technology = definition?.technology;
43
96
  }
44
97
  };
45
98
  var Relationship = class {
46
- constructor(source, destination, definition) {
99
+ constructor(source, destination, definition, archetype, overrideDefinition) {
47
100
  this.source = source;
48
101
  this.destination = destination;
49
102
  this.description = definition?.description;
50
103
  this.technology = definition?.technology;
51
104
  this.tags = (definition?.tags ?? []).concat(["Relationship"]);
105
+ this.archetype = archetype;
106
+ this.overrideDefinition = overrideDefinition;
52
107
  }
53
108
  description;
54
109
  tags;
55
110
  technology;
111
+ archetype;
112
+ overrideDefinition;
56
113
  };
57
114
  var Group = class {
58
115
  constructor(name) {
@@ -64,8 +121,8 @@ var Group = class {
64
121
 
65
122
  // libs/c4-model/src/component.ts
66
123
  var Component = class extends TechnicalElement {
67
- constructor(name, definition) {
68
- super(name, ["Component"], definition);
124
+ constructor(name, definition, archetype, overrideDefinition) {
125
+ super(name, ["Component"], definition, archetype, overrideDefinition);
69
126
  this.name = name;
70
127
  }
71
128
  getChildElements() {
@@ -81,8 +138,8 @@ var ContainerGroup = class extends Group {
81
138
  this.container = container;
82
139
  }
83
140
  _components = /* @__PURE__ */ new Map();
84
- defineComponent(name, definition) {
85
- const component = this.container.defineComponent(name, definition);
141
+ defineComponent(name, archetypeOrDef, override) {
142
+ const component = this.container.defineComponent(name, archetypeOrDef, override);
86
143
  this._components.set(name, component);
87
144
  return component;
88
145
  }
@@ -91,17 +148,25 @@ var ContainerGroup = class extends Group {
91
148
  }
92
149
  };
93
150
  var Container = class extends TechnicalElement {
94
- constructor(name, definition) {
95
- super(name, ["Container"], definition);
151
+ constructor(name, definition, archetype, overrideDefinition) {
152
+ super(name, ["Container"], definition, archetype, overrideDefinition);
96
153
  this.name = name;
97
154
  }
98
155
  _components = /* @__PURE__ */ new Map();
99
156
  _groups = /* @__PURE__ */ new Map();
100
- defineComponent(name, definition) {
157
+ defineComponent(name, archetypeOrDef, override) {
101
158
  if (this._components.has(name)) {
102
159
  throw Error(`A Component named '${name}' is defined elsewhere in this Container. A Component can be defined only once.`);
103
160
  }
104
- const component = new Component(name, definition);
161
+ let definition;
162
+ let archetype;
163
+ if (archetypeOrDef instanceof ElementArchetype) {
164
+ archetype = archetypeOrDef;
165
+ definition = mergeArchetypeWithOverride(archetypeOrDef, override);
166
+ } else {
167
+ definition = archetypeOrDef;
168
+ }
169
+ const component = new Component(name, definition, archetype, override);
105
170
  this._components.set(name, component);
106
171
  return component;
107
172
  }
@@ -133,8 +198,8 @@ var SoftwareSystemGroup = class extends Group {
133
198
  this.softwareSystem = softwareSystem;
134
199
  }
135
200
  _containers = /* @__PURE__ */ new Map();
136
- defineContainer(name, definition) {
137
- const container = this.softwareSystem.defineContainer(name, definition);
201
+ defineContainer(name, archetypeOrDef, override) {
202
+ const container = this.softwareSystem.defineContainer(name, archetypeOrDef, override);
138
203
  this._containers.set(name, container);
139
204
  return container;
140
205
  }
@@ -143,17 +208,25 @@ var SoftwareSystemGroup = class extends Group {
143
208
  }
144
209
  };
145
210
  var SoftwareSystem = class extends Element {
146
- constructor(name, definition) {
147
- super(name, ["Software System"], definition);
211
+ constructor(name, definition, archetype, overrideDefinition) {
212
+ super(name, ["Software System"], definition, archetype, overrideDefinition);
148
213
  this.name = name;
149
214
  }
150
215
  _containers = /* @__PURE__ */ new Map();
151
216
  _groups = /* @__PURE__ */ new Map();
152
- defineContainer(name, definition) {
217
+ defineContainer(name, archetypeOrDef, override) {
153
218
  if (this._containers.has(name)) {
154
219
  throw Error(`A Container named '${name}' is defined elsewhere in this SoftwareSystem. A Container can be defined only once.`);
155
220
  }
156
- const container = new Container(name, definition);
221
+ let definition;
222
+ let archetype;
223
+ if (archetypeOrDef instanceof ElementArchetype) {
224
+ archetype = archetypeOrDef;
225
+ definition = mergeArchetypeWithOverride(archetypeOrDef, override);
226
+ } else {
227
+ definition = archetypeOrDef;
228
+ }
229
+ const container = new Container(name, definition, archetype, override);
157
230
  this._containers.set(name, container);
158
231
  return container;
159
232
  }
@@ -179,8 +252,8 @@ var SoftwareSystem = class extends Element {
179
252
 
180
253
  // libs/c4-model/src/person.ts
181
254
  var Person = class extends Element {
182
- constructor(name, definition) {
183
- super(name, ["Person"], definition);
255
+ constructor(name, definition, archetype, overrideDefinition) {
256
+ super(name, ["Person"], definition, archetype, overrideDefinition);
184
257
  this.name = name;
185
258
  }
186
259
  getChildElements() {
@@ -189,7 +262,6 @@ var Person = class extends Element {
189
262
  };
190
263
 
191
264
  // libs/c4-model/src/model.ts
192
- var __dirname = dirname(fileURLToPath(import.meta.url));
193
265
  var ModelGroup = class extends Group {
194
266
  constructor(name, model) {
195
267
  super(name);
@@ -198,13 +270,13 @@ var ModelGroup = class extends Group {
198
270
  }
199
271
  softwareSystems = /* @__PURE__ */ new Map();
200
272
  people = /* @__PURE__ */ new Map();
201
- defineSoftwareSystem(name, definition) {
202
- const softwareSystem = this.model.defineSoftwareSystem(name, definition);
273
+ defineSoftwareSystem(name, archetypeOrDef, override) {
274
+ const softwareSystem = this.model.defineSoftwareSystem(name, archetypeOrDef, override);
203
275
  this.softwareSystems.set(name, softwareSystem);
204
276
  return softwareSystem;
205
277
  }
206
- definePerson(name, definition) {
207
- const person = this.model.definePerson(name, definition);
278
+ definePerson(name, archetypeOrDef, override) {
279
+ const person = this.model.definePerson(name, archetypeOrDef, override);
208
280
  this.people.set(name, person);
209
281
  return person;
210
282
  }
@@ -222,11 +294,19 @@ var Model = class {
222
294
  softwareSystems = /* @__PURE__ */ new Map();
223
295
  people = /* @__PURE__ */ new Map();
224
296
  groups = /* @__PURE__ */ new Map();
225
- defineSoftwareSystem(name, definition) {
297
+ defineSoftwareSystem(name, archetypeOrDef, override) {
226
298
  if (this.softwareSystems.has(name)) {
227
299
  throw Error(`A SoftwareSystem named '${name}' is defined elsewhere in this Model. A SoftwareSystem can be defined only once.`);
228
300
  }
229
- const system = new SoftwareSystem(name, definition);
301
+ let definition;
302
+ let archetype;
303
+ if (archetypeOrDef instanceof ElementArchetype) {
304
+ archetype = archetypeOrDef;
305
+ definition = mergeArchetypeWithOverride(archetypeOrDef, override);
306
+ } else {
307
+ definition = archetypeOrDef;
308
+ }
309
+ const system = new SoftwareSystem(name, definition, archetype, override);
230
310
  this.softwareSystems.set(name, system);
231
311
  return system;
232
312
  }
@@ -239,11 +319,19 @@ var Model = class {
239
319
  }
240
320
  return group;
241
321
  }
242
- definePerson(name, definition) {
322
+ definePerson(name, archetypeOrDef, override) {
243
323
  if (this.people.has(name)) {
244
324
  throw Error(`A Person named '${name}' is defined elsewhere in this Model. A Person can be defined only once.`);
245
325
  }
246
- const person = new Person(name, definition);
326
+ let definition;
327
+ let archetype;
328
+ if (archetypeOrDef instanceof ElementArchetype) {
329
+ archetype = archetypeOrDef;
330
+ definition = mergeArchetypeWithOverride(archetypeOrDef, override);
331
+ } else {
332
+ definition = archetypeOrDef;
333
+ }
334
+ const person = new Person(name, definition, archetype, override);
247
335
  this.people.set(name, person);
248
336
  return person;
249
337
  }
@@ -267,37 +355,6 @@ var Model = class {
267
355
  return Array.from(this.groups.values());
268
356
  }
269
357
  };
270
- async function buildModelWithCatalog(options = {}) {
271
- const { modelName = "model", globPath = "c4.dsl.ts", searchRoot = __dirname } = options;
272
- const model = new Model(modelName);
273
- const result = await glob(`**/${globPath}`, { cwd: searchRoot });
274
- if (result.length === 0) {
275
- throw new Error(`No ${globPath} files found`);
276
- }
277
- const modules = await Promise.all(result.map((file) => import(join(searchRoot, file))));
278
- const registrations = [];
279
- const rootCatalog = {};
280
- for (const module of modules) {
281
- if (!module.c4Module) {
282
- continue;
283
- }
284
- const instance = module.c4Module;
285
- const local = instance.registerDefinitions(model);
286
- rootCatalog[instance.key] = local;
287
- registrations.push({ instance, key: instance.key, local });
288
- }
289
- if (registrations.length === 0) {
290
- throw new Error(`No c4Module exports found in any ${globPath} files`);
291
- }
292
- for (const { instance, key, local } of registrations) {
293
- const dependencies = Object.fromEntries(Object.entries(rootCatalog).filter(([k]) => k !== key));
294
- instance.buildRelationships(local, dependencies);
295
- }
296
- return { model, catalog: rootCatalog };
297
- }
298
- async function buildModel(options = {}) {
299
- return (await buildModelWithCatalog(options)).model;
300
- }
301
358
 
302
359
  // libs/c4-model/src/views.ts
303
360
  var View = class {
@@ -379,13 +436,115 @@ var StructurizrDSLWriter = class {
379
436
  this.model = model;
380
437
  this.views = views;
381
438
  }
439
+ collectArchetypes() {
440
+ const elementSet = /* @__PURE__ */ new Set();
441
+ const relationshipSet = /* @__PURE__ */ new Set();
442
+ const collectFromElement = (element) => {
443
+ if (element.archetype) {
444
+ let arch = element.archetype;
445
+ while (arch) {
446
+ elementSet.add(arch);
447
+ arch = arch.parent;
448
+ }
449
+ }
450
+ element.relationships.forEach((rel) => {
451
+ if (rel.archetype) {
452
+ let arch = rel.archetype;
453
+ while (arch) {
454
+ relationshipSet.add(arch);
455
+ arch = arch.parent;
456
+ }
457
+ }
458
+ });
459
+ element.getChildElements().forEach(collectFromElement);
460
+ };
461
+ const allElements = [...this.model.getPeople(), ...this.model.getSoftwareSystems()];
462
+ allElements.forEach(collectFromElement);
463
+ const sortArchetypes = (set) => {
464
+ const sorted = [];
465
+ const visited = /* @__PURE__ */ new Set();
466
+ const visit = (arch) => {
467
+ if (visited.has(arch)) return;
468
+ if (arch.parent && set.has(arch.parent)) visit(arch.parent);
469
+ visited.add(arch);
470
+ sorted.push(arch);
471
+ };
472
+ set.forEach(visit);
473
+ return sorted;
474
+ };
475
+ return {
476
+ elementArchetypes: sortArchetypes(elementSet),
477
+ relationshipArchetypes: sortArchetypes(relationshipSet)
478
+ };
479
+ }
480
+ writeArchetypes(level) {
481
+ const { elementArchetypes, relationshipArchetypes } = this.collectArchetypes();
482
+ if (elementArchetypes.length === 0 && relationshipArchetypes.length === 0) return "";
483
+ let dsl = this.writeLine(`archetypes {`, level);
484
+ for (const arch of elementArchetypes) {
485
+ const baseType = arch.parent ? arch.parent.name : arch.elementKind;
486
+ let inner = "";
487
+ if (arch.ownDescription) {
488
+ inner += this.writeLine(`description "${arch.ownDescription}"`, level + 2);
489
+ }
490
+ if (arch.ownTechnology) {
491
+ inner += this.writeLine(`technology "${arch.ownTechnology}"`, level + 2);
492
+ }
493
+ if (arch.ownTags.length > 0) {
494
+ inner += this.writeLine(`tags ${arch.ownTags.map((t) => `"${t}"`).join(" ")}`, level + 2);
495
+ }
496
+ if (inner) {
497
+ dsl += this.writeLine(`${arch.name} = ${baseType} {`, level + 1);
498
+ dsl += inner;
499
+ dsl += this.writeLine(`}`, level + 1);
500
+ } else {
501
+ dsl += this.writeLine(`${arch.name} = ${baseType} {}`, level + 1);
502
+ }
503
+ }
504
+ for (const arch of relationshipArchetypes) {
505
+ const arrow = arch.parent ? `--${arch.parent.name}->` : `->`;
506
+ let inner = "";
507
+ if (arch.ownDescription) {
508
+ inner += this.writeLine(`description "${arch.ownDescription}"`, level + 2);
509
+ }
510
+ if (arch.ownTechnology) {
511
+ inner += this.writeLine(`technology "${arch.ownTechnology}"`, level + 2);
512
+ }
513
+ if (arch.ownTags.length > 0) {
514
+ inner += this.writeLine(`tags ${arch.ownTags.map((t) => `"${t}"`).join(" ")}`, level + 2);
515
+ }
516
+ if (inner) {
517
+ dsl += this.writeLine(`${arch.name} = ${arrow} {`, level + 1);
518
+ dsl += inner;
519
+ dsl += this.writeLine(`}`, level + 1);
520
+ } else {
521
+ dsl += this.writeLine(`${arch.name} = ${arrow} {}`, level + 1);
522
+ }
523
+ }
524
+ dsl += this.writeLine(`}`, level);
525
+ return dsl;
526
+ }
382
527
  writeElement(elementType, element, level, closeElement = true) {
383
528
  let elementDsl = "";
384
- elementDsl += this.writeLine(`${element.canonicalName} = ${elementType} "${element.name}" {`, level);
385
- if (element.description) {
386
- elementDsl += this.writeLine(`description "${element.description}"`, level + 1);
529
+ const type = element.archetype ? element.archetype.name : elementType;
530
+ elementDsl += this.writeLine(`${element.canonicalName} = ${type} "${element.name}" {`, level);
531
+ if (element.archetype) {
532
+ const ovr = element.overrideDefinition;
533
+ if (ovr?.description) {
534
+ elementDsl += this.writeLine(`description "${ovr.description}"`, level + 1);
535
+ }
536
+ if (ovr?.tags && ovr.tags.length > 0) {
537
+ elementDsl += this.writeLine(`tags ${ovr.tags.map((tag) => `"${tag}"`).join(" ")}`, level + 1);
538
+ }
539
+ if (ovr && "technology" in ovr && ovr.technology) {
540
+ elementDsl += this.writeLine(`technology "${ovr.technology}"`, level + 1);
541
+ }
542
+ } else {
543
+ if (element.description) {
544
+ elementDsl += this.writeLine(`description "${element.description}"`, level + 1);
545
+ }
546
+ elementDsl += this.writeLine(`tags ${element.tags.map((tag) => `"${tag}"`).join(" ")}`, level + 1);
387
547
  }
388
- elementDsl += this.writeLine(`tags ${element.tags.map((tag) => `"${tag}"`).join(" ")}`, level + 1);
389
548
  if (closeElement) {
390
549
  elementDsl += this.writeLine(`}`, level);
391
550
  }
@@ -394,7 +553,9 @@ var StructurizrDSLWriter = class {
394
553
  writeComponent(component, level) {
395
554
  let componentDsl = "";
396
555
  componentDsl += this.writeElement("component", component, level, false);
397
- componentDsl += this.writeLine(`technology "${component.technology}"`, level + 1);
556
+ if (!component.archetype && component.technology) {
557
+ componentDsl += this.writeLine(`technology "${component.technology}"`, level + 1);
558
+ }
398
559
  componentDsl += this.writeLine(`}`, level);
399
560
  return componentDsl;
400
561
  }
@@ -410,7 +571,9 @@ var StructurizrDSLWriter = class {
410
571
  writeContainer(container, level) {
411
572
  let containerDsl = "";
412
573
  containerDsl += this.writeElement("container", container, level, false);
413
- containerDsl += this.writeLine(`technology "${container.technology}"`, level + 1);
574
+ if (!container.archetype && container.technology) {
575
+ containerDsl += this.writeLine(`technology "${container.technology}"`, level + 1);
576
+ }
414
577
  container.getComponentsNotInGroups().forEach((component) => {
415
578
  containerDsl += this.writeComponent(component, level + 1);
416
579
  });
@@ -441,17 +604,39 @@ var StructurizrDSLWriter = class {
441
604
  softwareSystemDsl += this.writeLine(`}`, level);
442
605
  return softwareSystemDsl;
443
606
  }
607
+ writeRelationship(relationship, level) {
608
+ let dsl = "";
609
+ if (relationship.archetype) {
610
+ const arrow = `--${relationship.archetype.name}->`;
611
+ const ovr = relationship.overrideDefinition;
612
+ const desc = ovr?.description ?? relationship.description ?? "uses";
613
+ dsl += this.writeLine(
614
+ `${relationship.source.canonicalName} ${arrow} ${relationship.destination.canonicalName} "${desc}" {`,
615
+ level
616
+ );
617
+ if (ovr?.technology) {
618
+ dsl += this.writeLine(`technology "${ovr.technology}"`, level + 1);
619
+ }
620
+ if (ovr?.tags && ovr.tags.length > 0) {
621
+ dsl += this.writeLine(`tags ${ovr.tags.map((tag) => `"${tag}"`).join(" ")}`, level + 1);
622
+ }
623
+ dsl += this.writeLine(`}`, level);
624
+ } else {
625
+ const tech = relationship.technology ? ` "${relationship.technology}"` : "";
626
+ dsl += this.writeLine(
627
+ `${relationship.source.canonicalName} -> ${relationship.destination.canonicalName} "${relationship.description ?? "uses"}"${tech} {`,
628
+ level
629
+ );
630
+ dsl += this.writeLine(`tags ${relationship.tags.map((tag) => `"${tag}"`).join(" ")}`, level + 1);
631
+ dsl += this.writeLine(`}`, level);
632
+ }
633
+ return dsl;
634
+ }
444
635
  writeRelationships(elements, level) {
445
636
  let relationshipsDsl = "";
446
637
  elements.forEach((element) => {
447
638
  element.getRelationshipsInHierarchy().forEach((relationship) => {
448
- const tech = relationship.technology ? ` "${relationship.technology}"` : "";
449
- relationshipsDsl += this.writeLine(
450
- `${relationship.source.canonicalName} -> ${relationship.destination.canonicalName} "${relationship.description ?? "uses"}"${tech} {`,
451
- level
452
- );
453
- relationshipsDsl += this.writeLine(`tags ${relationship.tags.map((tag) => `"${tag}"`).join(" ")}`, level + 1);
454
- relationshipsDsl += this.writeLine(`}`, level);
639
+ relationshipsDsl += this.writeRelationship(relationship, level);
455
640
  });
456
641
  });
457
642
  return relationshipsDsl;
@@ -471,6 +656,7 @@ var StructurizrDSLWriter = class {
471
656
  writeModel(model, level) {
472
657
  let modelDsl = "";
473
658
  modelDsl += this.writeLine(`model {`, level);
659
+ modelDsl += this.writeArchetypes(level + 1);
474
660
  modelDsl += this.writeLine("// Elements", level + 1);
475
661
  model.getPeopleNotInGroups().forEach((person) => {
476
662
  modelDsl += this.writeElement("person", person, level + 1);
@@ -487,10 +673,7 @@ var StructurizrDSLWriter = class {
487
673
  return modelDsl;
488
674
  }
489
675
  writeView(view, viewType, level) {
490
- let viewDsl = this.writeLine(
491
- `${viewType}${view.subject ? ' "' + view.subject.canonicalName + '"' : ""} "${view.key}" {`,
492
- level
493
- );
676
+ let viewDsl = this.writeLine(`${viewType}${view.subject ? ' "' + view.subject.canonicalName + '"' : ""} "${view.key}" {`, level);
494
677
  viewDsl += this.writeLine(`description "${view.description}"`, level + 1);
495
678
  if (view.title) {
496
679
  viewDsl += this.writeLine(`title "${view.title}"`, level + 1);
@@ -540,6 +723,46 @@ var StructurizrDSLWriter = class {
540
723
  }
541
724
  };
542
725
 
726
+ // libs/c4-model/src/buildModel.ts
727
+ import { glob } from "glob";
728
+ import { join, dirname } from "path";
729
+ import { fileURLToPath } from "url";
730
+ var __dirname = dirname(fileURLToPath(import.meta.url));
731
+ async function buildModelWithCatalog(options = {}) {
732
+ const { modelName = "model", modules: explicitModules } = options;
733
+ const model = new Model(modelName);
734
+ let c4Modules;
735
+ if (explicitModules) {
736
+ c4Modules = [...explicitModules];
737
+ } else {
738
+ const { globPath = "c4.dsl.ts", searchRoot = __dirname } = options;
739
+ const result = await glob(`**/${globPath}`, { cwd: searchRoot });
740
+ if (result.length === 0) {
741
+ throw new Error(`No ${globPath} files found`);
742
+ }
743
+ const imported = await Promise.all(result.map((file) => import(join(searchRoot, file))));
744
+ c4Modules = imported.filter((m) => m.c4Module).map((m) => m.c4Module);
745
+ }
746
+ if (c4Modules.length === 0) {
747
+ throw new Error("No c4Module instances found");
748
+ }
749
+ const registrations = [];
750
+ const rootCatalog = {};
751
+ for (const instance of c4Modules) {
752
+ const local = instance.registerDefinitions(model);
753
+ rootCatalog[instance.key] = local;
754
+ registrations.push({ instance, key: instance.key, local });
755
+ }
756
+ for (const { instance, key, local } of registrations) {
757
+ const dependencies = Object.fromEntries(Object.entries(rootCatalog).filter(([k]) => k !== key));
758
+ instance.buildRelationships(local, dependencies);
759
+ }
760
+ return { model, catalog: rootCatalog };
761
+ }
762
+ async function buildModel(options = {}) {
763
+ return (await buildModelWithCatalog(options)).model;
764
+ }
765
+
543
766
  // libs/c4-model/src/generateDiagrams.ts
544
767
  import * as fs from "fs";
545
768
  import * as os from "os";
@@ -573,9 +796,11 @@ export {
573
796
  Component,
574
797
  Container,
575
798
  ContainerGroup,
799
+ ElementArchetype,
576
800
  Model,
577
801
  ModelGroup,
578
802
  Person,
803
+ RelationshipArchetype,
579
804
  SoftwareSystem,
580
805
  SoftwareSystemGroup,
581
806
  StructurizrDSLWriter,
@@ -583,5 +808,6 @@ export {
583
808
  Views,
584
809
  buildModel,
585
810
  buildModelWithCatalog,
586
- generateDiagrams
811
+ generateDiagrams,
812
+ mergeArchetypeWithOverride
587
813
  };
package/src/model.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Group } from './core';
2
2
  import { SoftwareSystem, SoftwareSystemDefinition } from './softwareSystem';
3
3
  import { Person, PersonDefinition } from './person';
4
+ import { ElementArchetype } from './archetype';
4
5
  export type CatalogKeyOf<TRoot, TModule> = {
5
6
  [K in keyof TRoot]: TRoot[K] extends TModule ? K : never;
6
7
  }[keyof TRoot];
@@ -11,10 +12,10 @@ export interface C4Module<TRoot, TLocal> {
11
12
  buildRelationships(local: TLocal, dependencies: Dependencies<TRoot, TLocal>): void;
12
13
  }
13
14
  interface DefineSoftwareSystem {
14
- defineSoftwareSystem(name: string, definition?: SoftwareSystemDefinition): SoftwareSystem;
15
+ defineSoftwareSystem(name: string, archetypeOrDef?: ElementArchetype | SoftwareSystemDefinition, override?: SoftwareSystemDefinition): SoftwareSystem;
15
16
  }
16
17
  interface DefinePerson {
17
- definePerson(name: string, definition?: PersonDefinition): Person;
18
+ definePerson(name: string, archetypeOrDef?: ElementArchetype | PersonDefinition, override?: PersonDefinition): Person;
18
19
  }
19
20
  export declare class ModelGroup extends Group implements DefineSoftwareSystem, DefinePerson {
20
21
  readonly name: string;
@@ -22,14 +23,14 @@ export declare class ModelGroup extends Group implements DefineSoftwareSystem, D
22
23
  private softwareSystems;
23
24
  private people;
24
25
  constructor(name: string, model: DefineSoftwareSystem & DefinePerson);
25
- defineSoftwareSystem(name: string, definition?: SoftwareSystemDefinition): SoftwareSystem;
26
- definePerson(name: string, definition?: PersonDefinition): Person;
26
+ defineSoftwareSystem(name: string, archetypeOrDef?: ElementArchetype | SoftwareSystemDefinition, override?: SoftwareSystemDefinition): SoftwareSystem;
27
+ definePerson(name: string, archetypeOrDef?: ElementArchetype | PersonDefinition, override?: PersonDefinition): Person;
27
28
  getSoftwareSystems(): ReadonlyArray<SoftwareSystem>;
28
29
  getPeople(): ReadonlyArray<Person>;
29
30
  }
30
31
  export interface ModelDefinitions {
31
- defineSoftwareSystem(name: string, definition?: SoftwareSystemDefinition): SoftwareSystem;
32
- definePerson(name: string, definition?: PersonDefinition): Person;
32
+ defineSoftwareSystem(name: string, archetypeOrDef?: ElementArchetype | SoftwareSystemDefinition, override?: SoftwareSystemDefinition): SoftwareSystem;
33
+ definePerson(name: string, archetypeOrDef?: ElementArchetype | PersonDefinition, override?: PersonDefinition): Person;
33
34
  }
34
35
  export declare class Model {
35
36
  name: string;
@@ -37,9 +38,9 @@ export declare class Model {
37
38
  private softwareSystems;
38
39
  private people;
39
40
  private groups;
40
- defineSoftwareSystem(name: string, definition?: SoftwareSystemDefinition): SoftwareSystem;
41
+ defineSoftwareSystem(name: string, archetypeOrDef?: ElementArchetype | SoftwareSystemDefinition, override?: SoftwareSystemDefinition): SoftwareSystem;
41
42
  addGroup(groupName: string): Group & ModelDefinitions;
42
- definePerson(name: string, definition?: PersonDefinition): Person;
43
+ definePerson(name: string, archetypeOrDef?: ElementArchetype | PersonDefinition, override?: PersonDefinition): Person;
43
44
  validate(): void;
44
45
  getPeople(): ReadonlyArray<Person>;
45
46
  getSoftwareSystems(): ReadonlyArray<SoftwareSystem>;
@@ -47,14 +48,4 @@ export declare class Model {
47
48
  getSoftwareSystemsNotInGroups(): ReadonlyArray<SoftwareSystem>;
48
49
  getGroups(): ReadonlyArray<ModelGroup>;
49
50
  }
50
- export interface BuildModelOptions {
51
- modelName?: string;
52
- globPath?: string;
53
- searchRoot?: string;
54
- }
55
- export declare function buildModelWithCatalog<TRoot>(options?: BuildModelOptions): Promise<{
56
- model: Model;
57
- catalog: TRoot;
58
- }>;
59
- export declare function buildModel(options?: BuildModelOptions): Promise<Model>;
60
51
  export {};
package/src/person.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { Definition, Element } from './core';
1
+ import { Definition, Element, TechnologyDefinition } from './core';
2
+ import { ElementArchetype } from './archetype';
2
3
  export type PersonDefinition = Definition;
3
4
  export declare class Person extends Element {
4
5
  readonly name: string;
5
- constructor(name: string, definition?: PersonDefinition);
6
+ constructor(name: string, definition?: PersonDefinition, archetype?: ElementArchetype, overrideDefinition?: TechnologyDefinition);
6
7
  getChildElements(): ReadonlyArray<Element>;
7
8
  }
@@ -1,8 +1,9 @@
1
- import { Definition, Element, Group } from './core';
1
+ import { Definition, Element, Group, TechnologyDefinition } from './core';
2
2
  import { Container, ContainerDefinition } from './container';
3
+ import { ElementArchetype } from './archetype';
3
4
  export type SoftwareSystemDefinition = Definition;
4
5
  interface DefineContainer {
5
- defineContainer(name: string, definition?: ContainerDefinition): Container;
6
+ defineContainer(name: string, archetypeOrDef?: ElementArchetype | ContainerDefinition, override?: ContainerDefinition): Container;
6
7
  }
7
8
  export interface SoftwareSystemReference {
8
9
  name: string;
@@ -12,15 +13,15 @@ export declare class SoftwareSystemGroup extends Group implements DefineContaine
12
13
  private readonly softwareSystem;
13
14
  private _containers;
14
15
  constructor(name: string, softwareSystem: DefineContainer);
15
- defineContainer(name: string, definition?: ContainerDefinition): Container;
16
+ defineContainer(name: string, archetypeOrDef?: ElementArchetype | ContainerDefinition, override?: ContainerDefinition): Container;
16
17
  getContainers(): ReadonlyArray<Container>;
17
18
  }
18
19
  export declare class SoftwareSystem extends Element implements DefineContainer {
19
20
  readonly name: string;
20
21
  private _containers;
21
22
  private _groups;
22
- constructor(name: string, definition?: SoftwareSystemDefinition);
23
- defineContainer(name: string, definition?: ContainerDefinition): Container;
23
+ constructor(name: string, definition?: SoftwareSystemDefinition, archetype?: ElementArchetype, overrideDefinition?: TechnologyDefinition);
24
+ defineContainer(name: string, archetypeOrDef?: ElementArchetype | ContainerDefinition, override?: ContainerDefinition): Container;
24
25
  addGroup(groupName: string): SoftwareSystemGroup;
25
26
  getGroups(): ReadonlyArray<SoftwareSystemGroup>;
26
27
  getChildElements(): ReadonlyArray<Element>;
@@ -4,12 +4,15 @@ export declare class StructurizrDSLWriter {
4
4
  private readonly model;
5
5
  private readonly views;
6
6
  constructor(model: Model, views: Views);
7
+ private collectArchetypes;
8
+ private writeArchetypes;
7
9
  private writeElement;
8
10
  private writeComponent;
9
11
  private writeContainerGroup;
10
12
  private writeContainer;
11
13
  private writeSoftwareSystemGroup;
12
14
  private writeSoftwareSystem;
15
+ private writeRelationship;
13
16
  private writeRelationships;
14
17
  private writeModelGroup;
15
18
  private writeModel;