@domainlang/language 0.5.2 → 0.6.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 (104) hide show
  1. package/out/domain-lang-module.js +3 -1
  2. package/out/domain-lang-module.js.map +1 -1
  3. package/out/generated/ast.d.ts +24 -0
  4. package/out/generated/ast.js.map +1 -1
  5. package/out/generated/grammar.js +22 -32
  6. package/out/generated/grammar.js.map +1 -1
  7. package/out/index.d.ts +2 -5
  8. package/out/index.js +10 -6
  9. package/out/index.js.map +1 -1
  10. package/out/lsp/domain-lang-code-actions.js +14 -8
  11. package/out/lsp/domain-lang-code-actions.js.map +1 -1
  12. package/out/lsp/domain-lang-completion.d.ts +3 -0
  13. package/out/lsp/domain-lang-completion.js +41 -13
  14. package/out/lsp/domain-lang-completion.js.map +1 -1
  15. package/out/lsp/domain-lang-formatter.js +24 -18
  16. package/out/lsp/domain-lang-formatter.js.map +1 -1
  17. package/out/lsp/domain-lang-index-manager.d.ts +102 -0
  18. package/out/lsp/domain-lang-index-manager.js +221 -0
  19. package/out/lsp/domain-lang-index-manager.js.map +1 -0
  20. package/out/lsp/domain-lang-scope.js +31 -17
  21. package/out/lsp/domain-lang-scope.js.map +1 -1
  22. package/out/lsp/domain-lang-workspace-manager.d.ts +51 -9
  23. package/out/lsp/domain-lang-workspace-manager.js +86 -63
  24. package/out/lsp/domain-lang-workspace-manager.js.map +1 -1
  25. package/out/lsp/hover/domain-lang-hover.d.ts +45 -1
  26. package/out/lsp/hover/domain-lang-hover.js +308 -232
  27. package/out/lsp/hover/domain-lang-hover.js.map +1 -1
  28. package/out/lsp/hover/domain-lang-keywords.d.ts +3 -7
  29. package/out/lsp/hover/domain-lang-keywords.js +115 -38
  30. package/out/lsp/hover/domain-lang-keywords.js.map +1 -1
  31. package/out/lsp/manifest-diagnostics.js +95 -50
  32. package/out/lsp/manifest-diagnostics.js.map +1 -1
  33. package/out/main.js +109 -17
  34. package/out/main.js.map +1 -1
  35. package/out/services/import-resolver.d.ts +16 -2
  36. package/out/services/import-resolver.js +37 -11
  37. package/out/services/import-resolver.js.map +1 -1
  38. package/out/services/types.d.ts +2 -2
  39. package/out/services/workspace-manager.d.ts +33 -31
  40. package/out/services/workspace-manager.js +92 -148
  41. package/out/services/workspace-manager.js.map +1 -1
  42. package/out/utils/document-utils.d.ts +41 -0
  43. package/out/utils/document-utils.js +64 -0
  44. package/out/utils/document-utils.js.map +1 -0
  45. package/out/utils/import-utils.d.ts +0 -17
  46. package/out/utils/import-utils.js +2 -32
  47. package/out/utils/import-utils.js.map +1 -1
  48. package/out/utils/manifest-utils.d.ts +56 -0
  49. package/out/utils/manifest-utils.js +119 -0
  50. package/out/utils/manifest-utils.js.map +1 -0
  51. package/out/validation/import.d.ts +1 -2
  52. package/out/validation/import.js +33 -20
  53. package/out/validation/import.js.map +1 -1
  54. package/package.json +1 -1
  55. package/src/domain-lang-module.ts +4 -1
  56. package/src/domain-lang.langium +37 -13
  57. package/src/generated/ast.ts +24 -0
  58. package/src/generated/grammar.ts +22 -32
  59. package/src/index.ts +12 -6
  60. package/src/lsp/domain-lang-code-actions.ts +13 -8
  61. package/src/lsp/domain-lang-completion.ts +61 -13
  62. package/src/lsp/domain-lang-formatter.ts +28 -23
  63. package/src/lsp/domain-lang-index-manager.ts +256 -0
  64. package/src/lsp/domain-lang-scope.ts +29 -17
  65. package/src/lsp/domain-lang-workspace-manager.ts +89 -66
  66. package/src/lsp/hover/domain-lang-hover.ts +332 -226
  67. package/src/lsp/hover/domain-lang-keywords.ts +129 -43
  68. package/src/lsp/manifest-diagnostics.ts +100 -59
  69. package/src/main.ts +127 -16
  70. package/src/services/import-resolver.ts +39 -11
  71. package/src/services/types.ts +2 -2
  72. package/src/services/workspace-manager.ts +101 -175
  73. package/src/utils/document-utils.ts +80 -0
  74. package/src/utils/import-utils.ts +2 -40
  75. package/src/utils/manifest-utils.ts +132 -0
  76. package/src/validation/import.ts +32 -22
  77. package/out/lsp/hover/ddd-pattern-explanations.d.ts +0 -50
  78. package/out/lsp/hover/ddd-pattern-explanations.js +0 -196
  79. package/out/lsp/hover/ddd-pattern-explanations.js.map +0 -1
  80. package/out/services/dependency-analyzer.d.ts +0 -58
  81. package/out/services/dependency-analyzer.js +0 -254
  82. package/out/services/dependency-analyzer.js.map +0 -1
  83. package/out/services/dependency-resolver.d.ts +0 -146
  84. package/out/services/dependency-resolver.js +0 -452
  85. package/out/services/dependency-resolver.js.map +0 -1
  86. package/out/services/git-url-resolver.browser.d.ts +0 -10
  87. package/out/services/git-url-resolver.browser.js +0 -19
  88. package/out/services/git-url-resolver.browser.js.map +0 -1
  89. package/out/services/git-url-resolver.d.ts +0 -158
  90. package/out/services/git-url-resolver.js +0 -416
  91. package/out/services/git-url-resolver.js.map +0 -1
  92. package/out/services/governance-validator.d.ts +0 -44
  93. package/out/services/governance-validator.js +0 -153
  94. package/out/services/governance-validator.js.map +0 -1
  95. package/out/services/semver.d.ts +0 -98
  96. package/out/services/semver.js +0 -195
  97. package/out/services/semver.js.map +0 -1
  98. package/src/lsp/hover/ddd-pattern-explanations.ts +0 -237
  99. package/src/services/dependency-analyzer.ts +0 -321
  100. package/src/services/dependency-resolver.ts +0 -551
  101. package/src/services/git-url-resolver.browser.ts +0 -26
  102. package/src/services/git-url-resolver.ts +0 -517
  103. package/src/services/governance-validator.ts +0 -177
  104. package/src/services/semver.ts +0 -213
@@ -1,50 +1,136 @@
1
1
  /**
2
2
  * Keyword explanations for DomainLang hover documentation.
3
3
  *
4
- * This dictionary provides fallback hover content for keywords that don't have
5
- * JSDoc comments in the grammar file, or for providing richer DDD pattern explanations.
4
+ * This dictionary provides concise hover content for all DomainLang keywords,
5
+ * DDD patterns, and special symbols. Uses exact casing from grammar.
6
6
  *
7
- * Basic keyword documentation (domain, boundedcontext, etc.) is now in the grammar file
8
- * as JSDoc comments. This dictionary focuses on DDD integration patterns and advanced concepts.
9
- *
10
- * @see src/language/domain-lang.langium for basic keyword JSDoc
11
- * @see ddd-pattern-explanations.ts for role patterns and relationship types
7
+ * @see https://domainlang.net/reference/language for full documentation
12
8
  */
9
+
10
+ // Documentation links
11
+ const DOMAIN_LINK = '\n\n[Read more](https://domainlang.net/guide/domains)';
12
+ const BC_LINK = '\n\n[Read more](https://domainlang.net/guide/bounded-contexts)';
13
+ const TEAM_LINK = '\n\n[Read more](https://domainlang.net/guide/teams-classifications)';
14
+ const MAP_LINK = '\n\n[Read more](https://domainlang.net/guide/context-maps)';
15
+ const REL_LINK = '\n\n[Read more](https://domainlang.net/guide/context-maps#relationships)';
16
+ const IMPORT_LINK = '\n\n[Read more](https://domainlang.net/guide/imports)';
17
+ const NS_LINK = '\n\n[Read more](https://domainlang.net/guide/namespaces)';
18
+ const TERM_LINK = '\n\n[Read more](https://domainlang.net/reference/language#terminology)';
19
+ const DECISION_LINK = '\n\n[Read more](https://domainlang.net/reference/language#decisions-policies-rules)';
20
+ const METADATA_LINK = '\n\n[Read more](https://domainlang.net/reference/language#metadata)';
21
+ const SYNTAX_LINK = '\n\n[Read more](https://domainlang.net/reference/language)';
22
+
13
23
  export const keywordExplanations: Record<string, string> = {
14
- // Advanced syntax keywords
15
- implements: "**implements** - Declares that a Bounded Context or type implements a Domain or interface.",
16
- as: "**as** - Used for aliasing or renaming imports or types.",
17
- from: "**from** - Specifies the source module or file for an import statement.",
18
- type: "**type** - Declares a new type or alias in the model.",
19
- map: "**map** - Defines a mapping or transformation between elements.",
20
- this: "**this** - Refers to the current context or object.",
21
-
22
- // DDD Classifiers
23
- entity: "**Entity** - Domain object with distinct identity that runs through time.",
24
- valueobject: "**Value Object** - Immutable object that describes a characteristic.",
25
- aggregate: "**Aggregate** - Cluster of domain objects with a root and boundary.",
26
- service: "**Service** - Stateless domain operation.",
27
- event: "**Event** - Significant domain occurrence or state change.",
28
- businessrule: "**Business Rule** - Rule that constrains business behavior.",
29
-
24
+ // ========================================================================
25
+ // Primary Constructs
26
+ // ========================================================================
27
+ domain: `**Domain** - A sphere of knowledge or activity. Can be nested to show subdomain hierarchy.${DOMAIN_LINK}`,
28
+ dom: `**Domain** - A sphere of knowledge or activity. Can be nested to show subdomain hierarchy.${DOMAIN_LINK}`,
29
+ boundedcontext: `**BoundedContext** - A boundary where a domain model is defined. Central DDD pattern for managing complexity.${BC_LINK}`,
30
+ bc: `**BoundedContext** - A boundary where a domain model is defined. Central DDD pattern for managing complexity.${BC_LINK}`,
31
+ team: `**Team** - A group responsible for one or more bounded contexts.${TEAM_LINK}`,
32
+ classification: `**Classification** - Reusable label for categorizing elements (e.g., Core, Supporting, Generic).${TEAM_LINK}`,
33
+ metadata: `**Metadata** - Defines a key that can be used in metadata blocks.${METADATA_LINK}`,
34
+ meta: `**Metadata** - Defines a key that can be used in metadata blocks.${METADATA_LINK}`,
35
+
36
+ // ========================================================================
37
+ // Maps
38
+ // ========================================================================
39
+ contextmap: `**ContextMap** - Shows relationships between bounded contexts.${MAP_LINK}`,
40
+ cmap: `**ContextMap** - Shows relationships between bounded contexts.${MAP_LINK}`,
41
+ domainmap: `**DomainMap** - Visualizes domains and their subdomain structure.${MAP_LINK}`,
42
+ dmap: `**DomainMap** - Visualizes domains and their subdomain structure.${MAP_LINK}`,
43
+ contains: `**contains** - Specifies which elements are part of a map.${MAP_LINK}`,
44
+
45
+ // ========================================================================
46
+ // Bounded Context & Domain Properties
47
+ // ========================================================================
48
+ for: `**for** - Associates a bounded context with its parent domain.${BC_LINK}`,
49
+ as: `**as** - Assigns a classification to an element.${BC_LINK}`,
50
+ by: `**by** - Assigns a team responsible for an element.${BC_LINK}`,
51
+ in: `**in** - Specifies parent domain for subdomain nesting.${DOMAIN_LINK}`,
52
+ description: `**description** - Human-readable explanation of the element's purpose.${SYNTAX_LINK}`,
53
+ vision: `**vision** - Strategic vision statement for a domain.${DOMAIN_LINK}`,
54
+ type: `**type** - Assigns a classification type to a domain or relationship.${DOMAIN_LINK}`,
55
+ businessmodel: `**businessModel** - Revenue or engagement model for a context.${BC_LINK}`,
56
+ evolution: `**evolution** - Maturity stage (Genesis, Custom, Product, Commodity).${BC_LINK}`,
57
+ archetype: `**archetype** - Behavioral role (Gateway, Execution, etc.).${BC_LINK}`,
58
+ relationships: `**relationships** - Block defining integration patterns with other contexts.${REL_LINK}`,
59
+ integrations: `**integrations** - Block defining integration patterns with other contexts.${REL_LINK}`,
60
+
61
+ // ========================================================================
62
+ // Terminology & Glossary
63
+ // ========================================================================
64
+ terminology: `**terminology** - Block defining domain-specific terms and definitions.${TERM_LINK}`,
65
+ glossary: `**glossary** - Block defining domain-specific terms and definitions.${TERM_LINK}`,
66
+ term: `**Term** - Defines a domain term with its meaning.${TERM_LINK}`,
67
+ aka: `**aka** - Alternative names (also known as) for a term.${TERM_LINK}`,
68
+ synonyms: `**synonyms** - Alternative names (also known as) for a term.${TERM_LINK}`,
69
+ examples: `**examples** - Example usage of a term.${TERM_LINK}`,
70
+ meaning: `**meaning** - The definition or explanation of a term.${TERM_LINK}`,
71
+
72
+ // ========================================================================
73
+ // Decisions, Policies & Rules
74
+ // ========================================================================
75
+ decisions: `**decisions** - Block containing architectural decisions or business rules.${DECISION_LINK}`,
76
+ rules: `**rules** - Block containing architectural decisions or business rules.${DECISION_LINK}`,
77
+ decision: `**Decision** - An architectural or domain decision.${DECISION_LINK}`,
78
+ policy: `**Policy** - A business policy or organizational rule.${DECISION_LINK}`,
79
+ rule: `**Rule** - A business rule or constraint (also BusinessRule).${DECISION_LINK}`,
80
+
81
+ // ========================================================================
82
+ // Import System
83
+ // ========================================================================
84
+ import: `**Import** - Imports definitions from an external model or file.${IMPORT_LINK}`,
85
+
86
+ // ========================================================================
87
+ // Namespaces
88
+ // ========================================================================
89
+ namespace: `**Namespace** - Groups elements under a qualified name.${NS_LINK}`,
90
+ ns: `**Namespace** - Groups elements under a qualified name.${NS_LINK}`,
91
+
92
+ // ========================================================================
93
+ // Assignment Operators
94
+ // ========================================================================
95
+ ':': `**:** - Assignment operator (property: value).${SYNTAX_LINK}`,
96
+ is: `**is** - Assignment operator (property is value).${SYNTAX_LINK}`,
97
+ '=': `**=** - Assignment operator (property = value).${SYNTAX_LINK}`,
98
+
99
+ // ========================================================================
100
+ // Context Reference
101
+ // ========================================================================
102
+ this: `**this** - References the current bounded context in relationships.${REL_LINK}`,
103
+
104
+ // ========================================================================
30
105
  // DDD Integration Patterns
31
- acl: "**ACL (Anti-Corruption Layer)** - Translation layer protecting downstream context from upstream changes.",
32
- ohs: "**OHS (Open Host Service)** - Well-defined protocol providing access to a subsystem.",
33
- pl: "**PL (Published Language)** - Documented shared language for context communication.",
34
- cf: "**CF (Conformist)** - Downstream adopts upstream model without translation.",
35
- bbom: "**BBoM (Big Ball of Mud)** - System with tangled architecture and no clear boundaries.",
36
- sk: "**SK (Shared Kernel)** - Shared domain model subset requiring coordination.",
37
- p: "**P (Partnership)** - Teams collaborate closely with shared success/failure.",
38
-
39
- // DDD Relationship Types
40
- separateways: "**Separate Ways** - No connection between contexts, each solves problems independently.",
41
- partnership: "**Partnership** - Teams share risks and rewards with close collaboration.",
42
- sharedkernel: "**Shared Kernel** - Shared domain model subset requiring coordination.",
43
- customersupplier: "**Customer-Supplier** - Upstream prioritizes downstream needs.",
44
- upstreamdownstream: "**Upstream-Downstream** - Upstream changes affect downstream.",
45
-
46
- // Relationship arrows
47
- '<->': "**Bidirectional** - Two contexts connected in both directions.",
48
- '->': "**Upstream → Downstream** - Left depends on right.",
49
- '<-': "**Downstream ← Upstream** - Right depends on left.",
50
- };
106
+ // ========================================================================
107
+ acl: `**ACL** - Anti-Corruption Layer. Protects from external models by translating between domains.${REL_LINK}`,
108
+ anticorruptionlayer: `**AntiCorruptionLayer** - Protects from external models by translating between domains.${REL_LINK}`,
109
+ ohs: `**OHS** - Open Host Service. Provides a well-documented API for integration.${REL_LINK}`,
110
+ openhostservice: `**OpenHostService** - Provides a well-documented API for integration.${REL_LINK}`,
111
+ pl: `**PL** - Published Language. Documented language for inter-context communication.${REL_LINK}`,
112
+ publishedlanguage: `**PublishedLanguage** - Documented language for inter-context communication.${REL_LINK}`,
113
+ cf: `**CF** - Conformist. Adopts upstream model without translation.${REL_LINK}`,
114
+ conformist: `**Conformist** - Adopts upstream model without translation.${REL_LINK}`,
115
+ p: `**P** - Partnership. Two teams with mutual dependency and shared goals.${REL_LINK}`,
116
+ partnership: `**Partnership** - Two teams with mutual dependency and shared goals.${REL_LINK}`,
117
+ sk: `**SK** - Shared Kernel. Shared code/data requiring careful coordination.${REL_LINK}`,
118
+ sharedkernel: `**SharedKernel** - Shared code/data requiring careful coordination.${REL_LINK}`,
119
+ bbom: `**BBoM** - Big Ball of Mud. Legacy area without clear boundaries.${REL_LINK}`,
120
+ bigballofmud: `**BigBallOfMud** - Legacy area without clear boundaries.${REL_LINK}`,
121
+
122
+ // ========================================================================
123
+ // Relationship Types
124
+ // ========================================================================
125
+ customersupplier: `**CustomerSupplier** - Downstream depends on upstream with influence over priorities.${REL_LINK}`,
126
+ upstreamdownstream: `**UpstreamDownstream** - One context depends on another's model.${REL_LINK}`,
127
+ separateways: `**SeparateWays** - Contexts with no integration, solving problems independently.${REL_LINK}`,
128
+
129
+ // ========================================================================
130
+ // Relationship Arrows
131
+ // ========================================================================
132
+ '->': `**->** - Unidirectional dependency (upstream to downstream).${REL_LINK}`,
133
+ '<->': `**<->** - Bidirectional dependency (mutual).${REL_LINK}`,
134
+ '<-': `**<-** - Reverse unidirectional dependency (downstream to upstream).${REL_LINK}`,
135
+ '><': `**><** - Separate Ways (no integration).${REL_LINK}`,
136
+ };
@@ -14,7 +14,7 @@
14
14
 
15
15
  import type { Connection } from 'vscode-languageserver';
16
16
  import { Diagnostic, DiagnosticSeverity, Position, Range } from 'vscode-languageserver-types';
17
- import YAML, { type Document as YAMLDocument, type YAMLMap, type Pair, isMap, isPair, isScalar } from 'yaml';
17
+ import YAML, { type Document as YAMLDocument, type Pair, isMap, isPair, isScalar } from 'yaml';
18
18
  import { ManifestValidator, type ManifestDiagnostic, type ManifestSeverity } from '../validation/manifest.js';
19
19
  import type { ModelManifest } from '../services/types.js';
20
20
 
@@ -45,16 +45,32 @@ export class ManifestDiagnosticsService {
45
45
  content: string,
46
46
  options?: { requirePublishable?: boolean }
47
47
  ): Promise<void> {
48
- if (!this.connection) {
49
- return; // No connection, skip diagnostics
50
- }
48
+ try {
49
+ if (!this.connection) {
50
+ return; // No connection, skip diagnostics
51
+ }
51
52
 
52
- const diagnostics = this.validate(content, options);
53
-
54
- await this.connection.sendDiagnostics({
55
- uri: manifestUri,
56
- diagnostics
57
- });
53
+ const diagnostics = this.validate(content, options);
54
+
55
+ await this.connection.sendDiagnostics({
56
+ uri: manifestUri,
57
+ diagnostics
58
+ });
59
+ } catch (error) {
60
+ console.error('Error in validateAndSendDiagnostics:', error);
61
+ // Send minimal error diagnostic instead of crashing
62
+ if (this.connection) {
63
+ await this.connection.sendDiagnostics({
64
+ uri: manifestUri,
65
+ diagnostics: [{
66
+ severity: DiagnosticSeverity.Error,
67
+ range: Range.create(Position.create(0, 0), Position.create(0, 1)),
68
+ message: 'Internal error validating manifest file',
69
+ source: 'domainlang'
70
+ }]
71
+ });
72
+ }
73
+ }
58
74
  }
59
75
 
60
76
  /**
@@ -68,42 +84,53 @@ export class ManifestDiagnosticsService {
68
84
  content: string,
69
85
  options?: { requirePublishable?: boolean }
70
86
  ): Diagnostic[] {
71
- // Parse YAML to get both the manifest object and source map
72
- let yamlDoc: YAMLDocument.Parsed;
73
- let manifest: ModelManifest;
74
-
75
87
  try {
76
- yamlDoc = YAML.parseDocument(content);
88
+ // Parse YAML to get both the manifest object and source map
89
+ let yamlDoc: YAMLDocument.Parsed;
90
+ let manifest: ModelManifest;
77
91
 
78
- // Check for YAML parse errors (they're in the errors array, not thrown)
79
- if (yamlDoc.errors && yamlDoc.errors.length > 0) {
80
- return yamlDoc.errors.map(err => ({
92
+ try {
93
+ yamlDoc = YAML.parseDocument(content);
94
+
95
+ // Check for YAML parse errors (they're in the errors array, not thrown)
96
+ if (yamlDoc.errors && yamlDoc.errors.length > 0) {
97
+ return yamlDoc.errors.map(err => ({
98
+ severity: DiagnosticSeverity.Error,
99
+ range: this.yamlErrorToRange(err, content),
100
+ message: `YAML parse error: ${err.message}`,
101
+ source: 'domainlang'
102
+ }));
103
+ }
104
+
105
+ manifest = (yamlDoc.toJSON() ?? {}) as ModelManifest;
106
+ } catch (error) {
107
+ // Fallback for unexpected errors
108
+ const message = error instanceof Error ? error.message : 'Invalid YAML syntax';
109
+ return [{
81
110
  severity: DiagnosticSeverity.Error,
82
- range: this.yamlErrorToRange(err, content),
83
- message: `YAML parse error: ${err.message}`,
111
+ range: Range.create(Position.create(0, 0), Position.create(0, 1)),
112
+ message: `YAML parse error: ${message}`,
84
113
  source: 'domainlang'
85
- }));
114
+ }];
86
115
  }
116
+
117
+ // Run manifest validation
118
+ const result = this.validator.validate(manifest, options);
87
119
 
88
- manifest = (yamlDoc.toJSON() ?? {}) as ModelManifest;
120
+ // Convert to LSP diagnostics with source locations
121
+ return result.diagnostics.map(diag =>
122
+ this.toVSCodeDiagnostic(diag, yamlDoc)
123
+ );
89
124
  } catch (error) {
90
- // Fallback for unexpected errors
91
- const message = error instanceof Error ? error.message : 'Invalid YAML syntax';
125
+ console.error('Error in validate:', error);
126
+ // Return minimal error diagnostic
92
127
  return [{
93
128
  severity: DiagnosticSeverity.Error,
94
129
  range: Range.create(Position.create(0, 0), Position.create(0, 1)),
95
- message: `YAML parse error: ${message}`,
130
+ message: 'Internal error during validation',
96
131
  source: 'domainlang'
97
132
  }];
98
133
  }
99
-
100
- // Run manifest validation
101
- const result = this.validator.validate(manifest, options);
102
-
103
- // Convert to LSP diagnostics with source locations
104
- return result.diagnostics.map(diag =>
105
- this.toVSCodeDiagnostic(diag, yamlDoc)
106
- );
107
134
  }
108
135
 
109
136
  /**
@@ -130,14 +157,19 @@ export class ManifestDiagnosticsService {
130
157
  * Call this when the file is closed or deleted.
131
158
  */
132
159
  async clearDiagnostics(manifestUri: string): Promise<void> {
133
- if (!this.connection) {
134
- return;
135
- }
160
+ try {
161
+ if (!this.connection) {
162
+ return;
163
+ }
136
164
 
137
- await this.connection.sendDiagnostics({
138
- uri: manifestUri,
139
- diagnostics: []
140
- });
165
+ await this.connection.sendDiagnostics({
166
+ uri: manifestUri,
167
+ diagnostics: []
168
+ });
169
+ } catch (error) {
170
+ console.error('Error in clearDiagnostics:', error);
171
+ // Ignore - don't crash on cleanup
172
+ }
141
173
  }
142
174
 
143
175
  /**
@@ -147,20 +179,32 @@ export class ManifestDiagnosticsService {
147
179
  diag: ManifestDiagnostic,
148
180
  yamlDoc: YAMLDocument.Parsed
149
181
  ): Diagnostic {
150
- const range = this.findRangeForPath(diag.path, yamlDoc);
151
-
152
- let message = diag.message;
153
- if (diag.hint) {
154
- message += `\nHint: ${diag.hint}`;
155
- }
182
+ try {
183
+ const range = this.findRangeForPath(diag.path, yamlDoc);
184
+
185
+ let message = diag.message;
186
+ if (diag.hint) {
187
+ message += `\nHint: ${diag.hint}`;
188
+ }
156
189
 
157
- return {
158
- severity: this.toVSCodeSeverity(diag.severity),
159
- range,
160
- message,
161
- source: 'domainlang',
162
- code: diag.code
163
- };
190
+ return {
191
+ severity: this.toVSCodeSeverity(diag.severity),
192
+ range,
193
+ message,
194
+ source: 'domainlang',
195
+ code: diag.code
196
+ };
197
+ } catch (error) {
198
+ console.error('Error converting diagnostic:', error);
199
+ // Return minimal diagnostic at file start
200
+ return {
201
+ severity: DiagnosticSeverity.Error,
202
+ range: Range.create(Position.create(0, 0), Position.create(0, 1)),
203
+ message: diag.message,
204
+ source: 'domainlang',
205
+ code: diag.code
206
+ };
207
+ }
164
208
  }
165
209
 
166
210
  /**
@@ -198,8 +242,7 @@ export class ManifestDiagnosticsService {
198
242
  return fallback;
199
243
  }
200
244
 
201
- const mapNode = currentNode as YAMLMap;
202
- const item = mapNode.items.find((pair): pair is Pair =>
245
+ const item = currentNode.items.find((pair): pair is Pair =>
203
246
  isPair(pair) && isScalar(pair.key) && String(pair.key.value) === part
204
247
  );
205
248
 
@@ -208,7 +251,7 @@ export class ManifestDiagnosticsService {
208
251
  }
209
252
 
210
253
  // If this is the last part, return the range of the key
211
- if (part === parts[parts.length - 1]) {
254
+ if (part === parts.at(-1)) {
212
255
  const keyNode = item.key;
213
256
  if (isScalar(keyNode) && keyNode.range) {
214
257
  const [start, end] = keyNode.range;
@@ -269,9 +312,7 @@ let manifestDiagnosticsService: ManifestDiagnosticsService | undefined;
269
312
  * Gets or creates the manifest diagnostics service singleton.
270
313
  */
271
314
  export function getManifestDiagnosticsService(): ManifestDiagnosticsService {
272
- if (!manifestDiagnosticsService) {
273
- manifestDiagnosticsService = new ManifestDiagnosticsService();
274
- }
315
+ manifestDiagnosticsService ??= new ManifestDiagnosticsService();
275
316
  return manifestDiagnosticsService;
276
317
  }
277
318
 
package/src/main.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { startLanguageServer } from 'langium/lsp';
2
2
  import { NodeFileSystem } from 'langium/node';
3
- import { createConnection, ProposedFeatures } from 'vscode-languageserver/node.js';
3
+ import { createConnection, ProposedFeatures, FileChangeType } from 'vscode-languageserver/node.js';
4
4
  import { createDomainLangServices } from './domain-lang-module.js';
5
5
  import { ensureImportGraphFromEntryFile } from './utils/import-utils.js';
6
+ import { DomainLangIndexManager } from './lsp/domain-lang-index-manager.js';
6
7
  import { URI } from 'langium';
7
8
 
8
9
  // Create a connection to the client
@@ -11,29 +12,138 @@ const connection = createConnection(ProposedFeatures.all);
11
12
  // Inject the shared services and language-specific services
12
13
  const { shared, DomainLang } = createDomainLangServices({ connection, ...NodeFileSystem });
13
14
 
14
- // Initialize workspace on connection
15
- connection.onInitialize(async (params) => {
16
- const workspaceRoot = params.rootUri ? URI.parse(params.rootUri).fsPath : undefined;
15
+ // Initialize workspace manager when language server initializes
16
+ // Uses Langium's LanguageServer.onInitialize hook (not raw connection handler)
17
+ // This integrates properly with Langium's initialization flow
18
+ shared.lsp.LanguageServer.onInitialize((params) => {
19
+ // Use workspaceFolders (preferred) over deprecated rootUri
20
+ const folders = params.workspaceFolders;
21
+ const workspaceRoot = folders?.[0]?.uri
22
+ ? URI.parse(folders[0].uri).fsPath
23
+ : undefined;
17
24
 
18
25
  if (workspaceRoot) {
19
- try {
20
- // Initialize workspace manager
21
- const workspaceManager = DomainLang.imports.WorkspaceManager;
22
- await workspaceManager.initialize(workspaceRoot);
23
- console.warn(`DomainLang workspace initialized: ${workspaceRoot}`);
24
- } catch (error) {
26
+ // Initialize workspace manager synchronously (just sets root path)
27
+ // Heavy work happens in initializeWorkspace() called by Langium later
28
+ const workspaceManager = DomainLang.imports.WorkspaceManager;
29
+ workspaceManager.initialize(workspaceRoot).catch(error => {
25
30
  const message = error instanceof Error ? error.message : String(error);
26
31
  console.warn(`Failed to initialize workspace: ${message}`);
27
32
  // Continue without workspace - local imports will still work
33
+ });
34
+ console.warn(`DomainLang workspace root: ${workspaceRoot}`);
35
+ }
36
+ });
37
+
38
+ // Handle file changes for model.yaml and model.lock (PRS-010)
39
+ // Uses Langium's built-in file watcher which already watches **/* in workspace
40
+ // This invalidates caches when config files change externally
41
+ shared.lsp.DocumentUpdateHandler?.onWatchedFilesChange(async (params) => {
42
+ try {
43
+ await handleConfigFileChanges(params, DomainLang.imports.WorkspaceManager, shared);
44
+ } catch (error) {
45
+ const message = error instanceof Error ? error.message : String(error);
46
+ console.error(`Error handling file change notification: ${message}`);
47
+ // Continue - don't crash the server
48
+ }
49
+ });
50
+
51
+ /**
52
+ * Handles changes to model.yaml and model.lock files.
53
+ * Invalidates caches and rebuilds workspace as needed.
54
+ * Uses incremental updates: only rebuilds if dependencies actually changed.
55
+ */
56
+ async function handleConfigFileChanges(
57
+ params: { changes: Array<{ uri: string; type: number }> },
58
+ workspaceManager: typeof DomainLang.imports.WorkspaceManager,
59
+ sharedServices: typeof shared
60
+ ): Promise<void> {
61
+ let manifestChanged = false;
62
+ let lockFileChanged = false;
63
+
64
+ for (const change of params.changes) {
65
+ const uri = URI.parse(change.uri);
66
+ const fileName = uri.path.split('/').pop() ?? '';
67
+
68
+ if (fileName === 'model.yaml') {
69
+ console.warn(`model.yaml changed: ${change.uri}`);
70
+ workspaceManager.invalidateManifestCache();
71
+ DomainLang.imports.ImportResolver.clearCache();
72
+ // Clear IndexManager import dependencies - resolved paths may have changed
73
+ const indexManager = sharedServices.workspace.IndexManager as DomainLangIndexManager;
74
+ indexManager.clearImportDependencies();
75
+ manifestChanged = true;
76
+ } else if (fileName === 'model.lock') {
77
+ await handleLockFileChange(change, workspaceManager);
78
+ DomainLang.imports.ImportResolver.clearCache();
79
+ lockFileChanged = true;
28
80
  }
29
81
  }
82
+
83
+ // Only rebuild if dependencies changed, not just any manifest change
84
+ if (manifestChanged || lockFileChanged) {
85
+ await rebuildWorkspace(sharedServices, workspaceManager, manifestChanged);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Handles lock file creation, change, or deletion.
91
+ */
92
+ async function handleLockFileChange(
93
+ change: { uri: string; type: number },
94
+ workspaceManager: typeof DomainLang.imports.WorkspaceManager
95
+ ): Promise<void> {
96
+ console.warn(`model.lock changed: ${change.uri}`);
30
97
 
31
- return {
32
- capabilities: {
33
- // Language server capabilities are configured by Langium
98
+ if (change.type === FileChangeType.Changed || change.type === FileChangeType.Created) {
99
+ await workspaceManager.refreshLockFile();
100
+ } else if (change.type === FileChangeType.Deleted) {
101
+ workspaceManager.invalidateLockCache();
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Rebuilds the workspace after config file changes.
107
+ * Uses incremental strategy: only full rebuild if dependencies changed.
108
+ *
109
+ * @param sharedServices - Shared Langium services
110
+ * @param workspaceManager - Workspace manager for manifest access
111
+ * @param manifestChanged - Whether model.yaml changed (vs just model.lock)
112
+ */
113
+ async function rebuildWorkspace(
114
+ sharedServices: typeof shared,
115
+ workspaceManager: typeof DomainLang.imports.WorkspaceManager,
116
+ manifestChanged: boolean
117
+ ): Promise<void> {
118
+ try {
119
+ // If only lock file changed, caches are already invalidated - no rebuild needed
120
+ // Lock file changes mean resolved versions changed, but import resolver cache is cleared
121
+ // Documents will re-resolve imports on next access
122
+ if (!manifestChanged) {
123
+ console.warn('Lock file changed - caches invalidated, no rebuild needed');
124
+ return;
34
125
  }
35
- };
36
- });
126
+
127
+ // For manifest changes, check if dependencies section actually changed
128
+ // If only metadata changed (name, version, etc.), no rebuild needed
129
+ const manifest = await workspaceManager.getManifest();
130
+ const hasDependencies = manifest?.dependencies && Object.keys(manifest.dependencies).length > 0;
131
+
132
+ if (!hasDependencies) {
133
+ console.warn('Manifest changed but has no dependencies - skipping rebuild');
134
+ return;
135
+ }
136
+
137
+ // Dependencies exist and manifest changed - do full rebuild
138
+ const documents = sharedServices.workspace.LangiumDocuments.all.toArray();
139
+ const uris = documents.map(doc => doc.uri);
140
+ await sharedServices.workspace.DocumentBuilder.update([], uris);
141
+ console.warn(`Workspace rebuilt: ${documents.length} documents revalidated`);
142
+ } catch (error) {
143
+ const message = error instanceof Error ? error.message : String(error);
144
+ console.error(`Failed to rebuild workspace: ${message}`);
145
+ }
146
+ }
37
147
 
38
148
  // Optionally start from a single entry file and follow imports.
39
149
  // Configure via env DOMAINLANG_ENTRY (absolute or workspace-relative path)
@@ -63,7 +173,8 @@ if (entryFile) {
63
173
  };
64
174
 
65
175
  // Initial load from entry file, then start the server
66
- reloadFromEntry().finally(() => startLanguageServer(shared));
176
+ await reloadFromEntry();
177
+ startLanguageServer(shared);
67
178
 
68
179
  // Any change within the loaded graph should trigger a reload from the entry
69
180
  shared.workspace.TextDocuments.onDidChangeContent(async (event) => {