@domainlang/language 0.8.0 → 0.9.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.
@@ -16,6 +16,7 @@
16
16
  * - Fluent query chains with lazy iteration
17
17
  * - O(1) indexed lookups by FQN/name
18
18
  * - Resolution rules (which block wins for 0..1 properties)
19
+ * - File validation (Node.js only, via `validateFile()`)
19
20
  *
20
21
  * **Entry points for different deployment targets:**
21
22
  *
@@ -23,27 +24,24 @@
23
24
  * |--------|-------------|--------------|-------|
24
25
  * | VS Code Extension | `fromDocument()` | ✅ | Zero-copy LSP integration |
25
26
  * | Web Editor | `fromDocument()`, `loadModelFromText()` | ✅ | Browser-compatible |
26
- * | CLI (Node.js) | `loadModel()` from `sdk/loader-node` | ❌ | File system access |
27
+ * | CLI (Node.js) | `loadModel()`, `validateFile()` | ❌ | File system access |
27
28
  * | Hosted LSP | `fromDocument()`, `fromServices()` | ✅ | Server-side only |
28
29
  * | Testing | `loadModelFromText()` | ✅ | In-memory parsing |
29
30
  *
30
31
  * ## Browser vs Node.js
31
32
  *
32
- * This module (`sdk/index`) is **browser-safe** and exports only:
33
- * - `loadModelFromText()` - uses EmptyFileSystem
34
- * - `fromModel()`, `fromDocument()`, `fromServices()` - zero-copy wrappers
33
+ * Most of this module is **browser-safe**, but Node.js-specific functions are exported as well:
34
+ * - `loadModel()` - requires Node.js file system (uses NodeFileSystem)
35
+ * - `validateFile()` - requires Node.js file system (uses NodeFileSystem)
35
36
  *
36
- * For file-based loading in Node.js CLI tools:
37
- * ```typescript
38
- * import { loadModel } from 'domain-lang-language/sdk/loader-node';
39
- * ```
37
+ * These will fail at runtime in browser environments.
40
38
  *
41
39
  * @packageDocumentation
42
40
  *
43
41
  * @example
44
42
  * ```typescript
45
- * // Node.js CLI: Load from file (requires sdk/loader-node)
46
- * import { loadModel } from 'domain-lang-language/sdk/loader-node';
43
+ * // Node.js CLI: Load from file
44
+ * import { loadModel } from '@domainlang/language/sdk';
47
45
  *
48
46
  * const { query } = await loadModel('./domains.dlang', {
49
47
  * workspaceDir: process.cwd()
@@ -60,6 +58,24 @@
60
58
  *
61
59
  * @example
62
60
  * ```typescript
61
+ * // Node.js CLI: Validate a model (requires sdk/loader-node)
62
+ * import { validateFile } from '@domainlang/language/sdk';
63
+ *
64
+ * const result = await validateFile('./domains.dlang');
65
+ *
66
+ * if (!result.valid) {
67
+ * for (const error of result.errors) {
68
+ * console.error(`${error.file}:${error.line}: ${error.message}`);
69
+ * }
70
+ * process.exit(1);
71
+ * }
72
+ *
73
+ * console.log(`✓ Validated ${result.fileCount} files`);
74
+ * console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
75
+ * ```
76
+ *
77
+ * @example
78
+ * ```typescript
63
79
  * // Browser/Testing: Load from text (browser-safe)
64
80
  * import { loadModelFromText } from '@domainlang/language/sdk';
65
81
  *
@@ -93,3 +109,6 @@ export { fromModel, fromDocument, fromServices, augmentModel } from './query.js'
93
109
  export { Pattern, PatternFullName, PatternAliases, matchesPattern, isUpstreamPattern, isDownstreamPattern, isMutualPattern, UpstreamPatterns, DownstreamPatterns, MutualPatterns, } from './patterns.js';
94
110
  export type { IntegrationPattern } from './patterns.js';
95
111
  export type { Query, QueryBuilder, QueryContext, LoadOptions, BcQueryBuilder, RelationshipView, } from './types.js';
112
+ export { loadModel } from './loader-node.js';
113
+ export { validateFile, validateWorkspace } from './validator.js';
114
+ export type { ValidationResult, ValidationDiagnostic, ValidationOptions, WorkspaceValidationResult } from './validator.js';
package/out/sdk/index.js CHANGED
@@ -16,6 +16,7 @@
16
16
  * - Fluent query chains with lazy iteration
17
17
  * - O(1) indexed lookups by FQN/name
18
18
  * - Resolution rules (which block wins for 0..1 properties)
19
+ * - File validation (Node.js only, via `validateFile()`)
19
20
  *
20
21
  * **Entry points for different deployment targets:**
21
22
  *
@@ -23,27 +24,24 @@
23
24
  * |--------|-------------|--------------|-------|
24
25
  * | VS Code Extension | `fromDocument()` | ✅ | Zero-copy LSP integration |
25
26
  * | Web Editor | `fromDocument()`, `loadModelFromText()` | ✅ | Browser-compatible |
26
- * | CLI (Node.js) | `loadModel()` from `sdk/loader-node` | ❌ | File system access |
27
+ * | CLI (Node.js) | `loadModel()`, `validateFile()` | ❌ | File system access |
27
28
  * | Hosted LSP | `fromDocument()`, `fromServices()` | ✅ | Server-side only |
28
29
  * | Testing | `loadModelFromText()` | ✅ | In-memory parsing |
29
30
  *
30
31
  * ## Browser vs Node.js
31
32
  *
32
- * This module (`sdk/index`) is **browser-safe** and exports only:
33
- * - `loadModelFromText()` - uses EmptyFileSystem
34
- * - `fromModel()`, `fromDocument()`, `fromServices()` - zero-copy wrappers
33
+ * Most of this module is **browser-safe**, but Node.js-specific functions are exported as well:
34
+ * - `loadModel()` - requires Node.js file system (uses NodeFileSystem)
35
+ * - `validateFile()` - requires Node.js file system (uses NodeFileSystem)
35
36
  *
36
- * For file-based loading in Node.js CLI tools:
37
- * ```typescript
38
- * import { loadModel } from 'domain-lang-language/sdk/loader-node';
39
- * ```
37
+ * These will fail at runtime in browser environments.
40
38
  *
41
39
  * @packageDocumentation
42
40
  *
43
41
  * @example
44
42
  * ```typescript
45
- * // Node.js CLI: Load from file (requires sdk/loader-node)
46
- * import { loadModel } from 'domain-lang-language/sdk/loader-node';
43
+ * // Node.js CLI: Load from file
44
+ * import { loadModel } from '@domainlang/language/sdk';
47
45
  *
48
46
  * const { query } = await loadModel('./domains.dlang', {
49
47
  * workspaceDir: process.cwd()
@@ -60,6 +58,24 @@
60
58
  *
61
59
  * @example
62
60
  * ```typescript
61
+ * // Node.js CLI: Validate a model (requires sdk/loader-node)
62
+ * import { validateFile } from '@domainlang/language/sdk';
63
+ *
64
+ * const result = await validateFile('./domains.dlang');
65
+ *
66
+ * if (!result.valid) {
67
+ * for (const error of result.errors) {
68
+ * console.error(`${error.file}:${error.line}: ${error.message}`);
69
+ * }
70
+ * process.exit(1);
71
+ * }
72
+ *
73
+ * console.log(`✓ Validated ${result.fileCount} files`);
74
+ * console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
75
+ * ```
76
+ *
77
+ * @example
78
+ * ```typescript
63
79
  * // Browser/Testing: Load from text (browser-safe)
64
80
  * import { loadModelFromText } from '@domainlang/language/sdk';
65
81
  *
@@ -94,4 +110,7 @@ export { fromModel, fromDocument, fromServices, augmentModel } from './query.js'
94
110
  // For CLI/Node.js usage: import { loadModel } from '@domainlang/language/sdk/loader-node';
95
111
  // Integration patterns for type-safe pattern matching (no magic strings)
96
112
  export { Pattern, PatternFullName, PatternAliases, matchesPattern, isUpstreamPattern, isDownstreamPattern, isMutualPattern, UpstreamPatterns, DownstreamPatterns, MutualPatterns, } from './patterns.js';
113
+ // Node.js-specific exports (will fail in browser environments)
114
+ export { loadModel } from './loader-node.js';
115
+ export { validateFile, validateWorkspace } from './validator.js';
97
116
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sdk/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AAEH,4BAA4B;AAC5B,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEjF,0EAA0E;AAC1E,2FAA2F;AAE3F,yEAAyE;AACzE,OAAO,EACH,OAAO,EACP,eAAe,EACf,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,GACjB,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sdk/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwGG;AAEH,4BAA4B;AAC5B,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEjF,0EAA0E;AAC1E,2FAA2F;AAE3F,yEAAyE;AACzE,OAAO,EACH,OAAO,EACP,eAAe,EACf,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,GACjB,MAAM,eAAe,CAAC;AAkBvB,+DAA+D;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -49,3 +49,5 @@ import type { LoadOptions, QueryContext } from './types.js';
49
49
  * ```
50
50
  */
51
51
  export declare function loadModel(entryFile: string, options?: LoadOptions): Promise<QueryContext>;
52
+ export { validateFile } from './validator.js';
53
+ export type { ValidationResult, ValidationDiagnostic, ValidationOptions } from './validator.js';
@@ -116,4 +116,6 @@ export async function loadModel(entryFile, options) {
116
116
  query: fromModel(model),
117
117
  };
118
118
  }
119
+ // Re-export validation utilities
120
+ export { validateFile } from './validator.js';
119
121
  //# sourceMappingURL=loader-node.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"loader-node.js","sourceRoot":"","sources":["../../src/sdk/loader-node.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"loader-node.js","sourceRoot":"","sources":["../../src/sdk/loader-node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Model validation utilities for Node.js environments.
3
+ *
4
+ * **WARNING: This module is NOT browser-compatible.**
5
+ *
6
+ * Provides validation capabilities that leverage the LSP infrastructure
7
+ * for workspace initialization, import resolution, and document building.
8
+ *
9
+ * @module sdk/validator
10
+ */
11
+ /**
12
+ * Validation diagnostic with file context.
13
+ */
14
+ export interface ValidationDiagnostic {
15
+ /** Diagnostic severity (1=error, 2=warning, 3=info, 4=hint) */
16
+ severity: number;
17
+ /** Diagnostic message */
18
+ message: string;
19
+ /** File path where diagnostic occurred */
20
+ file: string;
21
+ /** Line number (1-based) */
22
+ line: number;
23
+ /** Column number (1-based) */
24
+ column: number;
25
+ }
26
+ /**
27
+ * Result of model validation.
28
+ */
29
+ export interface ValidationResult {
30
+ /** Whether the model is valid (no errors) */
31
+ valid: boolean;
32
+ /** Number of files validated */
33
+ fileCount: number;
34
+ /** Number of domains in the model */
35
+ domainCount: number;
36
+ /** Number of bounded contexts in the model */
37
+ bcCount: number;
38
+ /** Validation errors */
39
+ errors: ValidationDiagnostic[];
40
+ /** Validation warnings */
41
+ warnings: ValidationDiagnostic[];
42
+ }
43
+ /**
44
+ * Options for validation.
45
+ */
46
+ export interface ValidationOptions {
47
+ /** Workspace directory (defaults to file's directory) */
48
+ workspaceDir?: string;
49
+ }
50
+ /**
51
+ * Validates a DomainLang model file and all its imports.
52
+ *
53
+ * Uses the LSP infrastructure to:
54
+ * - Initialize the workspace
55
+ * - Resolve and load imports
56
+ * - Build and validate all documents
57
+ *
58
+ * @param filePath - Path to the entry .dlang file
59
+ * @param options - Validation options
60
+ * @returns Validation result with errors, warnings, and model statistics
61
+ * @throws Error if file doesn't exist or has invalid extension
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * import { validateFile } from '@domainlang/language/sdk';
66
+ *
67
+ * const result = await validateFile('./index.dlang');
68
+ *
69
+ * if (!result.valid) {
70
+ * for (const err of result.errors) {
71
+ * console.error(`${err.file}:${err.line}:${err.column}: ${err.message}`);
72
+ * }
73
+ * process.exit(1);
74
+ * }
75
+ *
76
+ * console.log(`✓ Validated ${result.fileCount} files`);
77
+ * console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
78
+ * ```
79
+ */
80
+ export declare function validateFile(filePath: string, options?: ValidationOptions): Promise<ValidationResult>;
81
+ /**
82
+ * Workspace validation result with diagnostics grouped by file.
83
+ */
84
+ export interface WorkspaceValidationResult {
85
+ /** Whether the workspace is valid (no errors in any file) */
86
+ valid: boolean;
87
+ /** Number of files validated */
88
+ fileCount: number;
89
+ /** Number of domains across all files */
90
+ domainCount: number;
91
+ /** Number of bounded contexts across all files */
92
+ bcCount: number;
93
+ /** Validation errors grouped by file path */
94
+ errors: ValidationDiagnostic[];
95
+ /** Validation warnings grouped by file path */
96
+ warnings: ValidationDiagnostic[];
97
+ /** Total number of diagnostics across all files */
98
+ totalDiagnostics: number;
99
+ }
100
+ /**
101
+ * Validates an entire DomainLang workspace.
102
+ *
103
+ * Uses the LSP infrastructure to:
104
+ * - Initialize the workspace from a directory containing model.yaml
105
+ * - Load the entry file (from manifest or default index.dlang)
106
+ * - Resolve and load all imports
107
+ * - Build and validate all documents in the workspace
108
+ * - Collect diagnostics from ALL documents (like VS Code Problems pane)
109
+ *
110
+ * @param workspaceDir - Path to the workspace directory (containing model.yaml)
111
+ * @returns Validation result with diagnostics from all files
112
+ * @throws Error if workspace directory doesn't exist or cannot be loaded
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * import { validateWorkspace } from '@domainlang/language/sdk';
117
+ *
118
+ * const result = await validateWorkspace('./my-workspace');
119
+ *
120
+ * if (!result.valid) {
121
+ * console.error(`Found ${result.errors.length} errors in ${result.fileCount} files`);
122
+ *
123
+ * for (const err of result.errors) {
124
+ * console.error(`${err.file}:${err.line}:${err.column}: ${err.message}`);
125
+ * }
126
+ * process.exit(1);
127
+ * }
128
+ *
129
+ * console.log(`✓ Validated ${result.fileCount} files`);
130
+ * console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
131
+ * console.log(` 0 errors, ${result.warnings.length} warnings`);
132
+ * ```
133
+ */
134
+ export declare function validateWorkspace(workspaceDir: string): Promise<WorkspaceValidationResult>;
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Model validation utilities for Node.js environments.
3
+ *
4
+ * **WARNING: This module is NOT browser-compatible.**
5
+ *
6
+ * Provides validation capabilities that leverage the LSP infrastructure
7
+ * for workspace initialization, import resolution, and document building.
8
+ *
9
+ * @module sdk/validator
10
+ */
11
+ import { NodeFileSystem } from 'langium/node';
12
+ import { URI } from 'langium';
13
+ import { createDomainLangServices } from '../domain-lang-module.js';
14
+ import { ensureImportGraphFromDocument } from '../utils/import-utils.js';
15
+ import { isModel } from '../generated/ast.js';
16
+ import { dirname, resolve, join } from 'node:path';
17
+ import { existsSync } from 'node:fs';
18
+ /**
19
+ * Convert Langium diagnostic to ValidationDiagnostic.
20
+ */
21
+ function toValidationDiagnostic(diagnostic, file) {
22
+ return {
23
+ severity: diagnostic.severity ?? 1,
24
+ message: diagnostic.message,
25
+ file,
26
+ line: diagnostic.range.start.line + 1,
27
+ column: diagnostic.range.start.character + 1,
28
+ };
29
+ }
30
+ /**
31
+ * Validates a DomainLang model file and all its imports.
32
+ *
33
+ * Uses the LSP infrastructure to:
34
+ * - Initialize the workspace
35
+ * - Resolve and load imports
36
+ * - Build and validate all documents
37
+ *
38
+ * @param filePath - Path to the entry .dlang file
39
+ * @param options - Validation options
40
+ * @returns Validation result with errors, warnings, and model statistics
41
+ * @throws Error if file doesn't exist or has invalid extension
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * import { validateFile } from '@domainlang/language/sdk';
46
+ *
47
+ * const result = await validateFile('./index.dlang');
48
+ *
49
+ * if (!result.valid) {
50
+ * for (const err of result.errors) {
51
+ * console.error(`${err.file}:${err.line}:${err.column}: ${err.message}`);
52
+ * }
53
+ * process.exit(1);
54
+ * }
55
+ *
56
+ * console.log(`✓ Validated ${result.fileCount} files`);
57
+ * console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
58
+ * ```
59
+ */
60
+ export async function validateFile(filePath, options = {}) {
61
+ // Resolve absolute path
62
+ const absolutePath = resolve(filePath);
63
+ // Check file exists
64
+ if (!existsSync(absolutePath)) {
65
+ throw new Error(`File not found: ${filePath}`);
66
+ }
67
+ // Create services with workspace support
68
+ const servicesObj = createDomainLangServices(NodeFileSystem);
69
+ const shared = servicesObj.shared;
70
+ const services = servicesObj.DomainLang;
71
+ // Check file extension
72
+ const extensions = services.LanguageMetaData.fileExtensions;
73
+ if (!extensions.some(ext => absolutePath.endsWith(ext))) {
74
+ throw new Error(`Invalid file extension. Expected: ${extensions.join(', ')}`);
75
+ }
76
+ // Initialize workspace with the specified directory or file's directory
77
+ const workspaceDir = options.workspaceDir ?? dirname(absolutePath);
78
+ const workspaceManager = services.imports.WorkspaceManager;
79
+ await workspaceManager.initialize(workspaceDir);
80
+ // Load and parse the document
81
+ const uri = URI.file(absolutePath);
82
+ const document = await shared.workspace.LangiumDocuments.getOrCreateDocument(uri);
83
+ // Build document initially without validation to load imports
84
+ await shared.workspace.DocumentBuilder.build([document], { validation: false });
85
+ // Load all imported documents via the import graph
86
+ const importResolver = services.imports.ImportResolver;
87
+ await ensureImportGraphFromDocument(document, shared.workspace.LangiumDocuments, importResolver);
88
+ // Build all documents with validation enabled
89
+ const allDocuments = Array.from(shared.workspace.LangiumDocuments.all);
90
+ await shared.workspace.DocumentBuilder.build(allDocuments, { validation: true });
91
+ // Collect diagnostics from the entry document
92
+ const diagnostics = document.diagnostics ?? [];
93
+ const errors = [];
94
+ const warnings = [];
95
+ for (const diagnostic of diagnostics) {
96
+ const validationDiag = toValidationDiagnostic(diagnostic, absolutePath);
97
+ if (diagnostic.severity === 1) {
98
+ errors.push(validationDiag);
99
+ }
100
+ else if (diagnostic.severity === 2) {
101
+ warnings.push(validationDiag);
102
+ }
103
+ }
104
+ // Count model elements across all documents
105
+ let domainCount = 0;
106
+ let bcCount = 0;
107
+ for (const doc of allDocuments) {
108
+ const model = doc.parseResult?.value;
109
+ if (isModel(model)) {
110
+ for (const element of model.children ?? []) {
111
+ if (element.$type === 'Domain') {
112
+ domainCount++;
113
+ }
114
+ else if (element.$type === 'BoundedContext') {
115
+ bcCount++;
116
+ }
117
+ }
118
+ }
119
+ }
120
+ return {
121
+ valid: errors.length === 0,
122
+ fileCount: allDocuments.length,
123
+ domainCount,
124
+ bcCount,
125
+ errors,
126
+ warnings,
127
+ };
128
+ }
129
+ /**
130
+ * Validates an entire DomainLang workspace.
131
+ *
132
+ * Uses the LSP infrastructure to:
133
+ * - Initialize the workspace from a directory containing model.yaml
134
+ * - Load the entry file (from manifest or default index.dlang)
135
+ * - Resolve and load all imports
136
+ * - Build and validate all documents in the workspace
137
+ * - Collect diagnostics from ALL documents (like VS Code Problems pane)
138
+ *
139
+ * @param workspaceDir - Path to the workspace directory (containing model.yaml)
140
+ * @returns Validation result with diagnostics from all files
141
+ * @throws Error if workspace directory doesn't exist or cannot be loaded
142
+ *
143
+ * @example
144
+ * ```typescript
145
+ * import { validateWorkspace } from '@domainlang/language/sdk';
146
+ *
147
+ * const result = await validateWorkspace('./my-workspace');
148
+ *
149
+ * if (!result.valid) {
150
+ * console.error(`Found ${result.errors.length} errors in ${result.fileCount} files`);
151
+ *
152
+ * for (const err of result.errors) {
153
+ * console.error(`${err.file}:${err.line}:${err.column}: ${err.message}`);
154
+ * }
155
+ * process.exit(1);
156
+ * }
157
+ *
158
+ * console.log(`✓ Validated ${result.fileCount} files`);
159
+ * console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
160
+ * console.log(` 0 errors, ${result.warnings.length} warnings`);
161
+ * ```
162
+ */
163
+ export async function validateWorkspace(workspaceDir) {
164
+ // Resolve absolute path
165
+ const absolutePath = resolve(workspaceDir);
166
+ // Check directory exists
167
+ if (!existsSync(absolutePath)) {
168
+ throw new Error(`Workspace directory not found: ${workspaceDir}`);
169
+ }
170
+ // Create services with workspace support
171
+ const servicesObj = createDomainLangServices(NodeFileSystem);
172
+ const shared = servicesObj.shared;
173
+ const services = servicesObj.DomainLang;
174
+ const workspaceManager = services.imports.WorkspaceManager;
175
+ try {
176
+ // Initialize workspace - this will find and load model.yaml
177
+ await workspaceManager.initialize(absolutePath);
178
+ }
179
+ catch (error) {
180
+ const message = error instanceof Error ? error.message : String(error);
181
+ throw new Error(`Failed to initialize workspace at ${workspaceDir}: ${message}`);
182
+ }
183
+ // Get the manifest to find the entry file
184
+ const manifest = await workspaceManager.getManifest();
185
+ let entryFile = 'index.dlang';
186
+ if (manifest?.model?.entry) {
187
+ entryFile = manifest.model.entry;
188
+ }
189
+ const entryPath = join(absolutePath, entryFile);
190
+ // Check if entry file exists
191
+ if (!existsSync(entryPath)) {
192
+ throw new Error(`Entry file not found: ${entryFile}\n` +
193
+ `Expected at: ${entryPath}\n` +
194
+ (manifest ? `Specified in manifest` : `Using default entry file`));
195
+ }
196
+ // Load and parse the entry document
197
+ const uri = URI.file(entryPath);
198
+ const document = await shared.workspace.LangiumDocuments.getOrCreateDocument(uri);
199
+ // Build document initially without validation to load imports
200
+ await shared.workspace.DocumentBuilder.build([document], { validation: false });
201
+ // Load all imported documents via the import graph
202
+ const importResolver = services.imports.ImportResolver;
203
+ await ensureImportGraphFromDocument(document, shared.workspace.LangiumDocuments, importResolver);
204
+ // Build all documents with validation enabled
205
+ const allDocuments = Array.from(shared.workspace.LangiumDocuments.all);
206
+ await shared.workspace.DocumentBuilder.build(allDocuments, { validation: true });
207
+ // Collect diagnostics from ALL documents (not just entry)
208
+ const errors = [];
209
+ const warnings = [];
210
+ for (const doc of allDocuments) {
211
+ const diagnostics = doc.diagnostics ?? [];
212
+ const docPath = doc.uri.fsPath;
213
+ for (const diagnostic of diagnostics) {
214
+ const validationDiag = toValidationDiagnostic(diagnostic, docPath);
215
+ if (diagnostic.severity === 1) {
216
+ errors.push(validationDiag);
217
+ }
218
+ else if (diagnostic.severity === 2) {
219
+ warnings.push(validationDiag);
220
+ }
221
+ }
222
+ }
223
+ // Count model elements across all documents
224
+ let domainCount = 0;
225
+ let bcCount = 0;
226
+ for (const doc of allDocuments) {
227
+ const model = doc.parseResult?.value;
228
+ if (isModel(model)) {
229
+ for (const element of model.children ?? []) {
230
+ if (element.$type === 'Domain') {
231
+ domainCount++;
232
+ }
233
+ else if (element.$type === 'BoundedContext') {
234
+ bcCount++;
235
+ }
236
+ }
237
+ }
238
+ }
239
+ return {
240
+ valid: errors.length === 0,
241
+ fileCount: allDocuments.length,
242
+ domainCount,
243
+ bcCount,
244
+ errors,
245
+ warnings,
246
+ totalDiagnostics: errors.length + warnings.length,
247
+ };
248
+ }
249
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/sdk/validator.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@domainlang/language",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "displayName": "DomainLang Language",
5
5
  "description": "Core language library for DomainLang - parse, validate, and query Domain-Driven Design models programmatically",
6
6
  "author": "larsbaunwall",
@@ -63,16 +63,15 @@
63
63
  "./syntaxes/domain-lang.monarch": {
64
64
  "types": "./out/syntaxes/domain-lang.monarch.d.ts",
65
65
  "default": "./out/syntaxes/domain-lang.monarch.js"
66
- },
67
- "./src/services/git-url-resolver.js": {
68
- "browser": "./src/services/git-url-resolver.browser.js",
69
- "default": "./src/services/git-url-resolver.js"
70
66
  }
71
67
  },
72
68
  "typesVersions": {
73
69
  "*": {
74
70
  ".": [
75
71
  "out/index"
72
+ ],
73
+ "sdk": [
74
+ "out/sdk/index"
76
75
  ]
77
76
  }
78
77
  },
package/src/sdk/index.ts CHANGED
@@ -16,6 +16,7 @@
16
16
  * - Fluent query chains with lazy iteration
17
17
  * - O(1) indexed lookups by FQN/name
18
18
  * - Resolution rules (which block wins for 0..1 properties)
19
+ * - File validation (Node.js only, via `validateFile()`)
19
20
  *
20
21
  * **Entry points for different deployment targets:**
21
22
  *
@@ -23,27 +24,24 @@
23
24
  * |--------|-------------|--------------|-------|
24
25
  * | VS Code Extension | `fromDocument()` | ✅ | Zero-copy LSP integration |
25
26
  * | Web Editor | `fromDocument()`, `loadModelFromText()` | ✅ | Browser-compatible |
26
- * | CLI (Node.js) | `loadModel()` from `sdk/loader-node` | ❌ | File system access |
27
+ * | CLI (Node.js) | `loadModel()`, `validateFile()` | ❌ | File system access |
27
28
  * | Hosted LSP | `fromDocument()`, `fromServices()` | ✅ | Server-side only |
28
29
  * | Testing | `loadModelFromText()` | ✅ | In-memory parsing |
29
30
  *
30
31
  * ## Browser vs Node.js
31
32
  *
32
- * This module (`sdk/index`) is **browser-safe** and exports only:
33
- * - `loadModelFromText()` - uses EmptyFileSystem
34
- * - `fromModel()`, `fromDocument()`, `fromServices()` - zero-copy wrappers
33
+ * Most of this module is **browser-safe**, but Node.js-specific functions are exported as well:
34
+ * - `loadModel()` - requires Node.js file system (uses NodeFileSystem)
35
+ * - `validateFile()` - requires Node.js file system (uses NodeFileSystem)
35
36
  *
36
- * For file-based loading in Node.js CLI tools:
37
- * ```typescript
38
- * import { loadModel } from 'domain-lang-language/sdk/loader-node';
39
- * ```
37
+ * These will fail at runtime in browser environments.
40
38
  *
41
39
  * @packageDocumentation
42
40
  *
43
41
  * @example
44
42
  * ```typescript
45
- * // Node.js CLI: Load from file (requires sdk/loader-node)
46
- * import { loadModel } from 'domain-lang-language/sdk/loader-node';
43
+ * // Node.js CLI: Load from file
44
+ * import { loadModel } from '@domainlang/language/sdk';
47
45
  *
48
46
  * const { query } = await loadModel('./domains.dlang', {
49
47
  * workspaceDir: process.cwd()
@@ -60,6 +58,24 @@
60
58
  *
61
59
  * @example
62
60
  * ```typescript
61
+ * // Node.js CLI: Validate a model (requires sdk/loader-node)
62
+ * import { validateFile } from '@domainlang/language/sdk';
63
+ *
64
+ * const result = await validateFile('./domains.dlang');
65
+ *
66
+ * if (!result.valid) {
67
+ * for (const error of result.errors) {
68
+ * console.error(`${error.file}:${error.line}: ${error.message}`);
69
+ * }
70
+ * process.exit(1);
71
+ * }
72
+ *
73
+ * console.log(`✓ Validated ${result.fileCount} files`);
74
+ * console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
75
+ * ```
76
+ *
77
+ * @example
78
+ * ```typescript
63
79
  * // Browser/Testing: Load from text (browser-safe)
64
80
  * import { loadModelFromText } from '@domainlang/language/sdk';
65
81
  *
@@ -125,3 +141,8 @@ export type {
125
141
  BcQueryBuilder,
126
142
  RelationshipView,
127
143
  } from './types.js';
144
+
145
+ // Node.js-specific exports (will fail in browser environments)
146
+ export { loadModel } from './loader-node.js';
147
+ export { validateFile, validateWorkspace } from './validator.js';
148
+ export type { ValidationResult, ValidationDiagnostic, ValidationOptions, WorkspaceValidationResult } from './validator.js';
@@ -145,3 +145,7 @@ export async function loadModel(
145
145
  query: fromModel(model),
146
146
  };
147
147
  }
148
+
149
+ // Re-export validation utilities
150
+ export { validateFile } from './validator.js';
151
+ export type { ValidationResult, ValidationDiagnostic, ValidationOptions } from './validator.js';
@@ -0,0 +1,358 @@
1
+ /**
2
+ * Model validation utilities for Node.js environments.
3
+ *
4
+ * **WARNING: This module is NOT browser-compatible.**
5
+ *
6
+ * Provides validation capabilities that leverage the LSP infrastructure
7
+ * for workspace initialization, import resolution, and document building.
8
+ *
9
+ * @module sdk/validator
10
+ */
11
+
12
+ import { NodeFileSystem } from 'langium/node';
13
+ import { URI } from 'langium';
14
+ import { createDomainLangServices } from '../domain-lang-module.js';
15
+ import { ensureImportGraphFromDocument } from '../utils/import-utils.js';
16
+ import { isModel } from '../generated/ast.js';
17
+ import { dirname, resolve, join } from 'node:path';
18
+ import { existsSync } from 'node:fs';
19
+
20
+ /**
21
+ * Validation diagnostic with file context.
22
+ */
23
+ export interface ValidationDiagnostic {
24
+ /** Diagnostic severity (1=error, 2=warning, 3=info, 4=hint) */
25
+ severity: number;
26
+ /** Diagnostic message */
27
+ message: string;
28
+ /** File path where diagnostic occurred */
29
+ file: string;
30
+ /** Line number (1-based) */
31
+ line: number;
32
+ /** Column number (1-based) */
33
+ column: number;
34
+ }
35
+
36
+ /**
37
+ * Result of model validation.
38
+ */
39
+ export interface ValidationResult {
40
+ /** Whether the model is valid (no errors) */
41
+ valid: boolean;
42
+ /** Number of files validated */
43
+ fileCount: number;
44
+ /** Number of domains in the model */
45
+ domainCount: number;
46
+ /** Number of bounded contexts in the model */
47
+ bcCount: number;
48
+ /** Validation errors */
49
+ errors: ValidationDiagnostic[];
50
+ /** Validation warnings */
51
+ warnings: ValidationDiagnostic[];
52
+ }
53
+
54
+ /**
55
+ * Options for validation.
56
+ */
57
+ export interface ValidationOptions {
58
+ /** Workspace directory (defaults to file's directory) */
59
+ workspaceDir?: string;
60
+ }
61
+
62
+ /**
63
+ * Convert Langium diagnostic to ValidationDiagnostic.
64
+ */
65
+ function toValidationDiagnostic(
66
+ diagnostic: { severity?: number; message: string; range: { start: { line: number; character: number } } },
67
+ file: string
68
+ ): ValidationDiagnostic {
69
+ return {
70
+ severity: diagnostic.severity ?? 1,
71
+ message: diagnostic.message,
72
+ file,
73
+ line: diagnostic.range.start.line + 1,
74
+ column: diagnostic.range.start.character + 1,
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Validates a DomainLang model file and all its imports.
80
+ *
81
+ * Uses the LSP infrastructure to:
82
+ * - Initialize the workspace
83
+ * - Resolve and load imports
84
+ * - Build and validate all documents
85
+ *
86
+ * @param filePath - Path to the entry .dlang file
87
+ * @param options - Validation options
88
+ * @returns Validation result with errors, warnings, and model statistics
89
+ * @throws Error if file doesn't exist or has invalid extension
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * import { validateFile } from '@domainlang/language/sdk';
94
+ *
95
+ * const result = await validateFile('./index.dlang');
96
+ *
97
+ * if (!result.valid) {
98
+ * for (const err of result.errors) {
99
+ * console.error(`${err.file}:${err.line}:${err.column}: ${err.message}`);
100
+ * }
101
+ * process.exit(1);
102
+ * }
103
+ *
104
+ * console.log(`✓ Validated ${result.fileCount} files`);
105
+ * console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
106
+ * ```
107
+ */
108
+ export async function validateFile(
109
+ filePath: string,
110
+ options: ValidationOptions = {}
111
+ ): Promise<ValidationResult> {
112
+ // Resolve absolute path
113
+ const absolutePath = resolve(filePath);
114
+
115
+ // Check file exists
116
+ if (!existsSync(absolutePath)) {
117
+ throw new Error(`File not found: ${filePath}`);
118
+ }
119
+
120
+ // Create services with workspace support
121
+ const servicesObj = createDomainLangServices(NodeFileSystem);
122
+ const shared = servicesObj.shared;
123
+ const services = servicesObj.DomainLang;
124
+
125
+ // Check file extension
126
+ const extensions = services.LanguageMetaData.fileExtensions;
127
+ if (!extensions.some(ext => absolutePath.endsWith(ext))) {
128
+ throw new Error(`Invalid file extension. Expected: ${extensions.join(', ')}`);
129
+ }
130
+
131
+ // Initialize workspace with the specified directory or file's directory
132
+ const workspaceDir = options.workspaceDir ?? dirname(absolutePath);
133
+ const workspaceManager = services.imports.WorkspaceManager;
134
+ await workspaceManager.initialize(workspaceDir);
135
+
136
+ // Load and parse the document
137
+ const uri = URI.file(absolutePath);
138
+ const document = await shared.workspace.LangiumDocuments.getOrCreateDocument(uri);
139
+
140
+ // Build document initially without validation to load imports
141
+ await shared.workspace.DocumentBuilder.build([document], { validation: false });
142
+
143
+ // Load all imported documents via the import graph
144
+ const importResolver = services.imports.ImportResolver;
145
+ await ensureImportGraphFromDocument(
146
+ document,
147
+ shared.workspace.LangiumDocuments,
148
+ importResolver
149
+ );
150
+
151
+ // Build all documents with validation enabled
152
+ const allDocuments = Array.from(shared.workspace.LangiumDocuments.all);
153
+ await shared.workspace.DocumentBuilder.build(allDocuments, { validation: true });
154
+
155
+ // Collect diagnostics from the entry document
156
+ const diagnostics = document.diagnostics ?? [];
157
+ const errors: ValidationDiagnostic[] = [];
158
+ const warnings: ValidationDiagnostic[] = [];
159
+
160
+ for (const diagnostic of diagnostics) {
161
+ const validationDiag = toValidationDiagnostic(diagnostic, absolutePath);
162
+ if (diagnostic.severity === 1) {
163
+ errors.push(validationDiag);
164
+ } else if (diagnostic.severity === 2) {
165
+ warnings.push(validationDiag);
166
+ }
167
+ }
168
+
169
+ // Count model elements across all documents
170
+ let domainCount = 0;
171
+ let bcCount = 0;
172
+
173
+ for (const doc of allDocuments) {
174
+ const model = doc.parseResult?.value;
175
+ if (isModel(model)) {
176
+ for (const element of model.children ?? []) {
177
+ if (element.$type === 'Domain') {
178
+ domainCount++;
179
+ } else if (element.$type === 'BoundedContext') {
180
+ bcCount++;
181
+ }
182
+ }
183
+ }
184
+ }
185
+
186
+ return {
187
+ valid: errors.length === 0,
188
+ fileCount: allDocuments.length,
189
+ domainCount,
190
+ bcCount,
191
+ errors,
192
+ warnings,
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Workspace validation result with diagnostics grouped by file.
198
+ */
199
+ export interface WorkspaceValidationResult {
200
+ /** Whether the workspace is valid (no errors in any file) */
201
+ valid: boolean;
202
+ /** Number of files validated */
203
+ fileCount: number;
204
+ /** Number of domains across all files */
205
+ domainCount: number;
206
+ /** Number of bounded contexts across all files */
207
+ bcCount: number;
208
+ /** Validation errors grouped by file path */
209
+ errors: ValidationDiagnostic[];
210
+ /** Validation warnings grouped by file path */
211
+ warnings: ValidationDiagnostic[];
212
+ /** Total number of diagnostics across all files */
213
+ totalDiagnostics: number;
214
+ }
215
+
216
+ /**
217
+ * Validates an entire DomainLang workspace.
218
+ *
219
+ * Uses the LSP infrastructure to:
220
+ * - Initialize the workspace from a directory containing model.yaml
221
+ * - Load the entry file (from manifest or default index.dlang)
222
+ * - Resolve and load all imports
223
+ * - Build and validate all documents in the workspace
224
+ * - Collect diagnostics from ALL documents (like VS Code Problems pane)
225
+ *
226
+ * @param workspaceDir - Path to the workspace directory (containing model.yaml)
227
+ * @returns Validation result with diagnostics from all files
228
+ * @throws Error if workspace directory doesn't exist or cannot be loaded
229
+ *
230
+ * @example
231
+ * ```typescript
232
+ * import { validateWorkspace } from '@domainlang/language/sdk';
233
+ *
234
+ * const result = await validateWorkspace('./my-workspace');
235
+ *
236
+ * if (!result.valid) {
237
+ * console.error(`Found ${result.errors.length} errors in ${result.fileCount} files`);
238
+ *
239
+ * for (const err of result.errors) {
240
+ * console.error(`${err.file}:${err.line}:${err.column}: ${err.message}`);
241
+ * }
242
+ * process.exit(1);
243
+ * }
244
+ *
245
+ * console.log(`✓ Validated ${result.fileCount} files`);
246
+ * console.log(` ${result.domainCount} domains, ${result.bcCount} bounded contexts`);
247
+ * console.log(` 0 errors, ${result.warnings.length} warnings`);
248
+ * ```
249
+ */
250
+ export async function validateWorkspace(
251
+ workspaceDir: string
252
+ ): Promise<WorkspaceValidationResult> {
253
+ // Resolve absolute path
254
+ const absolutePath = resolve(workspaceDir);
255
+
256
+ // Check directory exists
257
+ if (!existsSync(absolutePath)) {
258
+ throw new Error(`Workspace directory not found: ${workspaceDir}`);
259
+ }
260
+
261
+ // Create services with workspace support
262
+ const servicesObj = createDomainLangServices(NodeFileSystem);
263
+ const shared = servicesObj.shared;
264
+ const services = servicesObj.DomainLang;
265
+ const workspaceManager = services.imports.WorkspaceManager;
266
+
267
+ try {
268
+ // Initialize workspace - this will find and load model.yaml
269
+ await workspaceManager.initialize(absolutePath);
270
+ } catch (error) {
271
+ const message = error instanceof Error ? error.message : String(error);
272
+ throw new Error(`Failed to initialize workspace at ${workspaceDir}: ${message}`);
273
+ }
274
+
275
+ // Get the manifest to find the entry file
276
+ const manifest = await workspaceManager.getManifest();
277
+ let entryFile = 'index.dlang';
278
+
279
+ if (manifest?.model?.entry) {
280
+ entryFile = manifest.model.entry;
281
+ }
282
+
283
+ const entryPath = join(absolutePath, entryFile);
284
+
285
+ // Check if entry file exists
286
+ if (!existsSync(entryPath)) {
287
+ throw new Error(
288
+ `Entry file not found: ${entryFile}\n` +
289
+ `Expected at: ${entryPath}\n` +
290
+ (manifest ? `Specified in manifest` : `Using default entry file`)
291
+ );
292
+ }
293
+
294
+ // Load and parse the entry document
295
+ const uri = URI.file(entryPath);
296
+ const document = await shared.workspace.LangiumDocuments.getOrCreateDocument(uri);
297
+
298
+ // Build document initially without validation to load imports
299
+ await shared.workspace.DocumentBuilder.build([document], { validation: false });
300
+
301
+ // Load all imported documents via the import graph
302
+ const importResolver = services.imports.ImportResolver;
303
+ await ensureImportGraphFromDocument(
304
+ document,
305
+ shared.workspace.LangiumDocuments,
306
+ importResolver
307
+ );
308
+
309
+ // Build all documents with validation enabled
310
+ const allDocuments = Array.from(shared.workspace.LangiumDocuments.all);
311
+ await shared.workspace.DocumentBuilder.build(allDocuments, { validation: true });
312
+
313
+ // Collect diagnostics from ALL documents (not just entry)
314
+ const errors: ValidationDiagnostic[] = [];
315
+ const warnings: ValidationDiagnostic[] = [];
316
+
317
+ for (const doc of allDocuments) {
318
+ const diagnostics = doc.diagnostics ?? [];
319
+ const docPath = doc.uri.fsPath;
320
+
321
+ for (const diagnostic of diagnostics) {
322
+ const validationDiag = toValidationDiagnostic(diagnostic, docPath);
323
+
324
+ if (diagnostic.severity === 1) {
325
+ errors.push(validationDiag);
326
+ } else if (diagnostic.severity === 2) {
327
+ warnings.push(validationDiag);
328
+ }
329
+ }
330
+ }
331
+
332
+ // Count model elements across all documents
333
+ let domainCount = 0;
334
+ let bcCount = 0;
335
+
336
+ for (const doc of allDocuments) {
337
+ const model = doc.parseResult?.value;
338
+ if (isModel(model)) {
339
+ for (const element of model.children ?? []) {
340
+ if (element.$type === 'Domain') {
341
+ domainCount++;
342
+ } else if (element.$type === 'BoundedContext') {
343
+ bcCount++;
344
+ }
345
+ }
346
+ }
347
+ }
348
+
349
+ return {
350
+ valid: errors.length === 0,
351
+ fileCount: allDocuments.length,
352
+ domainCount,
353
+ bcCount,
354
+ errors,
355
+ warnings,
356
+ totalDiagnostics: errors.length + warnings.length,
357
+ };
358
+ }