@domainlang/language 0.12.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.
- package/out/ast-augmentation.d.ts +7 -2
- package/out/diagram/context-map-diagram-generator.d.ts +9 -2
- package/out/diagram/context-map-diagram-generator.js +112 -63
- package/out/diagram/context-map-diagram-generator.js.map +1 -1
- package/out/generated/ast.d.ts +323 -51
- package/out/generated/ast.js +194 -33
- package/out/generated/ast.js.map +1 -1
- package/out/generated/grammar.js +418 -172
- package/out/generated/grammar.js.map +1 -1
- package/out/lsp/domain-lang-completion.js +39 -15
- package/out/lsp/domain-lang-completion.js.map +1 -1
- package/out/lsp/domain-lang-formatter.js +32 -0
- package/out/lsp/domain-lang-formatter.js.map +1 -1
- package/out/lsp/domain-lang-index-manager.d.ts +2 -3
- package/out/lsp/domain-lang-index-manager.js +5 -8
- package/out/lsp/domain-lang-index-manager.js.map +1 -1
- package/out/lsp/domain-lang-workspace-manager.d.ts +1 -1
- package/out/lsp/domain-lang-workspace-manager.js +2 -26
- package/out/lsp/domain-lang-workspace-manager.js.map +1 -1
- package/out/lsp/explain.js +9 -3
- package/out/lsp/explain.js.map +1 -1
- package/out/lsp/hover/domain-lang-hover.js +13 -11
- package/out/lsp/hover/domain-lang-hover.js.map +1 -1
- package/out/lsp/hover/domain-lang-keywords.js +29 -26
- package/out/lsp/hover/domain-lang-keywords.js.map +1 -1
- package/out/sdk/ast-augmentation.d.ts +29 -21
- package/out/sdk/ast-augmentation.js +11 -7
- package/out/sdk/ast-augmentation.js.map +1 -1
- package/out/sdk/index.d.ts +2 -2
- package/out/sdk/index.js +1 -1
- package/out/sdk/index.js.map +1 -1
- package/out/sdk/loader-node.js +2 -2
- package/out/sdk/loader-node.js.map +1 -1
- package/out/sdk/patterns.d.ts +50 -61
- package/out/sdk/patterns.js +92 -62
- package/out/sdk/patterns.js.map +1 -1
- package/out/sdk/query.js +54 -43
- package/out/sdk/query.js.map +1 -1
- package/out/sdk/serializers.js +20 -7
- package/out/sdk/serializers.js.map +1 -1
- package/out/sdk/types.d.ts +87 -18
- package/out/sdk/types.js.map +1 -1
- package/out/sdk/validator.js +48 -64
- package/out/sdk/validator.js.map +1 -1
- package/out/services/performance-optimizer.d.ts +3 -3
- package/out/services/performance-optimizer.js +1 -3
- package/out/services/performance-optimizer.js.map +1 -1
- package/out/services/relationship-inference.d.ts +4 -4
- package/out/services/relationship-inference.js +34 -46
- package/out/services/relationship-inference.js.map +1 -1
- package/out/syntaxes/domain-lang.monarch.js +1 -1
- package/out/syntaxes/domain-lang.monarch.js.map +1 -1
- package/out/utils/import-utils.d.ts +6 -20
- package/out/utils/import-utils.js +3 -63
- package/out/utils/import-utils.js.map +1 -1
- package/out/validation/constants.d.ts +23 -6
- package/out/validation/constants.js +24 -7
- package/out/validation/constants.js.map +1 -1
- package/out/validation/maps.js +10 -4
- package/out/validation/maps.js.map +1 -1
- package/out/validation/relationships.d.ts +4 -8
- package/out/validation/relationships.js +96 -48
- package/out/validation/relationships.js.map +1 -1
- package/package.json +1 -1
- package/src/ast-augmentation.ts +7 -2
- package/src/diagram/context-map-diagram-generator.ts +132 -70
- package/src/domain-lang.langium +62 -26
- package/src/generated/ast.ts +413 -63
- package/src/generated/grammar.ts +418 -172
- package/src/lsp/domain-lang-completion.ts +42 -15
- package/src/lsp/domain-lang-formatter.ts +34 -0
- package/src/lsp/domain-lang-index-manager.ts +6 -9
- package/src/lsp/domain-lang-workspace-manager.ts +3 -29
- package/src/lsp/explain.ts +10 -2
- package/src/lsp/hover/domain-lang-hover.ts +10 -8
- package/src/lsp/hover/domain-lang-keywords.ts +27 -24
- package/src/sdk/ast-augmentation.ts +30 -21
- package/src/sdk/index.ts +11 -1
- package/src/sdk/loader-node.ts +2 -2
- package/src/sdk/patterns.ts +114 -76
- package/src/sdk/query.ts +57 -48
- package/src/sdk/serializers.ts +20 -7
- package/src/sdk/types.ts +92 -17
- package/src/sdk/validator.ts +52 -69
- package/src/services/performance-optimizer.ts +4 -6
- package/src/services/relationship-inference.ts +43 -54
- package/src/utils/import-utils.ts +9 -74
- package/src/validation/constants.ts +32 -9
- package/src/validation/maps.ts +12 -4
- package/src/validation/relationships.ts +150 -71
package/src/sdk/patterns.ts
CHANGED
|
@@ -1,147 +1,185 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
*
|
|
20
|
-
*
|
|
16
|
+
* Pattern constants for programmatic use.
|
|
17
|
+
* Values match the AST $type names.
|
|
21
18
|
*/
|
|
22
19
|
export const Pattern = {
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
93
|
-
*
|
|
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
|
-
*
|
|
118
|
+
* Checks if a side pattern AST node is an upstream pattern.
|
|
115
119
|
*/
|
|
116
|
-
export
|
|
120
|
+
export function isUpstreamSidePattern(pattern: SidePattern): boolean {
|
|
121
|
+
return isOpenHostService(pattern) || isPublishedLanguage(pattern) || isSupplier(pattern);
|
|
122
|
+
}
|
|
117
123
|
|
|
118
124
|
/**
|
|
119
|
-
*
|
|
125
|
+
* Checks if a side pattern AST node is a downstream pattern.
|
|
120
126
|
*/
|
|
121
|
-
export
|
|
127
|
+
export function isDownstreamSidePattern(pattern: SidePattern): boolean {
|
|
128
|
+
return isConformist(pattern) || isAntiCorruptionLayer(pattern) || isCustomer(pattern);
|
|
129
|
+
}
|
|
122
130
|
|
|
123
131
|
/**
|
|
124
|
-
*
|
|
132
|
+
* Checks if a side pattern AST node is a Big Ball of Mud.
|
|
125
133
|
*/
|
|
126
|
-
export
|
|
134
|
+
export function isBBoMSidePattern(pattern: SidePattern): boolean {
|
|
135
|
+
return isBigBallOfMud(pattern);
|
|
136
|
+
}
|
|
127
137
|
|
|
128
138
|
/**
|
|
129
|
-
* Checks if a pattern is
|
|
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
|
|
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
|
|
153
|
+
* Checks if a pattern string name is a mutual/symmetric pattern.
|
|
144
154
|
*/
|
|
145
155
|
export function isMutualPattern(pattern: string): boolean {
|
|
146
|
-
return
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
arrow
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
730
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
package/src/sdk/serializers.ts
CHANGED
|
@@ -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
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
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
|
-
|
|
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.
|
|
178
|
-
rightPatterns: view.
|
|
179
|
-
|
|
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
|
|
package/src/sdk/types.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
Model,
|
|
14
14
|
NamespaceDeclaration,
|
|
15
15
|
Relationship,
|
|
16
|
+
SidePattern,
|
|
16
17
|
Team,
|
|
17
18
|
} from '../generated/ast.js';
|
|
18
19
|
import type { DomainLangServices } from '../domain-lang-module.js';
|
|
@@ -281,30 +282,104 @@ export interface BcQueryBuilder extends QueryBuilder<BoundedContext> {
|
|
|
281
282
|
}
|
|
282
283
|
|
|
283
284
|
/**
|
|
284
|
-
*
|
|
285
|
-
*
|
|
285
|
+
* One side of a relationship: the bounded context and its annotated integration patterns.
|
|
286
|
+
* For symmetric relationships `patterns` is always empty — the pattern lives on the relationship itself.
|
|
287
|
+
*/
|
|
288
|
+
export interface RelationshipSide {
|
|
289
|
+
/** The bounded context on this side */
|
|
290
|
+
readonly context: BoundedContext;
|
|
291
|
+
/** Integration patterns annotated on this side (directional relationships only) */
|
|
292
|
+
readonly patterns: readonly SidePattern[];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* DDD kind of a directional relationship.
|
|
297
|
+
* Fully discriminates all three structural cases.
|
|
298
|
+
*/
|
|
299
|
+
export type DirectionalKind = 'UpstreamDownstream' | 'CustomerSupplier' | 'Bidirectional';
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Directional relationship: has an arrow expressing dependency between two contexts.
|
|
303
|
+
* Discriminate on `type` to access directional or symmetric properties.
|
|
304
|
+
* Discriminate on `kind` to handle upstream/downstream vs customer/supplier vs bidirectional.
|
|
286
305
|
*/
|
|
287
|
-
export interface
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
306
|
+
export interface DirectionalRelationshipView {
|
|
307
|
+
readonly type: 'directional';
|
|
308
|
+
/** DDD semantic kind — determines which roles each side plays */
|
|
309
|
+
readonly kind: DirectionalKind;
|
|
310
|
+
/** Arrow as written in source (use for display/serialization) */
|
|
311
|
+
readonly arrow: '->' | '<-' | '<->';
|
|
312
|
+
/** Left side as written in source — always defined */
|
|
313
|
+
readonly left: RelationshipSide;
|
|
314
|
+
/** Right side as written in source — always defined */
|
|
315
|
+
readonly right: RelationshipSide;
|
|
316
|
+
/**
|
|
317
|
+
* The upstream (provider) side with its patterns.
|
|
318
|
+
* Defined when `kind` is `'UpstreamDownstream'` or `'CustomerSupplier'`.
|
|
319
|
+
* `undefined` when `kind` is `'Bidirectional'` — no upstream role exists.
|
|
320
|
+
*/
|
|
321
|
+
readonly upstream: RelationshipSide | undefined;
|
|
322
|
+
/**
|
|
323
|
+
* The downstream (consumer) side with its patterns.
|
|
324
|
+
* Defined when `kind` is `'UpstreamDownstream'` or `'CustomerSupplier'`.
|
|
325
|
+
* `undefined` when `kind` is `'Bidirectional'` — no downstream role exists.
|
|
326
|
+
*/
|
|
327
|
+
readonly downstream: RelationshipSide | undefined;
|
|
302
328
|
/** Source of the relationship definition */
|
|
303
329
|
readonly source: 'BoundedContext' | 'ContextMap';
|
|
304
330
|
/** Original AST relationship node */
|
|
305
331
|
readonly astNode: Relationship;
|
|
306
332
|
}
|
|
307
333
|
|
|
334
|
+
/** Resolved symmetric integration pattern kind. */
|
|
335
|
+
export type SymmetricKind = 'SharedKernel' | 'Partnership' | 'SeparateWays';
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Symmetric relationship: no arrow; neither context is upstream or downstream.
|
|
339
|
+
* Discriminate on `type` to access directional or symmetric properties.
|
|
340
|
+
*/
|
|
341
|
+
export interface SymmetricRelationshipView {
|
|
342
|
+
readonly type: 'symmetric';
|
|
343
|
+
/**
|
|
344
|
+
* Always-resolved symmetric integration pattern.
|
|
345
|
+
* `><` resolves to `'SeparateWays'`.
|
|
346
|
+
*/
|
|
347
|
+
readonly kind: SymmetricKind;
|
|
348
|
+
/**
|
|
349
|
+
* Left side as written in source.
|
|
350
|
+
* `patterns` is always empty — symmetric patterns attach to the relationship, not each side.
|
|
351
|
+
*/
|
|
352
|
+
readonly left: RelationshipSide;
|
|
353
|
+
/**
|
|
354
|
+
* Right side as written in source.
|
|
355
|
+
* `patterns` is always empty — symmetric patterns attach to the relationship, not each side.
|
|
356
|
+
*/
|
|
357
|
+
readonly right: RelationshipSide;
|
|
358
|
+
/** Source of the relationship definition */
|
|
359
|
+
readonly source: 'BoundedContext' | 'ContextMap';
|
|
360
|
+
/** Original AST relationship node */
|
|
361
|
+
readonly astNode: Relationship;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Unified view of a relationship between two BoundedContexts.
|
|
366
|
+
* Relationships can be defined in BoundedContext blocks or ContextMap.
|
|
367
|
+
* Discriminate on `type` to access directional or symmetric properties.
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```typescript
|
|
371
|
+
* for (const rel of query.relationships()) {
|
|
372
|
+
* if (rel.type === 'symmetric') {
|
|
373
|
+
* console.log(rel.kind); // 'SharedKernel' | 'Partnership' | 'SeparateWays'
|
|
374
|
+
* } else {
|
|
375
|
+
* console.log(rel.kind); // 'UpstreamDownstream' | 'CustomerSupplier' | 'Bidirectional'
|
|
376
|
+
* console.log(rel.arrow); // '->' | '<-' | '<->'
|
|
377
|
+
* }
|
|
378
|
+
* }
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
export type RelationshipView = DirectionalRelationshipView | SymmetricRelationshipView;
|
|
382
|
+
|
|
308
383
|
/**
|
|
309
384
|
* Internal index structure for O(1) lookups.
|
|
310
385
|
* Not exported from public API.
|