@domainlang/language 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/out/ast-augmentation.d.ts +7 -2
  2. package/out/diagram/context-map-diagram-generator.d.ts +72 -0
  3. package/out/diagram/context-map-diagram-generator.js +405 -0
  4. package/out/diagram/context-map-diagram-generator.js.map +1 -0
  5. package/out/diagram/context-map-layout-configurator.d.ts +15 -0
  6. package/out/diagram/context-map-layout-configurator.js +39 -0
  7. package/out/diagram/context-map-layout-configurator.js.map +1 -0
  8. package/out/diagram/elk-layout-factory.d.ts +43 -0
  9. package/out/diagram/elk-layout-factory.js +64 -0
  10. package/out/diagram/elk-layout-factory.js.map +1 -0
  11. package/out/domain-lang-module.d.ts +7 -0
  12. package/out/domain-lang-module.js +11 -2
  13. package/out/domain-lang-module.js.map +1 -1
  14. package/out/generated/ast.d.ts +323 -51
  15. package/out/generated/ast.js +194 -33
  16. package/out/generated/ast.js.map +1 -1
  17. package/out/generated/grammar.js +418 -172
  18. package/out/generated/grammar.js.map +1 -1
  19. package/out/index.d.ts +3 -0
  20. package/out/index.js +4 -0
  21. package/out/index.js.map +1 -1
  22. package/out/lsp/domain-lang-code-lens-provider.d.ts +8 -0
  23. package/out/lsp/domain-lang-code-lens-provider.js +48 -0
  24. package/out/lsp/domain-lang-code-lens-provider.js.map +1 -0
  25. package/out/lsp/domain-lang-completion.js +39 -15
  26. package/out/lsp/domain-lang-completion.js.map +1 -1
  27. package/out/lsp/domain-lang-document-symbol-provider.js +5 -5
  28. package/out/lsp/domain-lang-document-symbol-provider.js.map +1 -1
  29. package/out/lsp/domain-lang-formatter.js +32 -0
  30. package/out/lsp/domain-lang-formatter.js.map +1 -1
  31. package/out/lsp/domain-lang-index-manager.d.ts +2 -3
  32. package/out/lsp/domain-lang-index-manager.js +5 -8
  33. package/out/lsp/domain-lang-index-manager.js.map +1 -1
  34. package/out/lsp/domain-lang-workspace-manager.d.ts +1 -1
  35. package/out/lsp/domain-lang-workspace-manager.js +2 -26
  36. package/out/lsp/domain-lang-workspace-manager.js.map +1 -1
  37. package/out/lsp/explain.js +9 -3
  38. package/out/lsp/explain.js.map +1 -1
  39. package/out/lsp/hover/domain-lang-hover.js +13 -11
  40. package/out/lsp/hover/domain-lang-hover.js.map +1 -1
  41. package/out/lsp/hover/domain-lang-keywords.js +29 -26
  42. package/out/lsp/hover/domain-lang-keywords.js.map +1 -1
  43. package/out/lsp/tool-handlers.js +63 -57
  44. package/out/lsp/tool-handlers.js.map +1 -1
  45. package/out/sdk/ast-augmentation.d.ts +29 -21
  46. package/out/sdk/ast-augmentation.js +11 -7
  47. package/out/sdk/ast-augmentation.js.map +1 -1
  48. package/out/sdk/index.d.ts +2 -2
  49. package/out/sdk/index.js +1 -1
  50. package/out/sdk/index.js.map +1 -1
  51. package/out/sdk/loader-node.js +2 -2
  52. package/out/sdk/loader-node.js.map +1 -1
  53. package/out/sdk/patterns.d.ts +50 -61
  54. package/out/sdk/patterns.js +92 -62
  55. package/out/sdk/patterns.js.map +1 -1
  56. package/out/sdk/query.js +54 -43
  57. package/out/sdk/query.js.map +1 -1
  58. package/out/sdk/serializers.js +20 -7
  59. package/out/sdk/serializers.js.map +1 -1
  60. package/out/sdk/types.d.ts +87 -18
  61. package/out/sdk/types.js.map +1 -1
  62. package/out/sdk/validator.js +48 -64
  63. package/out/sdk/validator.js.map +1 -1
  64. package/out/services/performance-optimizer.d.ts +3 -3
  65. package/out/services/performance-optimizer.js +1 -3
  66. package/out/services/performance-optimizer.js.map +1 -1
  67. package/out/services/relationship-inference.d.ts +4 -4
  68. package/out/services/relationship-inference.js +34 -46
  69. package/out/services/relationship-inference.js.map +1 -1
  70. package/out/syntaxes/domain-lang.monarch.js +1 -1
  71. package/out/syntaxes/domain-lang.monarch.js.map +1 -1
  72. package/out/utils/import-utils.d.ts +6 -20
  73. package/out/utils/import-utils.js +3 -63
  74. package/out/utils/import-utils.js.map +1 -1
  75. package/out/validation/constants.d.ts +23 -6
  76. package/out/validation/constants.js +24 -7
  77. package/out/validation/constants.js.map +1 -1
  78. package/out/validation/maps.js +10 -4
  79. package/out/validation/maps.js.map +1 -1
  80. package/out/validation/relationships.d.ts +4 -8
  81. package/out/validation/relationships.js +96 -48
  82. package/out/validation/relationships.js.map +1 -1
  83. package/package.json +5 -2
  84. package/src/ast-augmentation.ts +7 -2
  85. package/src/diagram/context-map-diagram-generator.ts +513 -0
  86. package/src/diagram/context-map-layout-configurator.ts +43 -0
  87. package/src/diagram/elk-layout-factory.ts +83 -0
  88. package/src/domain-lang-module.ts +19 -2
  89. package/src/domain-lang.langium +62 -26
  90. package/src/generated/ast.ts +413 -63
  91. package/src/generated/grammar.ts +418 -172
  92. package/src/index.ts +5 -0
  93. package/src/lsp/domain-lang-code-lens-provider.ts +54 -0
  94. package/src/lsp/domain-lang-completion.ts +42 -15
  95. package/src/lsp/domain-lang-document-symbol-provider.ts +5 -5
  96. package/src/lsp/domain-lang-formatter.ts +34 -0
  97. package/src/lsp/domain-lang-index-manager.ts +6 -9
  98. package/src/lsp/domain-lang-workspace-manager.ts +3 -29
  99. package/src/lsp/explain.ts +10 -2
  100. package/src/lsp/hover/domain-lang-hover.ts +10 -8
  101. package/src/lsp/hover/domain-lang-keywords.ts +27 -24
  102. package/src/lsp/tool-handlers.ts +61 -47
  103. package/src/sdk/ast-augmentation.ts +30 -21
  104. package/src/sdk/index.ts +11 -1
  105. package/src/sdk/loader-node.ts +2 -2
  106. package/src/sdk/patterns.ts +114 -76
  107. package/src/sdk/query.ts +57 -48
  108. package/src/sdk/serializers.ts +20 -7
  109. package/src/sdk/types.ts +92 -17
  110. package/src/sdk/validator.ts +52 -69
  111. package/src/services/performance-optimizer.ts +4 -6
  112. package/src/services/relationship-inference.ts +43 -54
  113. package/src/utils/import-utils.ts +9 -74
  114. package/src/validation/constants.ts +32 -9
  115. package/src/validation/maps.ts +12 -4
  116. package/src/validation/relationships.ts +150 -71
@@ -46,15 +46,19 @@
46
46
  * - `fqn` - Computed fully qualified name
47
47
  * - `hasType(name)` - Check type matches
48
48
  *
49
- * **Properties added to Relationship:**
50
- * - `hasPattern(pattern)` - Check if pattern exists on either side
51
- * - `hasLeftPattern(pattern)` - Check left patterns
52
- * - `hasRightPattern(pattern)` - Check right patterns
49
+ * **Properties added to DirectionalRelationship:**
50
+ * - `isBidirectional` - Check if relationship is bidirectional (`<->`)
51
+ * - `leftContextName` - Resolved name of left context (handles `this`)
52
+ * - `rightContextName` - Resolved name of right context (handles `this`)
53
+ * - `hasPattern(patternType)` - Check if pattern exists on either side
54
+ * - `hasLeftPattern(patternType)` - Check left patterns
55
+ * - `hasRightPattern(patternType)` - Check right patterns
53
56
  * - `isUpstream(side)` - Check if side is upstream
54
57
  * - `isDownstream(side)` - Check if side is downstream
55
- * - `isBidirectional` - Check if relationship is bidirectional
56
- * - `leftContextName` - Resolved name of left context
57
- * - `rightContextName` - Resolved name of right context
58
+ *
59
+ * **Properties added to SymmetricRelationship:**
60
+ * - `leftContextName` - Resolved name of left context (handles `this`)
61
+ * - `rightContextName` - Resolved name of right context (handles `this`)
58
62
  *
59
63
  * @module sdk/ast-augmentation
60
64
  */
@@ -109,9 +113,9 @@ declare module '../generated/ast.js' {
109
113
  }
110
114
 
111
115
  /**
112
- * Augmented Relationship with SDK helper methods.
116
+ * Augmented DirectionalRelationship with SDK helper methods.
113
117
  */
114
- interface Relationship {
118
+ interface DirectionalRelationship {
115
119
  /** Resolved name of left context (handles 'this') */
116
120
  readonly leftContextName: string;
117
121
  /** Resolved name of right context (handles 'this') */
@@ -120,36 +124,41 @@ declare module '../generated/ast.js' {
120
124
  readonly isBidirectional: boolean;
121
125
 
122
126
  /**
123
- * Checks if the relationship has a specific integration pattern on either side.
124
- * Accepts both abbreviations (SK, ACL) and full names (SharedKernel, AntiCorruptionLayer).
125
- * @param pattern - Pattern abbreviation or full name
127
+ * Checks if the relationship has a specific side pattern on either side.
128
+ * Accepts pattern $type names like 'OpenHostService', 'Conformist', etc.
126
129
  */
127
- hasPattern(pattern: import('./patterns.js').IntegrationPattern | string): boolean;
130
+ hasPattern(patternType: string): boolean;
128
131
 
129
132
  /**
130
- * Checks if the left side has a specific integration pattern.
131
- * @param pattern - Pattern abbreviation or full name
133
+ * Checks if the left side has a specific side pattern.
132
134
  */
133
- hasLeftPattern(pattern: import('./patterns.js').IntegrationPattern | string): boolean;
135
+ hasLeftPattern(patternType: string): boolean;
134
136
 
135
137
  /**
136
- * Checks if the right side has a specific integration pattern.
137
- * @param pattern - Pattern abbreviation or full name
138
+ * Checks if the right side has a specific side pattern.
138
139
  */
139
- hasRightPattern(pattern: import('./patterns.js').IntegrationPattern | string): boolean;
140
+ hasRightPattern(patternType: string): boolean;
140
141
 
141
142
  /**
142
143
  * Checks if the specified side is upstream (provider) in this relationship.
143
- * @param side - 'left' or 'right'
144
144
  */
145
145
  isUpstream(side: 'left' | 'right'): boolean;
146
146
 
147
147
  /**
148
148
  * Checks if the specified side is downstream (consumer) in this relationship.
149
- * @param side - 'left' or 'right'
150
149
  */
151
150
  isDownstream(side: 'left' | 'right'): boolean;
152
151
  }
152
+
153
+ /**
154
+ * Augmented SymmetricRelationship with SDK helper methods.
155
+ */
156
+ interface SymmetricRelationship {
157
+ /** Resolved name of left context (handles 'this') */
158
+ readonly leftContextName: string;
159
+ /** Resolved name of right context (handles 'this') */
160
+ readonly rightContextName: string;
161
+ }
153
162
  }
154
163
 
155
164
  // Export nothing - this file is purely for type augmentation
package/src/sdk/index.ts CHANGED
@@ -116,14 +116,19 @@ export { fromModel, fromDocument, fromServices, augmentModel } from './query.js'
116
116
  export {
117
117
  Pattern,
118
118
  PatternFullName,
119
+ PatternAbbreviation,
119
120
  PatternAliases,
120
121
  matchesPattern,
121
122
  isUpstreamPattern,
122
123
  isDownstreamPattern,
123
124
  isMutualPattern,
125
+ isUpstreamSidePattern,
126
+ isDownstreamSidePattern,
127
+ isBBoMSidePattern,
128
+ getPatternAbbreviation,
124
129
  UpstreamPatterns,
125
130
  DownstreamPatterns,
126
- MutualPatterns,
131
+ SymmetricPatterns,
127
132
  } from './patterns.js';
128
133
 
129
134
  export type { IntegrationPattern } from './patterns.js';
@@ -140,6 +145,11 @@ export type {
140
145
  LoadOptions,
141
146
  BcQueryBuilder,
142
147
  RelationshipView,
148
+ RelationshipSide,
149
+ DirectionalRelationshipView,
150
+ DirectionalKind,
151
+ SymmetricRelationshipView,
152
+ SymmetricKind,
143
153
  } from './types.js';
144
154
 
145
155
  // Serializers for tool responses (browser-safe)
@@ -62,7 +62,7 @@ export async function loadModel(
62
62
  options?: LoadOptions
63
63
  ): Promise<QueryContext> {
64
64
  // Resolve absolute path
65
- const path = await import('path');
65
+ const path = await import('node:path');
66
66
  const absolutePath = path.isAbsolute(entryFile)
67
67
  ? entryFile
68
68
  : path.resolve(options?.workspaceDir ?? process.cwd(), entryFile);
@@ -82,7 +82,7 @@ export async function loadModel(
82
82
  }
83
83
 
84
84
  // Read file content and create document
85
- const fs = await import('fs/promises');
85
+ const fs = await import('node:fs/promises');
86
86
  const fileContent = await fs.readFile(absolutePath, 'utf-8');
87
87
  const uri = URI.file(absolutePath);
88
88
 
@@ -1,147 +1,185 @@
1
- /**
2
- * Integration Patterns - Type-safe constants for DDD integration patterns.
3
- *
4
- * Use these constants instead of magic strings when checking relationship patterns.
5
- *
6
- * @example
7
- * ```typescript
8
- * import { Pattern } from '../sdk/patterns.js';
9
- *
10
- * // Instead of: hasPattern('SK') or hasPattern('SharedKernel')
11
- * // Use:
12
- * if (relationship.hasPattern(Pattern.SharedKernel)) {
13
- * // ...
14
- * }
15
- * ```
16
- */
1
+ import type { SidePattern, SymmetricPattern } from '../generated/ast.js';
2
+ import {
3
+ isOpenHostService,
4
+ isPublishedLanguage,
5
+ isConformist,
6
+ isAntiCorruptionLayer,
7
+ isSupplier,
8
+ isCustomer,
9
+ isBigBallOfMud,
10
+ isSharedKernel,
11
+ isPartnership,
12
+ isSeparateWays,
13
+ } from '../generated/ast.js';
17
14
 
18
15
  /**
19
- * Integration pattern abbreviations as used in the grammar.
20
- * These are the canonical abbreviations recognized by DomainLang.
16
+ * Pattern constants for programmatic use.
17
+ * Values match the AST $type names.
21
18
  */
22
19
  export const Pattern = {
23
- // Upstream patterns (provider side)
24
- /** Open Host Service - exposes a clean API for consumers */
25
- OHS: 'OHS',
26
- /** Published Language - shared data format/protocol */
27
- PL: 'PL',
28
-
29
- // Downstream patterns (consumer side)
30
- /** Conformist - accepts upstream model without translation */
31
- CF: 'CF',
32
- /** Anti-Corruption Layer - translates upstream model */
33
- ACL: 'ACL',
34
-
35
- // Mutual patterns (both sides)
36
- /** Shared Kernel - shared code/model ownership */
37
- SK: 'SK',
38
- /** Partnership - coordinated development */
39
- P: 'P',
40
-
41
- // Relationship types
42
- /** Customer/Supplier - negotiated contract */
43
- CustomerSupplier: 'Customer/Supplier',
44
- /** Separate Ways - no integration */
45
- SeparateWays: 'Separate Ways',
46
- /** Big Ball of Mud - legacy or unstructured */
47
- BigBallOfMud: 'Big Ball of Mud',
20
+ // Side patterns (directional)
21
+ OHS: 'OpenHostService',
22
+ PL: 'PublishedLanguage',
23
+ CF: 'Conformist',
24
+ ACL: 'AntiCorruptionLayer',
25
+ S: 'Supplier',
26
+ C: 'Customer',
27
+ BBoM: 'BigBallOfMud',
28
+ // Symmetric patterns
29
+ SK: 'SharedKernel',
30
+ P: 'Partnership',
31
+ SW: 'SeparateWays',
48
32
  } as const;
49
33
 
50
34
  /**
51
- * Full names for integration patterns.
52
- * Used when patterns are spelled out in documentation blocks.
35
+ * Mapping from short abbreviation to full $type name.
53
36
  */
54
- export const PatternFullName = {
37
+ export const PatternFullName: Record<string, string> = {
55
38
  OHS: 'OpenHostService',
56
39
  PL: 'PublishedLanguage',
57
40
  CF: 'Conformist',
58
41
  ACL: 'AntiCorruptionLayer',
42
+ S: 'Supplier',
43
+ C: 'Customer',
44
+ BBoM: 'BigBallOfMud',
59
45
  SK: 'SharedKernel',
60
46
  P: 'Partnership',
61
- } as const;
47
+ SW: 'SeparateWays',
48
+ };
49
+
50
+ /**
51
+ * Mapping from $type name to short abbreviation.
52
+ */
53
+ export const PatternAbbreviation: Record<string, string> = {
54
+ OpenHostService: 'OHS',
55
+ PublishedLanguage: 'PL',
56
+ Conformist: 'CF',
57
+ AntiCorruptionLayer: 'ACL',
58
+ Supplier: 'S',
59
+ Customer: 'C',
60
+ BigBallOfMud: 'BBoM',
61
+ SharedKernel: 'SK',
62
+ Partnership: 'P',
63
+ SeparateWays: 'SW',
64
+ };
62
65
 
63
66
  /**
64
- * Mapping from abbreviations to full names and vice versa.
67
+ * All short+long forms that map to a given canonical $type name.
65
68
  */
66
69
  export const PatternAliases: Record<string, readonly string[]> = {
67
- // Abbreviation -> [abbreviation, fullName]
68
70
  OHS: ['OHS', 'OpenHostService'],
69
71
  PL: ['PL', 'PublishedLanguage'],
70
72
  CF: ['CF', 'Conformist'],
71
73
  ACL: ['ACL', 'AntiCorruptionLayer'],
74
+ S: ['S', 'Supplier'],
75
+ C: ['C', 'Customer'],
76
+ BBoM: ['BBoM', 'BigBallOfMud'],
72
77
  SK: ['SK', 'SharedKernel'],
73
78
  P: ['P', 'Partnership'],
74
-
75
- // Full names map to same
79
+ SW: ['SW', 'SeparateWays'],
76
80
  OpenHostService: ['OHS', 'OpenHostService'],
77
81
  PublishedLanguage: ['PL', 'PublishedLanguage'],
78
82
  Conformist: ['CF', 'Conformist'],
79
83
  AntiCorruptionLayer: ['ACL', 'AntiCorruptionLayer'],
84
+ Supplier: ['S', 'Supplier'],
85
+ Customer: ['C', 'Customer'],
86
+ BigBallOfMud: ['BBoM', 'BigBallOfMud'],
80
87
  SharedKernel: ['SK', 'SharedKernel'],
81
88
  Partnership: ['P', 'Partnership'],
89
+ SeparateWays: ['SW', 'SeparateWays'],
82
90
  };
83
91
 
84
- /**
85
- * Type representing any valid integration pattern (abbreviation or full name).
86
- */
87
- export type IntegrationPattern =
88
- | typeof Pattern[keyof typeof Pattern]
89
- | typeof PatternFullName[keyof typeof PatternFullName];
92
+ /** Union of all pattern type names */
93
+ export type IntegrationPattern = typeof Pattern[keyof typeof Pattern];
90
94
 
91
95
  /**
92
- * Checks if a pattern string matches any of the expected aliases.
93
- * Handles both abbreviations and full names case-insensitively.
94
- *
95
- * @param actual - Pattern string from the AST
96
- * @param expected - Pattern to match (abbreviation or full name)
97
- * @returns true if patterns match
96
+ * Checks if a pattern name matches an expected pattern.
97
+ * Works with both $type names and short abbreviations.
98
98
  */
99
99
  export function matchesPattern(actual: string, expected: string): boolean {
100
100
  const normalizedActual = actual.trim();
101
101
  const aliases = PatternAliases[expected];
102
-
103
102
  if (aliases) {
104
103
  return aliases.some(alias =>
105
104
  alias.toLowerCase() === normalizedActual.toLowerCase()
106
105
  );
107
106
  }
108
-
109
- // Fallback: direct comparison
110
107
  return normalizedActual.toLowerCase() === expected.toLowerCase();
111
108
  }
112
109
 
110
+ /** Side patterns that belong on the upstream side */
111
+ export const UpstreamPatterns: readonly string[] = ['OpenHostService', 'PublishedLanguage', 'Supplier'];
112
+ /** Side patterns that belong on the downstream side */
113
+ export const DownstreamPatterns: readonly string[] = ['Conformist', 'AntiCorruptionLayer', 'Customer'];
114
+ /** Symmetric patterns (mutual) */
115
+ export const SymmetricPatterns: readonly string[] = ['SharedKernel', 'Partnership', 'SeparateWays'];
116
+
113
117
  /**
114
- * Patterns that are typically on the upstream (provider) side.
118
+ * Checks if a side pattern AST node is an upstream pattern.
115
119
  */
116
- export const UpstreamPatterns: readonly string[] = ['OHS', 'OpenHostService', 'PL', 'PublishedLanguage'];
120
+ export function isUpstreamSidePattern(pattern: SidePattern): boolean {
121
+ return isOpenHostService(pattern) || isPublishedLanguage(pattern) || isSupplier(pattern);
122
+ }
117
123
 
118
124
  /**
119
- * Patterns that are typically on the downstream (consumer) side.
125
+ * Checks if a side pattern AST node is a downstream pattern.
120
126
  */
121
- export const DownstreamPatterns: readonly string[] = ['CF', 'Conformist', 'ACL', 'AntiCorruptionLayer'];
127
+ export function isDownstreamSidePattern(pattern: SidePattern): boolean {
128
+ return isConformist(pattern) || isAntiCorruptionLayer(pattern) || isCustomer(pattern);
129
+ }
122
130
 
123
131
  /**
124
- * Patterns that require mutual/bidirectional relationships.
132
+ * Checks if a side pattern AST node is a Big Ball of Mud.
125
133
  */
126
- export const MutualPatterns: readonly string[] = ['SK', 'SharedKernel', 'P', 'Partnership'];
134
+ export function isBBoMSidePattern(pattern: SidePattern): boolean {
135
+ return isBigBallOfMud(pattern);
136
+ }
127
137
 
128
138
  /**
129
- * Checks if a pattern is typically an upstream pattern.
139
+ * Checks if a pattern string name is an upstream pattern.
130
140
  */
131
141
  export function isUpstreamPattern(pattern: string): boolean {
132
142
  return UpstreamPatterns.some(p => matchesPattern(pattern, p));
133
143
  }
134
144
 
135
145
  /**
136
- * Checks if a pattern is typically a downstream pattern.
146
+ * Checks if a pattern string name is a downstream pattern.
137
147
  */
138
148
  export function isDownstreamPattern(pattern: string): boolean {
139
149
  return DownstreamPatterns.some(p => matchesPattern(pattern, p));
140
150
  }
141
151
 
142
152
  /**
143
- * Checks if a pattern requires bidirectional relationships.
153
+ * Checks if a pattern string name is a mutual/symmetric pattern.
144
154
  */
145
155
  export function isMutualPattern(pattern: string): boolean {
146
- return MutualPatterns.some(p => matchesPattern(pattern, p));
156
+ return SymmetricPatterns.some(p => matchesPattern(pattern, p));
157
+ }
158
+
159
+ /**
160
+ * Gets the short abbreviation for a pattern $type name.
161
+ */
162
+ export function getPatternAbbreviation(typeName: string): string {
163
+ return PatternAbbreviation[typeName] ?? typeName;
164
+ }
165
+
166
+ /**
167
+ * Checks if a symmetric pattern is a Shared Kernel.
168
+ */
169
+ export function isSharedKernelPattern(pattern: SymmetricPattern): boolean {
170
+ return isSharedKernel(pattern);
171
+ }
172
+
173
+ /**
174
+ * Checks if a symmetric pattern is a Partnership.
175
+ */
176
+ export function isPartnershipPattern(pattern: SymmetricPattern): boolean {
177
+ return isPartnership(pattern);
178
+ }
179
+
180
+ /**
181
+ * Checks if a symmetric pattern is Separate Ways.
182
+ */
183
+ export function isSeparateWaysPattern(pattern: SymmetricPattern): boolean {
184
+ return isSeparateWays(pattern);
147
185
  }
package/src/sdk/query.ts CHANGED
@@ -21,12 +21,16 @@ import {
21
21
  isBoundedContext,
22
22
  isClassification,
23
23
  isContextMap,
24
+ isDirectionalRelationship,
24
25
  isDomain,
25
26
  isDomainMap,
26
27
  isModel,
27
28
  isNamespaceDeclaration,
29
+ isSymmetricRelationship,
28
30
  isTeam,
29
31
  isThisRef,
32
+ isSupplier,
33
+ isCustomer,
30
34
  } from '../generated/ast.js';
31
35
  import { QualifiedNameProvider } from '../lsp/domain-lang-naming.js';
32
36
  import type { DomainLangServices } from '../domain-lang-module.js';
@@ -39,6 +43,7 @@ import {
39
43
  import { isDownstreamPattern, isUpstreamPattern, matchesPattern } from './patterns.js';
40
44
  import type {
41
45
  BcQueryBuilder,
46
+ DirectionalKind,
42
47
  ModelIndexes,
43
48
  Query,
44
49
  QueryBuilder,
@@ -331,14 +336,39 @@ class QueryImpl implements Query {
331
336
  return undefined;
332
337
  }
333
338
 
339
+ if (isSymmetricRelationship(rel)) {
340
+ // Grammar invariant: either `arrow === '><'` (SeparateWays shorthand, pattern undefined)
341
+ // OR `pattern` is set (explicit [SK]/[P]/[SW] brackets). Never both; never neither
342
+ // (validation rejects a bare "A B" without pattern or arrow).
343
+ // The fallback to 'SeparateWays' correctly handles the `><` case.
344
+ return {
345
+ type: 'symmetric' as const,
346
+ kind: (rel.pattern?.$type ?? 'SeparateWays') as 'SharedKernel' | 'Partnership' | 'SeparateWays',
347
+ left: { context: left, patterns: [] },
348
+ right: { context: right, patterns: [] },
349
+ source,
350
+ astNode: rel,
351
+ };
352
+ }
353
+
354
+ const arrow = rel.arrow as '->' | '<-' | '<->';
355
+ const leftSide = { context: left, patterns: rel.leftPatterns };
356
+ const rightSide = { context: right, patterns: rel.rightPatterns };
357
+ const hasSupplier = rel.leftPatterns.some(isSupplier) || rel.rightPatterns.some(isSupplier);
358
+ const hasCustomer = rel.leftPatterns.some(isCustomer) || rel.rightPatterns.some(isCustomer);
359
+ const kind: DirectionalKind = arrow === '<->'
360
+ ? 'Bidirectional'
361
+ : (hasSupplier || hasCustomer) ? 'CustomerSupplier' : 'UpstreamDownstream';
362
+ const upstreamSide = arrow === '->' ? leftSide : arrow === '<-' ? rightSide : undefined;
363
+ const downstreamSide = arrow === '->' ? rightSide : arrow === '<-' ? leftSide : undefined;
334
364
  return {
335
- left,
336
- right,
337
- arrow: rel.arrow,
338
- leftPatterns: rel.leftPatterns,
339
- rightPatterns: rel.rightPatterns,
340
- type: rel.type,
341
- inferredType: this.inferRelationshipType(rel),
365
+ type: 'directional' as const,
366
+ kind,
367
+ arrow,
368
+ left: leftSide,
369
+ right: rightSide,
370
+ upstream: upstreamSide,
371
+ downstream: downstreamSide,
342
372
  source,
343
373
  astNode: rel,
344
374
  };
@@ -358,41 +388,6 @@ class QueryImpl implements Query {
358
388
  return ref.link?.ref;
359
389
  }
360
390
 
361
- /**
362
- * Infers relationship type from integration patterns.
363
- * Simple heuristic based on common DDD pattern combinations.
364
- */
365
- private inferRelationshipType(rel: Relationship): string | undefined {
366
- const leftPatterns = rel.leftPatterns;
367
- const rightPatterns = rel.rightPatterns;
368
-
369
- // Partnership: Bidirectional with P or SK
370
- if (rel.arrow === '<->' && (leftPatterns.includes('P') || leftPatterns.includes('SK'))) {
371
- return 'Partnership';
372
- }
373
-
374
- // Shared Kernel: SK pattern
375
- if (leftPatterns.includes('SK') || rightPatterns.includes('SK')) {
376
- return 'SharedKernel';
377
- }
378
-
379
- // Customer-Supplier: OHS + CF
380
- if (leftPatterns.includes('OHS') && rightPatterns.includes('CF')) {
381
- return 'CustomerSupplier';
382
- }
383
-
384
- // Upstream-Downstream: directional arrow
385
- if (rel.arrow === '->') {
386
- return 'UpstreamDownstream';
387
- }
388
-
389
- // Separate Ways
390
- if (rel.arrow === '><') {
391
- return 'SeparateWays';
392
- }
393
-
394
- return undefined;
395
- }
396
391
  }
397
392
 
398
393
  /**
@@ -726,38 +721,52 @@ export function augmentRelationship(rel: Relationship, containingBc?: BoundedCon
726
721
  // Helper methods for pattern matching (type-safe, no magic strings)
727
722
  hasPattern: {
728
723
  value: (pattern: string): boolean => {
729
- return rel.leftPatterns.some(p => matchesPattern(p, pattern)) ||
730
- rel.rightPatterns.some(p => matchesPattern(p, pattern));
724
+ if (isDirectionalRelationship(rel)) {
725
+ return rel.leftPatterns.some(p => matchesPattern(p.$type, pattern)) ||
726
+ rel.rightPatterns.some(p => matchesPattern(p.$type, pattern));
727
+ }
728
+ if (isSymmetricRelationship(rel) && rel.pattern) {
729
+ return matchesPattern(rel.pattern.$type, pattern);
730
+ }
731
+ return false;
731
732
  },
732
733
  enumerable: false,
733
734
  configurable: true,
734
735
  },
735
736
  hasLeftPattern: {
736
737
  value: (pattern: string): boolean => {
737
- return rel.leftPatterns.some(p => matchesPattern(p, pattern));
738
+ if (isDirectionalRelationship(rel)) {
739
+ return rel.leftPatterns.some(p => matchesPattern(p.$type, pattern));
740
+ }
741
+ return false;
738
742
  },
739
743
  enumerable: false,
740
744
  configurable: true,
741
745
  },
742
746
  hasRightPattern: {
743
747
  value: (pattern: string): boolean => {
744
- return rel.rightPatterns.some(p => matchesPattern(p, pattern));
748
+ if (isDirectionalRelationship(rel)) {
749
+ return rel.rightPatterns.some(p => matchesPattern(p.$type, pattern));
750
+ }
751
+ return false;
745
752
  },
746
753
  enumerable: false,
747
754
  configurable: true,
748
755
  },
749
756
  isUpstream: {
750
757
  value: (side: 'left' | 'right'): boolean => {
758
+ if (!isDirectionalRelationship(rel)) return false;
751
759
  const patterns = side === 'left' ? rel.leftPatterns : rel.rightPatterns;
752
- return patterns.some(p => isUpstreamPattern(p));
760
+ return patterns.some(p => isUpstreamPattern(p.$type));
753
761
  },
754
762
  enumerable: false,
755
763
  configurable: true,
756
764
  },
757
765
  isDownstream: {
758
766
  value: (side: 'left' | 'right'): boolean => {
767
+ if (!isDirectionalRelationship(rel)) return false;
759
768
  const patterns = side === 'left' ? rel.leftPatterns : rel.rightPatterns;
760
- return patterns.some(p => isDownstreamPattern(p));
769
+ return patterns.some(p => isDownstreamPattern(p.$type));
761
770
  },
762
771
  enumerable: false,
763
772
  configurable: true,
@@ -165,18 +165,31 @@ export function serializeNode(node: AstNode, query: Query): Record<string, unkno
165
165
  * @returns Serialized relationship object
166
166
  */
167
167
  export function serializeRelationship(view: RelationshipView): Record<string, unknown> {
168
- // RelationshipView.left and .right are BoundedContext (which have name property)
169
- const leftName = view.left.name;
170
- const rightName = view.right.name;
168
+ const leftName = view.left.context.name;
169
+ const rightName = view.right.context.name;
170
+ if (view.type === 'symmetric') {
171
+ const patternDisplay = view.kind === 'SeparateWays' ? '><' : `[${view.kind}]`;
172
+ return {
173
+ type: 'symmetric',
174
+ name: `${leftName} ${patternDisplay} ${rightName}`,
175
+ left: leftName,
176
+ right: rightName,
177
+ kind: view.kind,
178
+ source: view.source,
179
+ };
180
+ }
171
181
  return {
172
- $type: 'Relationship',
182
+ type: 'directional',
183
+ kind: view.kind,
173
184
  name: `${leftName} ${view.arrow} ${rightName}`,
174
185
  left: leftName,
175
186
  right: rightName,
176
187
  arrow: view.arrow,
177
- leftPatterns: view.leftPatterns,
178
- rightPatterns: view.rightPatterns,
179
- inferredType: view.inferredType,
188
+ leftPatterns: view.left.patterns.map(p => p.$type),
189
+ rightPatterns: view.right.patterns.map(p => p.$type),
190
+ upstreamPatterns: view.upstream?.patterns.map(p => p.$type),
191
+ downstreamPatterns: view.downstream?.patterns.map(p => p.$type),
192
+ source: view.source,
180
193
  };
181
194
  }
182
195