@domainlang/language 0.1.20

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 (212) hide show
  1. package/README.md +163 -0
  2. package/out/ast-augmentation.d.ts +6 -0
  3. package/out/ast-augmentation.js +2 -0
  4. package/out/ast-augmentation.js.map +1 -0
  5. package/out/domain-lang-module.d.ts +57 -0
  6. package/out/domain-lang-module.js +67 -0
  7. package/out/domain-lang-module.js.map +1 -0
  8. package/out/generated/ast.d.ts +759 -0
  9. package/out/generated/ast.js +556 -0
  10. package/out/generated/ast.js.map +1 -0
  11. package/out/generated/grammar.d.ts +6 -0
  12. package/out/generated/grammar.js +2407 -0
  13. package/out/generated/grammar.js.map +1 -0
  14. package/out/generated/module.d.ts +13 -0
  15. package/out/generated/module.js +21 -0
  16. package/out/generated/module.js.map +1 -0
  17. package/out/index.d.ts +16 -0
  18. package/out/index.js +22 -0
  19. package/out/index.js.map +1 -0
  20. package/out/lsp/domain-lang-code-actions.d.ts +55 -0
  21. package/out/lsp/domain-lang-code-actions.js +143 -0
  22. package/out/lsp/domain-lang-code-actions.js.map +1 -0
  23. package/out/lsp/domain-lang-completion.d.ts +37 -0
  24. package/out/lsp/domain-lang-completion.js +452 -0
  25. package/out/lsp/domain-lang-completion.js.map +1 -0
  26. package/out/lsp/domain-lang-formatter.d.ts +15 -0
  27. package/out/lsp/domain-lang-formatter.js +43 -0
  28. package/out/lsp/domain-lang-formatter.js.map +1 -0
  29. package/out/lsp/domain-lang-naming.d.ts +34 -0
  30. package/out/lsp/domain-lang-naming.js +49 -0
  31. package/out/lsp/domain-lang-naming.js.map +1 -0
  32. package/out/lsp/domain-lang-scope.d.ts +59 -0
  33. package/out/lsp/domain-lang-scope.js +102 -0
  34. package/out/lsp/domain-lang-scope.js.map +1 -0
  35. package/out/lsp/domain-lang-workspace-manager.d.ts +21 -0
  36. package/out/lsp/domain-lang-workspace-manager.js +93 -0
  37. package/out/lsp/domain-lang-workspace-manager.js.map +1 -0
  38. package/out/lsp/hover/ddd-pattern-explanations.d.ts +50 -0
  39. package/out/lsp/hover/ddd-pattern-explanations.js +196 -0
  40. package/out/lsp/hover/ddd-pattern-explanations.js.map +1 -0
  41. package/out/lsp/hover/domain-lang-hover.d.ts +19 -0
  42. package/out/lsp/hover/domain-lang-hover.js +302 -0
  43. package/out/lsp/hover/domain-lang-hover.js.map +1 -0
  44. package/out/lsp/hover/domain-lang-keywords.d.ts +13 -0
  45. package/out/lsp/hover/domain-lang-keywords.js +47 -0
  46. package/out/lsp/hover/domain-lang-keywords.js.map +1 -0
  47. package/out/lsp/manifest-diagnostics.d.ts +82 -0
  48. package/out/lsp/manifest-diagnostics.js +230 -0
  49. package/out/lsp/manifest-diagnostics.js.map +1 -0
  50. package/out/main-browser.d.ts +1 -0
  51. package/out/main-browser.js +11 -0
  52. package/out/main-browser.js.map +1 -0
  53. package/out/main.d.ts +1 -0
  54. package/out/main.js +74 -0
  55. package/out/main.js.map +1 -0
  56. package/out/sdk/ast-augmentation.d.ts +136 -0
  57. package/out/sdk/ast-augmentation.js +62 -0
  58. package/out/sdk/ast-augmentation.js.map +1 -0
  59. package/out/sdk/index.d.ts +94 -0
  60. package/out/sdk/index.js +97 -0
  61. package/out/sdk/index.js.map +1 -0
  62. package/out/sdk/indexes.d.ts +16 -0
  63. package/out/sdk/indexes.js +97 -0
  64. package/out/sdk/indexes.js.map +1 -0
  65. package/out/sdk/loader-node.d.ts +51 -0
  66. package/out/sdk/loader-node.js +119 -0
  67. package/out/sdk/loader-node.js.map +1 -0
  68. package/out/sdk/loader.d.ts +49 -0
  69. package/out/sdk/loader.js +85 -0
  70. package/out/sdk/loader.js.map +1 -0
  71. package/out/sdk/patterns.d.ts +93 -0
  72. package/out/sdk/patterns.js +123 -0
  73. package/out/sdk/patterns.js.map +1 -0
  74. package/out/sdk/query.d.ts +90 -0
  75. package/out/sdk/query.js +679 -0
  76. package/out/sdk/query.js.map +1 -0
  77. package/out/sdk/resolution.d.ts +52 -0
  78. package/out/sdk/resolution.js +68 -0
  79. package/out/sdk/resolution.js.map +1 -0
  80. package/out/sdk/types.d.ts +280 -0
  81. package/out/sdk/types.js +8 -0
  82. package/out/sdk/types.js.map +1 -0
  83. package/out/services/dependency-analyzer.d.ts +58 -0
  84. package/out/services/dependency-analyzer.js +254 -0
  85. package/out/services/dependency-analyzer.js.map +1 -0
  86. package/out/services/dependency-resolver.d.ts +146 -0
  87. package/out/services/dependency-resolver.js +452 -0
  88. package/out/services/dependency-resolver.js.map +1 -0
  89. package/out/services/git-url-resolver.browser.d.ts +10 -0
  90. package/out/services/git-url-resolver.browser.js +19 -0
  91. package/out/services/git-url-resolver.browser.js.map +1 -0
  92. package/out/services/git-url-resolver.d.ts +158 -0
  93. package/out/services/git-url-resolver.js +416 -0
  94. package/out/services/git-url-resolver.js.map +1 -0
  95. package/out/services/governance-validator.d.ts +44 -0
  96. package/out/services/governance-validator.js +153 -0
  97. package/out/services/governance-validator.js.map +1 -0
  98. package/out/services/import-resolver.d.ts +77 -0
  99. package/out/services/import-resolver.js +240 -0
  100. package/out/services/import-resolver.js.map +1 -0
  101. package/out/services/performance-optimizer.d.ts +60 -0
  102. package/out/services/performance-optimizer.js +140 -0
  103. package/out/services/performance-optimizer.js.map +1 -0
  104. package/out/services/relationship-inference.d.ts +11 -0
  105. package/out/services/relationship-inference.js +98 -0
  106. package/out/services/relationship-inference.js.map +1 -0
  107. package/out/services/semver.d.ts +98 -0
  108. package/out/services/semver.js +195 -0
  109. package/out/services/semver.js.map +1 -0
  110. package/out/services/types.d.ts +340 -0
  111. package/out/services/types.js +46 -0
  112. package/out/services/types.js.map +1 -0
  113. package/out/services/workspace-manager.d.ts +123 -0
  114. package/out/services/workspace-manager.js +489 -0
  115. package/out/services/workspace-manager.js.map +1 -0
  116. package/out/syntaxes/domain-lang.monarch.d.ts +76 -0
  117. package/out/syntaxes/domain-lang.monarch.js +29 -0
  118. package/out/syntaxes/domain-lang.monarch.js.map +1 -0
  119. package/out/utils/import-utils.d.ts +49 -0
  120. package/out/utils/import-utils.js +128 -0
  121. package/out/utils/import-utils.js.map +1 -0
  122. package/out/validation/bounded-context.d.ts +11 -0
  123. package/out/validation/bounded-context.js +79 -0
  124. package/out/validation/bounded-context.js.map +1 -0
  125. package/out/validation/classification.d.ts +3 -0
  126. package/out/validation/classification.js +3 -0
  127. package/out/validation/classification.js.map +1 -0
  128. package/out/validation/constants.d.ts +180 -0
  129. package/out/validation/constants.js +235 -0
  130. package/out/validation/constants.js.map +1 -0
  131. package/out/validation/domain-lang-validator.d.ts +2 -0
  132. package/out/validation/domain-lang-validator.js +27 -0
  133. package/out/validation/domain-lang-validator.js.map +1 -0
  134. package/out/validation/domain.d.ts +11 -0
  135. package/out/validation/domain.js +63 -0
  136. package/out/validation/domain.js.map +1 -0
  137. package/out/validation/import.d.ts +68 -0
  138. package/out/validation/import.js +237 -0
  139. package/out/validation/import.js.map +1 -0
  140. package/out/validation/manifest.d.ts +144 -0
  141. package/out/validation/manifest.js +327 -0
  142. package/out/validation/manifest.js.map +1 -0
  143. package/out/validation/maps.d.ts +21 -0
  144. package/out/validation/maps.js +60 -0
  145. package/out/validation/maps.js.map +1 -0
  146. package/out/validation/metadata.d.ts +7 -0
  147. package/out/validation/metadata.js +16 -0
  148. package/out/validation/metadata.js.map +1 -0
  149. package/out/validation/model.d.ts +12 -0
  150. package/out/validation/model.js +29 -0
  151. package/out/validation/model.js.map +1 -0
  152. package/out/validation/relationships.d.ts +12 -0
  153. package/out/validation/relationships.js +94 -0
  154. package/out/validation/relationships.js.map +1 -0
  155. package/out/validation/shared.d.ts +6 -0
  156. package/out/validation/shared.js +12 -0
  157. package/out/validation/shared.js.map +1 -0
  158. package/package.json +110 -0
  159. package/src/ast-augmentation.ts +5 -0
  160. package/src/domain-lang-module.ts +112 -0
  161. package/src/domain-lang.langium +351 -0
  162. package/src/generated/ast.ts +986 -0
  163. package/src/generated/grammar.ts +2409 -0
  164. package/src/generated/module.ts +25 -0
  165. package/src/index.ts +24 -0
  166. package/src/lsp/domain-lang-code-actions.ts +189 -0
  167. package/src/lsp/domain-lang-completion.ts +514 -0
  168. package/src/lsp/domain-lang-formatter.ts +51 -0
  169. package/src/lsp/domain-lang-naming.ts +56 -0
  170. package/src/lsp/domain-lang-scope.ts +137 -0
  171. package/src/lsp/domain-lang-workspace-manager.ts +104 -0
  172. package/src/lsp/hover/ddd-pattern-explanations.ts +237 -0
  173. package/src/lsp/hover/domain-lang-hover.ts +338 -0
  174. package/src/lsp/hover/domain-lang-keywords.ts +50 -0
  175. package/src/lsp/manifest-diagnostics.ts +290 -0
  176. package/src/main-browser.ts +15 -0
  177. package/src/main.ts +85 -0
  178. package/src/sdk/README.md +297 -0
  179. package/src/sdk/ast-augmentation.ts +157 -0
  180. package/src/sdk/index.ts +126 -0
  181. package/src/sdk/indexes.ts +155 -0
  182. package/src/sdk/loader-node.ts +146 -0
  183. package/src/sdk/loader.ts +99 -0
  184. package/src/sdk/patterns.ts +147 -0
  185. package/src/sdk/query.ts +802 -0
  186. package/src/sdk/resolution.ts +78 -0
  187. package/src/sdk/types.ts +323 -0
  188. package/src/services/dependency-analyzer.ts +321 -0
  189. package/src/services/dependency-resolver.ts +551 -0
  190. package/src/services/git-url-resolver.browser.ts +26 -0
  191. package/src/services/git-url-resolver.ts +517 -0
  192. package/src/services/governance-validator.ts +177 -0
  193. package/src/services/import-resolver.ts +292 -0
  194. package/src/services/performance-optimizer.ts +170 -0
  195. package/src/services/relationship-inference.ts +121 -0
  196. package/src/services/semver.ts +213 -0
  197. package/src/services/types.ts +415 -0
  198. package/src/services/workspace-manager.ts +607 -0
  199. package/src/syntaxes/domain-lang.monarch.ts +29 -0
  200. package/src/utils/import-utils.ts +152 -0
  201. package/src/validation/bounded-context.ts +99 -0
  202. package/src/validation/classification.ts +5 -0
  203. package/src/validation/constants.ts +304 -0
  204. package/src/validation/domain-lang-validator.ts +33 -0
  205. package/src/validation/domain.ts +77 -0
  206. package/src/validation/import.ts +295 -0
  207. package/src/validation/manifest.ts +439 -0
  208. package/src/validation/maps.ts +76 -0
  209. package/src/validation/metadata.ts +18 -0
  210. package/src/validation/model.ts +37 -0
  211. package/src/validation/relationships.ts +154 -0
  212. package/src/validation/shared.ts +14 -0
@@ -0,0 +1,802 @@
1
+ /**
2
+ * Query and QueryBuilder implementation for the Model Query SDK.
3
+ * Provides fluent, lazy evaluation of model queries.
4
+ */
5
+
6
+ import type { AstNode, LangiumDocument, URI } from 'langium';
7
+ import { AstUtils } from 'langium';
8
+ import type {
9
+ BoundedContext,
10
+ BoundedContextRef,
11
+ Classification,
12
+ ContextMap,
13
+ Domain,
14
+ DomainMap,
15
+ Model,
16
+ NamespaceDeclaration,
17
+ Relationship,
18
+ Team,
19
+ } from '../generated/ast.js';
20
+ import {
21
+ isBoundedContext,
22
+ isClassification,
23
+ isContextMap,
24
+ isDomain,
25
+ isDomainMap,
26
+ isModel,
27
+ isNamespaceDeclaration,
28
+ isTeam,
29
+ isThisRef,
30
+ } from '../generated/ast.js';
31
+ import { QualifiedNameProvider } from '../lsp/domain-lang-naming.js';
32
+ import type { DomainLangServices } from '../domain-lang-module.js';
33
+ import { buildIndexes } from './indexes.js';
34
+ import {
35
+ metadataAsMap,
36
+ effectiveClassification,
37
+ effectiveTeam,
38
+ } from './resolution.js';
39
+ import { isDownstreamPattern, isUpstreamPattern, matchesPattern } from './patterns.js';
40
+ import type {
41
+ BcQueryBuilder,
42
+ ModelIndexes,
43
+ Query,
44
+ QueryBuilder,
45
+ RelationshipView,
46
+ } from './types.js';
47
+
48
+ /**
49
+ * Tracks which models have been augmented to avoid redundant augmentation.
50
+ * Uses WeakSet to allow garbage collection of unused models.
51
+ */
52
+ const augmentedModels = new WeakSet<Model>();
53
+
54
+ /**
55
+ * Ensures a model is augmented with SDK properties.
56
+ * Idempotent - safe to call multiple times.
57
+ *
58
+ * @param model - Model to ensure is augmented
59
+ */
60
+ function ensureAugmented(model: Model): void {
61
+ if (!augmentedModels.has(model)) {
62
+ augmentModelInternal(model);
63
+ augmentedModels.add(model);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Creates a Query instance from a Model node.
69
+ * Zero-copy operation for already-linked AST (used in LSP/validation).
70
+ *
71
+ * Automatically augments the AST with resolved properties on first access.
72
+ *
73
+ * @param model - Root Model node
74
+ * @returns Query interface for model traversal
75
+ */
76
+ export function fromModel(model: Model): Query {
77
+ ensureAugmented(model);
78
+ return new QueryImpl(model);
79
+ }
80
+
81
+ /**
82
+ * Creates a Query instance from a LangiumDocument.
83
+ * Zero-copy operation for already-linked AST (used in LSP providers).
84
+ *
85
+ * Automatically augments the AST with resolved properties on first access.
86
+ *
87
+ * @param document - LangiumDocument containing a Model
88
+ * @returns Query interface for model traversal
89
+ */
90
+ export function fromDocument(document: LangiumDocument<Model>): Query {
91
+ const model = document.parseResult.value;
92
+ ensureAugmented(model);
93
+ return new QueryImpl(model);
94
+ }
95
+
96
+ /**
97
+ * Creates a Query instance from DomainLangServices and document URI.
98
+ * Zero-copy operation for already-linked AST (used when only services available).
99
+ *
100
+ * Automatically augments the AST with resolved properties on first access.
101
+ *
102
+ * @param services - DomainLangServices instance
103
+ * @param documentUri - URI of the document to query
104
+ * @returns Query interface for model traversal
105
+ * @throws Error if document not found or not a Model
106
+ */
107
+ export function fromServices(services: DomainLangServices, documentUri: URI): Query {
108
+ const document = services.shared.workspace.LangiumDocuments.getDocument(documentUri);
109
+ if (!document) {
110
+ throw new Error(`Document not found: ${documentUri.toString()}`);
111
+ }
112
+
113
+ const model = document.parseResult.value;
114
+ if (!isModel(model)) {
115
+ throw new Error(`Document root is not a Model: ${documentUri.toString()}`);
116
+ }
117
+
118
+ ensureAugmented(model);
119
+ return new QueryImpl(model);
120
+ }
121
+
122
+ /**
123
+ * Implementation of Query interface.
124
+ * Lazily builds indexes on first access.
125
+ */
126
+ class QueryImpl implements Query {
127
+ private readonly model: Model;
128
+ private readonly fqnProvider: QualifiedNameProvider;
129
+ private indexes?: ModelIndexes;
130
+
131
+ constructor(model: Model) {
132
+ this.model = model;
133
+ this.fqnProvider = new QualifiedNameProvider();
134
+ }
135
+
136
+ /**
137
+ * Lazily builds and caches indexes on first access.
138
+ */
139
+ private getIndexes(): ModelIndexes {
140
+ if (!this.indexes) {
141
+ this.indexes = buildIndexes(this.model);
142
+ }
143
+ return this.indexes;
144
+ }
145
+
146
+ domains(): QueryBuilder<Domain> {
147
+ // Use generator for lazy iteration per PRS requirement
148
+ const model = this.model;
149
+ function* domainIterator(): Generator<Domain> {
150
+ for (const node of AstUtils.streamAllContents(model)) {
151
+ if (isDomain(node)) {
152
+ yield node;
153
+ }
154
+ }
155
+ }
156
+ return new QueryBuilderImpl(domainIterator(), this.fqnProvider);
157
+ }
158
+
159
+ boundedContexts(): BcQueryBuilder {
160
+ // Use generator for lazy iteration per PRS requirement
161
+ const model = this.model;
162
+ function* bcIterator(): Generator<BoundedContext> {
163
+ for (const node of AstUtils.streamAllContents(model)) {
164
+ if (isBoundedContext(node)) {
165
+ yield node;
166
+ }
167
+ }
168
+ }
169
+ return new BcQueryBuilderImpl(bcIterator(), this.fqnProvider, this.getIndexes());
170
+ }
171
+
172
+ teams(): QueryBuilder<Team> {
173
+ // Use generator for lazy iteration
174
+ const model = this.model;
175
+ function* teamIterator(): Generator<Team> {
176
+ for (const node of AstUtils.streamAllContents(model)) {
177
+ if (isTeam(node)) {
178
+ yield node;
179
+ }
180
+ }
181
+ }
182
+ return new QueryBuilderImpl(teamIterator(), this.fqnProvider);
183
+ }
184
+
185
+ classifications(): QueryBuilder<Classification> {
186
+ // Use generator for lazy iteration
187
+ const model = this.model;
188
+ function* classIterator(): Generator<Classification> {
189
+ for (const node of AstUtils.streamAllContents(model)) {
190
+ if (isClassification(node)) {
191
+ yield node;
192
+ }
193
+ }
194
+ }
195
+ return new QueryBuilderImpl(classIterator(), this.fqnProvider);
196
+ }
197
+
198
+ relationships(): QueryBuilder<RelationshipView> {
199
+ // Use generator for lazy iteration - combines BC and ContextMap relationships
200
+ return new QueryBuilderImpl(this.iterateRelationships(), this.fqnProvider);
201
+ }
202
+
203
+ /** @internal Generator for relationship iteration */
204
+ private *iterateRelationships(): Generator<RelationshipView> {
205
+ // Collect relationships defined on bounded contexts
206
+ for (const node of AstUtils.streamAllContents(this.model)) {
207
+ if (isBoundedContext(node)) {
208
+ for (const rel of node.relationships) {
209
+ const view = this.createRelationshipView(rel, node, 'BoundedContext');
210
+ if (view) {
211
+ yield view;
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ // Collect from ContextMap.relationships
218
+ for (const node of AstUtils.streamAllContents(this.model)) {
219
+ if (isContextMap(node)) {
220
+ for (const rel of node.relationships) {
221
+ const view = this.createRelationshipView(rel, undefined, 'ContextMap');
222
+ if (view) {
223
+ yield view;
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
229
+
230
+ contextMaps(): QueryBuilder<ContextMap> {
231
+ const model = this.model;
232
+ function* cmapIterator(): Generator<ContextMap> {
233
+ for (const node of AstUtils.streamAllContents(model)) {
234
+ if (isContextMap(node)) {
235
+ yield node;
236
+ }
237
+ }
238
+ }
239
+ return new QueryBuilderImpl(cmapIterator(), this.fqnProvider);
240
+ }
241
+
242
+ domainMaps(): QueryBuilder<DomainMap> {
243
+ const model = this.model;
244
+ function* dmapIterator(): Generator<DomainMap> {
245
+ for (const node of AstUtils.streamAllContents(model)) {
246
+ if (isDomainMap(node)) {
247
+ yield node;
248
+ }
249
+ }
250
+ }
251
+ return new QueryBuilderImpl(dmapIterator(), this.fqnProvider);
252
+ }
253
+
254
+ namespaces(): QueryBuilder<NamespaceDeclaration> {
255
+ const model = this.model;
256
+ function* nsIterator(): Generator<NamespaceDeclaration> {
257
+ for (const node of AstUtils.streamAllContents(model)) {
258
+ if (isNamespaceDeclaration(node)) {
259
+ yield node;
260
+ }
261
+ }
262
+ }
263
+ return new QueryBuilderImpl(nsIterator(), this.fqnProvider);
264
+ }
265
+
266
+ byFqn<T extends AstNode = AstNode>(fqn: string): T | undefined {
267
+ return this.getIndexes().byFqn.get(fqn) as T | undefined;
268
+ }
269
+
270
+ domain(name: string): Domain | undefined {
271
+ // Try FQN lookup first
272
+ const byFqn = this.byFqn<Domain>(name);
273
+ if (byFqn) {
274
+ return byFqn;
275
+ }
276
+
277
+ // Fallback to simple name lookup
278
+ const nodes = this.getIndexes().byName.get(name) ?? [];
279
+ return nodes.find(isDomain);
280
+ }
281
+
282
+ boundedContext(name: string): BoundedContext | undefined {
283
+ // Try FQN lookup first
284
+ const byFqn = this.byFqn<BoundedContext>(name);
285
+ if (byFqn) {
286
+ return byFqn;
287
+ }
288
+
289
+ // Fallback to simple name lookup
290
+ const nodes = this.getIndexes().byName.get(name) ?? [];
291
+ return nodes.find(isBoundedContext);
292
+ }
293
+
294
+ bc(name: string): BoundedContext | undefined {
295
+ return this.boundedContext(name);
296
+ }
297
+
298
+ team(name: string): Team | undefined {
299
+ const nodes = this.getIndexes().byName.get(name) ?? [];
300
+ return nodes.find(isTeam);
301
+ }
302
+
303
+ fqn(node: AstNode): string {
304
+ if ('name' in node && typeof node.name === 'string' && node.$container) {
305
+ const container = node.$container;
306
+ if (isModel(container) || isNamespaceDeclaration(container)) {
307
+ return this.fqnProvider.getQualifiedName(container, node.name);
308
+ }
309
+ }
310
+ return '';
311
+ }
312
+
313
+ /**
314
+ * Creates a RelationshipView from a Relationship AST node.
315
+ * Resolves 'this' references to the containing BoundedContext.
316
+ */
317
+ private createRelationshipView(
318
+ rel: Relationship,
319
+ containingBc: BoundedContext | undefined,
320
+ source: 'BoundedContext' | 'ContextMap'
321
+ ): RelationshipView | undefined {
322
+ const left = this.resolveContextRef(rel.left, containingBc);
323
+ const right = this.resolveContextRef(rel.right, containingBc);
324
+
325
+ if (!left || !right) {
326
+ return undefined;
327
+ }
328
+
329
+ return {
330
+ left,
331
+ right,
332
+ arrow: rel.arrow,
333
+ leftPatterns: rel.leftPatterns,
334
+ rightPatterns: rel.rightPatterns,
335
+ type: rel.type,
336
+ inferredType: this.inferRelationshipType(rel),
337
+ source,
338
+ astNode: rel,
339
+ };
340
+ }
341
+
342
+ /**
343
+ * Resolves a BoundedContextRef to a BoundedContext.
344
+ * Handles 'this' references by using the containing BoundedContext.
345
+ */
346
+ private resolveContextRef(
347
+ ref: BoundedContextRef,
348
+ containingBc: BoundedContext | undefined
349
+ ): BoundedContext | undefined {
350
+ if (isThisRef(ref)) {
351
+ return containingBc;
352
+ }
353
+ return ref.link?.ref;
354
+ }
355
+
356
+ /**
357
+ * Infers relationship type from integration patterns.
358
+ * Simple heuristic based on common DDD pattern combinations.
359
+ */
360
+ private inferRelationshipType(rel: Relationship): string | undefined {
361
+ const leftPatterns = rel.leftPatterns;
362
+ const rightPatterns = rel.rightPatterns;
363
+
364
+ // Partnership: Bidirectional with P or SK
365
+ if (rel.arrow === '<->' && (leftPatterns.includes('P') || leftPatterns.includes('SK'))) {
366
+ return 'Partnership';
367
+ }
368
+
369
+ // Shared Kernel: SK pattern
370
+ if (leftPatterns.includes('SK') || rightPatterns.includes('SK')) {
371
+ return 'SharedKernel';
372
+ }
373
+
374
+ // Customer-Supplier: OHS + CF
375
+ if (leftPatterns.includes('OHS') && rightPatterns.includes('CF')) {
376
+ return 'CustomerSupplier';
377
+ }
378
+
379
+ // Upstream-Downstream: directional arrow
380
+ if (rel.arrow === '->') {
381
+ return 'UpstreamDownstream';
382
+ }
383
+
384
+ // Separate Ways
385
+ if (rel.arrow === '><') {
386
+ return 'SeparateWays';
387
+ }
388
+
389
+ return undefined;
390
+ }
391
+ }
392
+
393
+ /**
394
+ * Base implementation of QueryBuilder with lazy iteration.
395
+ * Predicates are chained and only evaluated during iteration.
396
+ */
397
+ class QueryBuilderImpl<T> implements QueryBuilder<T> {
398
+ protected readonly sourceItems: Iterable<T>;
399
+ protected readonly predicateList: Array<(item: T) => boolean>;
400
+
401
+ constructor(
402
+ items: Iterable<T>,
403
+ protected readonly fqnProvider: QualifiedNameProvider,
404
+ predicates: Array<(item: T) => boolean> = []
405
+ ) {
406
+ this.sourceItems = items;
407
+ this.predicateList = predicates;
408
+ }
409
+
410
+ where(predicate: (item: T) => boolean): QueryBuilder<T> {
411
+ return new QueryBuilderImpl(
412
+ this.sourceItems,
413
+ this.fqnProvider,
414
+ [...this.predicateList, predicate]
415
+ );
416
+ }
417
+
418
+ withName(pattern: string | RegExp): QueryBuilder<T> {
419
+ const regex = typeof pattern === 'string' ? new RegExp(`^${escapeRegex(pattern)}$`) : pattern;
420
+ return this.where((item: T) => {
421
+ const node = item as unknown as AstNode;
422
+ if ('name' in node && typeof node.name === 'string') {
423
+ return regex.test(node.name);
424
+ }
425
+ return false;
426
+ });
427
+ }
428
+
429
+ withFqn(pattern: string | RegExp): QueryBuilder<T> {
430
+ const regex = typeof pattern === 'string' ? new RegExp(`^${escapeRegex(pattern)}$`) : pattern;
431
+ return this.where((item: T) => {
432
+ const node = item as unknown as AstNode;
433
+ if ('name' in node && typeof node.name === 'string' && node.$container) {
434
+ const container = node.$container;
435
+ if (isModel(container) || isNamespaceDeclaration(container)) {
436
+ const fqn = this.fqnProvider.getQualifiedName(container, node.name);
437
+ return regex.test(fqn);
438
+ }
439
+ }
440
+ return false;
441
+ });
442
+ }
443
+
444
+ first(): T | undefined {
445
+ for (const item of this) {
446
+ return item;
447
+ }
448
+ return undefined;
449
+ }
450
+
451
+ toArray(): T[] {
452
+ return Array.from(this);
453
+ }
454
+
455
+ count(): number {
456
+ let count = 0;
457
+ for (const _ of this) {
458
+ count++;
459
+ }
460
+ return count;
461
+ }
462
+
463
+ *[Symbol.iterator](): Iterator<T> {
464
+ for (const item of this.sourceItems) {
465
+ if (this.predicateList.every(p => p(item))) {
466
+ yield item;
467
+ }
468
+ }
469
+ }
470
+ }
471
+
472
+ /**
473
+ * BoundedContext-specific QueryBuilder with domain filters.
474
+ * Supports indexed lookups for performance when filters allow.
475
+ */
476
+ class BcQueryBuilderImpl extends QueryBuilderImpl<BoundedContext> implements BcQueryBuilder {
477
+ constructor(
478
+ items: Iterable<BoundedContext>,
479
+ fqnProvider: QualifiedNameProvider,
480
+ private readonly indexes: ModelIndexes,
481
+ predicates: Array<(item: BoundedContext) => boolean> = []
482
+ ) {
483
+ super(items, fqnProvider, predicates);
484
+ }
485
+
486
+ override where(predicate: (item: BoundedContext) => boolean): BcQueryBuilder {
487
+ return new BcQueryBuilderImpl(
488
+ this.sourceItems,
489
+ this.fqnProvider,
490
+ this.indexes,
491
+ [...this.predicateList, predicate]
492
+ );
493
+ }
494
+
495
+ override withName(pattern: string | RegExp): BcQueryBuilder {
496
+ return super.withName(pattern) as BcQueryBuilder;
497
+ }
498
+
499
+ override withFqn(pattern: string | RegExp): BcQueryBuilder {
500
+ return super.withFqn(pattern) as BcQueryBuilder;
501
+ }
502
+
503
+ inDomain(domain: string | Domain): BcQueryBuilder {
504
+ const domainName = typeof domain === 'string' ? domain : domain.name;
505
+ return this.where(bc => bc.domain?.ref?.name === domainName);
506
+ }
507
+
508
+ withTeam(team: string | Team): BcQueryBuilder {
509
+ const teamName = typeof team === 'string' ? team : team.name;
510
+
511
+ // Use index for initial filtering if no predicates yet
512
+ if (this.predicateList.length === 0) {
513
+ const indexed = this.indexes.byTeam.get(teamName) ?? [];
514
+ return new BcQueryBuilderImpl(indexed, this.fqnProvider, this.indexes);
515
+ }
516
+
517
+ // Add predicate to existing chain
518
+ return this.where(bc => effectiveTeam(bc)?.name === teamName);
519
+ }
520
+
521
+ withClassification(classification: string | Classification): BcQueryBuilder {
522
+ const classificationName = typeof classification === 'string' ? classification : classification.name;
523
+
524
+ // Use index for initial filtering if no predicates yet
525
+ if (this.predicateList.length === 0) {
526
+ const indexed = this.indexes.byClassification.get(classificationName) ?? [];
527
+ return new BcQueryBuilderImpl(indexed, this.fqnProvider, this.indexes);
528
+ }
529
+
530
+ // Add predicate to existing chain
531
+ return this.where(bc => effectiveClassification(bc)?.name === classificationName);
532
+ }
533
+
534
+ withMetadata(key: string, value?: string): BcQueryBuilder {
535
+ // Use index for initial filtering if no predicates yet and no value specified
536
+ if (this.predicateList.length === 0 && value === undefined) {
537
+ const indexed = this.indexes.byMetadataKey.get(key) ?? [];
538
+ return new BcQueryBuilderImpl(indexed, this.fqnProvider, this.indexes);
539
+ }
540
+
541
+ // Add predicate to existing chain
542
+ return this.where(bc => {
543
+ const metadata = metadataAsMap(bc);
544
+ const metaValue = metadata.get(key);
545
+ if (metaValue === undefined) {
546
+ return false;
547
+ }
548
+ return value === undefined || metaValue === value;
549
+ });
550
+ }
551
+ }
552
+
553
+ /**
554
+ * Escapes special regex characters in a string.
555
+ */
556
+ function escapeRegex(str: string): string {
557
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
558
+ }
559
+
560
+ /**
561
+ * Augments BoundedContext instances with SDK-resolved properties.
562
+ * Called during model loading to enrich the AST.
563
+ *
564
+ * Properties use natural names (not sdk* prefix) per PRS:
565
+ * "Properties are discoverable in IDE autocomplete. The SDK enriches the AST
566
+ * during load, so `bc.classification` just works—no imports needed."
567
+ *
568
+ * Note: We use getters to avoid computing values until accessed.
569
+ * Note: We use Object.defineProperty to avoid modifying the original interface.
570
+ *
571
+ * @param bc - BoundedContext to augment
572
+ */
573
+ export function augmentBoundedContext(bc: BoundedContext): void {
574
+ const fqnProvider = new QualifiedNameProvider();
575
+
576
+ // Define computed properties with getters for lazy evaluation
577
+ // Only include properties that add value beyond direct AST access:
578
+ // - effectiveClassification/effectiveTeam: array precedence resolution
579
+ // - metadataMap: array to Map conversion
580
+ // - fqn: computed qualified name
581
+ // - helper methods: hasClassification, hasTeam, hasMetadata
582
+ Object.defineProperties(bc, {
583
+ effectiveClassification: {
584
+ get: () => effectiveClassification(bc),
585
+ enumerable: true,
586
+ configurable: true,
587
+ },
588
+ effectiveTeam: {
589
+ get: () => effectiveTeam(bc),
590
+ enumerable: true,
591
+ configurable: true,
592
+ },
593
+ metadataMap: {
594
+ get: () => metadataAsMap(bc),
595
+ enumerable: true,
596
+ configurable: true,
597
+ },
598
+ fqn: {
599
+ get: () => {
600
+ if (bc.$container && (isModel(bc.$container) || isNamespaceDeclaration(bc.$container))) {
601
+ return fqnProvider.getQualifiedName(bc.$container, bc.name);
602
+ }
603
+ return bc.name;
604
+ },
605
+ enumerable: true,
606
+ configurable: true,
607
+ },
608
+ // Helper methods
609
+ hasClassification: {
610
+ value: (name: string | Classification): boolean => {
611
+ const classification = effectiveClassification(bc);
612
+ if (!classification) return false;
613
+ const targetName = typeof name === 'string' ? name : name?.name;
614
+ if (!targetName) return false;
615
+ return classification.name === targetName;
616
+ },
617
+ enumerable: false,
618
+ configurable: true,
619
+ },
620
+ hasTeam: {
621
+ value: (name: string | Team): boolean => {
622
+ const team = effectiveTeam(bc);
623
+ if (!team) return false;
624
+ const targetName = typeof name === 'string' ? name : name?.name;
625
+ if (!targetName) return false;
626
+ return team.name === targetName;
627
+ },
628
+ enumerable: false,
629
+ configurable: true,
630
+ },
631
+ hasMetadata: {
632
+ value: (key: string, value?: string): boolean => {
633
+ const metadata = metadataAsMap(bc);
634
+ const metaValue = metadata.get(key);
635
+ if (metaValue === undefined) return false;
636
+ return value === undefined || metaValue === value;
637
+ },
638
+ enumerable: false,
639
+ configurable: true,
640
+ },
641
+ });
642
+ }
643
+
644
+ /**
645
+ * Augments Domain instances with SDK-resolved properties.
646
+ * Called during model loading to enrich the AST.
647
+ *
648
+ * Only includes properties that add value beyond direct AST access:
649
+ * - fqn: computed qualified name
650
+ * - hasType: helper method
651
+ *
652
+ * Direct access (no augmentation needed):
653
+ * - domain.description
654
+ * - domain.vision
655
+ * - domain.type?.ref
656
+ *
657
+ * @param domain - Domain to augment
658
+ */
659
+ export function augmentDomain(domain: Domain): void {
660
+ const fqnProvider = new QualifiedNameProvider();
661
+
662
+ Object.defineProperties(domain, {
663
+ fqn: {
664
+ get: () => {
665
+ if (domain.$container && (isModel(domain.$container) || isNamespaceDeclaration(domain.$container))) {
666
+ return fqnProvider.getQualifiedName(domain.$container, domain.name);
667
+ }
668
+ return domain.name;
669
+ },
670
+ enumerable: true,
671
+ configurable: true,
672
+ },
673
+ // Helper methods
674
+ hasType: {
675
+ value: (name: string | Classification): boolean => {
676
+ const type = domain.type?.ref;
677
+ if (!type) return false;
678
+ const targetName = typeof name === 'string' ? name : name?.name;
679
+ if (!targetName) return false;
680
+ return type.name === targetName;
681
+ },
682
+ enumerable: false,
683
+ configurable: true,
684
+ },
685
+ });
686
+ }
687
+
688
+ /**
689
+ * Augments Relationship instances with SDK helper methods.
690
+ * Called during model loading to enrich the AST.
691
+ *
692
+ * @param rel - Relationship to augment
693
+ * @param containingBc - BoundedContext containing this relationship (for 'this' resolution)
694
+ */
695
+ export function augmentRelationship(rel: Relationship, containingBc?: BoundedContext): void {
696
+ Object.defineProperties(rel, {
697
+ leftContextName: {
698
+ get: () => {
699
+ if (isThisRef(rel.left)) {
700
+ return containingBc?.name ?? 'this';
701
+ }
702
+ return rel.left.link?.ref?.name ?? '';
703
+ },
704
+ enumerable: true,
705
+ configurable: true,
706
+ },
707
+ rightContextName: {
708
+ get: () => {
709
+ if (isThisRef(rel.right)) {
710
+ return containingBc?.name ?? 'this';
711
+ }
712
+ return rel.right.link?.ref?.name ?? '';
713
+ },
714
+ enumerable: true,
715
+ configurable: true,
716
+ },
717
+ isBidirectional: {
718
+ get: () => rel.arrow === '<->',
719
+ enumerable: true,
720
+ configurable: true,
721
+ },
722
+ // Helper methods for pattern matching (type-safe, no magic strings)
723
+ hasPattern: {
724
+ value: (pattern: string): boolean => {
725
+ return rel.leftPatterns.some(p => matchesPattern(p, pattern)) ||
726
+ rel.rightPatterns.some(p => matchesPattern(p, pattern));
727
+ },
728
+ enumerable: false,
729
+ configurable: true,
730
+ },
731
+ hasLeftPattern: {
732
+ value: (pattern: string): boolean => {
733
+ return rel.leftPatterns.some(p => matchesPattern(p, pattern));
734
+ },
735
+ enumerable: false,
736
+ configurable: true,
737
+ },
738
+ hasRightPattern: {
739
+ value: (pattern: string): boolean => {
740
+ return rel.rightPatterns.some(p => matchesPattern(p, pattern));
741
+ },
742
+ enumerable: false,
743
+ configurable: true,
744
+ },
745
+ isUpstream: {
746
+ value: (side: 'left' | 'right'): boolean => {
747
+ const patterns = side === 'left' ? rel.leftPatterns : rel.rightPatterns;
748
+ return patterns.some(p => isUpstreamPattern(p));
749
+ },
750
+ enumerable: false,
751
+ configurable: true,
752
+ },
753
+ isDownstream: {
754
+ value: (side: 'left' | 'right'): boolean => {
755
+ const patterns = side === 'left' ? rel.leftPatterns : rel.rightPatterns;
756
+ return patterns.some(p => isDownstreamPattern(p));
757
+ },
758
+ enumerable: false,
759
+ configurable: true,
760
+ },
761
+ });
762
+ }
763
+
764
+ /**
765
+ * Internal implementation of model augmentation.
766
+ * Called by ensureAugmented() which tracks augmentation state.
767
+ *
768
+ * @param model - Root Model node to augment
769
+ */
770
+ function augmentModelInternal(model: Model): void {
771
+ for (const node of AstUtils.streamAllContents(model)) {
772
+ if (isBoundedContext(node)) {
773
+ augmentBoundedContext(node);
774
+ // Augment relationships inside this bounded context
775
+ for (const rel of node.relationships) {
776
+ augmentRelationship(rel, node);
777
+ }
778
+ } else if (isDomain(node)) {
779
+ augmentDomain(node);
780
+ } else if (isContextMap(node)) {
781
+ // Augment relationships in context maps (no containing BC)
782
+ for (const rel of node.relationships) {
783
+ augmentRelationship(rel, undefined);
784
+ }
785
+ }
786
+ }
787
+ }
788
+
789
+ /**
790
+ * Augments all AST nodes in a model with SDK-resolved properties.
791
+ *
792
+ * This function walks the entire AST and adds lazy getters for resolved
793
+ * properties like `effectiveClassification`, `effectiveTeam`, etc.
794
+ *
795
+ * Idempotent - safe to call multiple times on the same model.
796
+ * Automatically called by `fromModel()`, `fromDocument()`, and `fromServices()`.
797
+ *
798
+ * @param model - Root Model node to augment
799
+ */
800
+ export function augmentModel(model: Model): void {
801
+ ensureAugmented(model);
802
+ }