@domainlang/language 0.8.0 → 0.10.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/index.d.ts +1 -0
- package/out/index.js +1 -0
- package/out/index.js.map +1 -1
- package/out/lsp/explain.d.ts +18 -0
- package/out/lsp/explain.js +138 -0
- package/out/lsp/explain.js.map +1 -0
- package/out/lsp/tool-handlers.d.ts +113 -0
- package/out/lsp/tool-handlers.js +297 -0
- package/out/lsp/tool-handlers.js.map +1 -0
- package/out/main.js +3 -0
- package/out/main.js.map +1 -1
- package/out/sdk/index.d.ts +31 -10
- package/out/sdk/index.js +31 -10
- package/out/sdk/index.js.map +1 -1
- package/out/sdk/loader-node.d.ts +2 -0
- package/out/sdk/loader-node.js +2 -0
- package/out/sdk/loader-node.js.map +1 -1
- package/out/sdk/serializers.d.ts +110 -0
- package/out/sdk/serializers.js +158 -0
- package/out/sdk/serializers.js.map +1 -0
- package/out/sdk/validator.d.ts +134 -0
- package/out/sdk/validator.js +249 -0
- package/out/sdk/validator.js.map +1 -0
- package/package.json +4 -5
- package/src/index.ts +1 -0
- package/src/lsp/explain.ts +172 -0
- package/src/lsp/tool-handlers.ts +443 -0
- package/src/main.ts +4 -0
- package/src/sdk/index.ts +42 -10
- package/src/sdk/loader-node.ts +4 -0
- package/src/sdk/serializers.ts +213 -0
- package/src/sdk/validator.ts +358 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSP Custom Request Handlers for VS Code Language Model Tools (PRS-015)
|
|
3
|
+
*
|
|
4
|
+
* These handlers respond to custom LSP requests from the VS Code extension
|
|
5
|
+
* and return serialized model data suitable for Language Model Tools.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* - Extension calls `client.sendRequest('domainlang/validate', params)`
|
|
9
|
+
* - LSP receives via `connection.onRequest('domainlang/validate', handler)`
|
|
10
|
+
* - Handler uses SDK `fromServices()` for zero-copy AST access
|
|
11
|
+
* - Handler returns plain JSON (no circular refs, no class instances)
|
|
12
|
+
*
|
|
13
|
+
* @module lsp/tool-handlers
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { Connection } from 'vscode-languageserver';
|
|
17
|
+
import type { LangiumDocument } from 'langium';
|
|
18
|
+
import type { LangiumSharedServices } from 'langium/lsp';
|
|
19
|
+
import { URI } from 'langium';
|
|
20
|
+
import { fromDocument } from '../sdk/query.js';
|
|
21
|
+
import type { Query } from '../sdk/types.js';
|
|
22
|
+
import {
|
|
23
|
+
serializeNode,
|
|
24
|
+
serializeRelationship,
|
|
25
|
+
normalizeEntityType,
|
|
26
|
+
} from '../sdk/serializers.js';
|
|
27
|
+
import type { QueryEntityType, QueryFilters } from '../sdk/serializers.js';
|
|
28
|
+
import type { Model } from '../generated/ast.js';
|
|
29
|
+
import { generateExplanation } from './explain.js';
|
|
30
|
+
|
|
31
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
32
|
+
// Request/Response Types
|
|
33
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Request parameters for domainlang/validate.
|
|
37
|
+
* No parameters needed - validates entire workspace.
|
|
38
|
+
*/
|
|
39
|
+
export interface ValidateParams {
|
|
40
|
+
/** Optional: filter by file URI */
|
|
41
|
+
file?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Response from domainlang/validate.
|
|
46
|
+
*/
|
|
47
|
+
export interface ValidateResponse {
|
|
48
|
+
/** Total number of validation diagnostics */
|
|
49
|
+
count: number;
|
|
50
|
+
/** Validation diagnostics grouped by severity */
|
|
51
|
+
diagnostics: {
|
|
52
|
+
errors: DiagnosticInfo[];
|
|
53
|
+
warnings: DiagnosticInfo[];
|
|
54
|
+
info: DiagnosticInfo[];
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Diagnostic information for validation response.
|
|
60
|
+
*/
|
|
61
|
+
export interface DiagnosticInfo {
|
|
62
|
+
/** File URI */
|
|
63
|
+
file: string;
|
|
64
|
+
/** Line number (1-indexed) */
|
|
65
|
+
line: number;
|
|
66
|
+
/** Column number (1-indexed) */
|
|
67
|
+
column: number;
|
|
68
|
+
/** Diagnostic message */
|
|
69
|
+
message: string;
|
|
70
|
+
/** Severity level */
|
|
71
|
+
severity: 'error' | 'warning' | 'info';
|
|
72
|
+
/** Optional diagnostic code */
|
|
73
|
+
code?: string | number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Request parameters for domainlang/list.
|
|
78
|
+
*/
|
|
79
|
+
export interface ListParams {
|
|
80
|
+
/** Entity type to query */
|
|
81
|
+
type: string;
|
|
82
|
+
/** Optional filters */
|
|
83
|
+
filters?: QueryFilters;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Response from domainlang/list.
|
|
88
|
+
*/
|
|
89
|
+
export interface ListResponse {
|
|
90
|
+
/** Entity type queried */
|
|
91
|
+
entityType: QueryEntityType;
|
|
92
|
+
/** Number of results */
|
|
93
|
+
count: number;
|
|
94
|
+
/** Serialized results */
|
|
95
|
+
results: Record<string, unknown>[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Request parameters for domainlang/get.
|
|
100
|
+
*/
|
|
101
|
+
export interface GetParams {
|
|
102
|
+
/** Fully qualified name of element to retrieve */
|
|
103
|
+
fqn?: string;
|
|
104
|
+
/** If true, return model summary instead of single element */
|
|
105
|
+
summary?: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Response from domainlang/get.
|
|
110
|
+
*/
|
|
111
|
+
export interface GetResponse {
|
|
112
|
+
/** Serialized element or model summary */
|
|
113
|
+
result: Record<string, unknown> | null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Request parameters for domainlang/explain.
|
|
118
|
+
*/
|
|
119
|
+
export interface ExplainParams {
|
|
120
|
+
/** Fully qualified name of element to explain */
|
|
121
|
+
fqn: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Response from domainlang/explain.
|
|
126
|
+
*/
|
|
127
|
+
export interface ExplainResponse {
|
|
128
|
+
/** Rich markdown explanation */
|
|
129
|
+
explanation: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
133
|
+
// Handler Registration
|
|
134
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Registers all custom request handlers on the LSP connection.
|
|
138
|
+
* Call this from main.ts after creating the connection.
|
|
139
|
+
*
|
|
140
|
+
* @param connection - LSP connection
|
|
141
|
+
* @param sharedServices - Langium shared services for workspace access
|
|
142
|
+
*/
|
|
143
|
+
export function registerToolHandlers(
|
|
144
|
+
connection: Connection,
|
|
145
|
+
sharedServices: LangiumSharedServices
|
|
146
|
+
): void {
|
|
147
|
+
connection.onRequest('domainlang/validate', async (params: ValidateParams) => {
|
|
148
|
+
return handleValidate(params, sharedServices);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
connection.onRequest('domainlang/list', async (params: ListParams) => {
|
|
152
|
+
return handleList(params, sharedServices);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
connection.onRequest('domainlang/get', async (params: GetParams) => {
|
|
156
|
+
return handleGet(params, sharedServices);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
connection.onRequest('domainlang/explain', async (params: ExplainParams) => {
|
|
160
|
+
return handleExplain(params, sharedServices);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
165
|
+
// Handler Implementations
|
|
166
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Handles domainlang/validate requests.
|
|
170
|
+
* Aggregates all validation diagnostics from the workspace.
|
|
171
|
+
*/
|
|
172
|
+
async function handleValidate(
|
|
173
|
+
params: ValidateParams,
|
|
174
|
+
sharedServices: LangiumSharedServices
|
|
175
|
+
): Promise<ValidateResponse> {
|
|
176
|
+
try {
|
|
177
|
+
const langiumDocs = sharedServices.workspace.LangiumDocuments;
|
|
178
|
+
const documents = params.file
|
|
179
|
+
? [langiumDocs.getDocument(URI.parse(params.file))]
|
|
180
|
+
: Array.from(langiumDocs.all);
|
|
181
|
+
|
|
182
|
+
const errors: DiagnosticInfo[] = [];
|
|
183
|
+
const warnings: DiagnosticInfo[] = [];
|
|
184
|
+
const info: DiagnosticInfo[] = [];
|
|
185
|
+
|
|
186
|
+
for (const doc of documents) {
|
|
187
|
+
if (!doc) continue;
|
|
188
|
+
|
|
189
|
+
const diagnostics = doc.diagnostics ?? [];
|
|
190
|
+
for (const diag of diagnostics) {
|
|
191
|
+
const diagInfo: DiagnosticInfo = {
|
|
192
|
+
file: doc.uri.toString(),
|
|
193
|
+
line: diag.range.start.line + 1, // 1-indexed
|
|
194
|
+
column: diag.range.start.character + 1, // 1-indexed
|
|
195
|
+
message: diag.message,
|
|
196
|
+
severity: severityToString(diag.severity ?? 1),
|
|
197
|
+
code: diag.code,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
if (diag.severity === 1) {
|
|
201
|
+
errors.push(diagInfo);
|
|
202
|
+
} else if (diag.severity === 2) {
|
|
203
|
+
warnings.push(diagInfo);
|
|
204
|
+
} else {
|
|
205
|
+
info.push(diagInfo);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
count: errors.length + warnings.length + info.length,
|
|
212
|
+
diagnostics: { errors, warnings, info },
|
|
213
|
+
};
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error('domainlang/validate handler error:', error);
|
|
216
|
+
return { count: 0, diagnostics: { errors: [], warnings: [], info: [] } };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Handles domainlang/list requests.
|
|
222
|
+
* Queries entities of a specific type and returns serialized results.
|
|
223
|
+
*/
|
|
224
|
+
async function handleList(
|
|
225
|
+
params: ListParams,
|
|
226
|
+
sharedServices: LangiumSharedServices
|
|
227
|
+
): Promise<ListResponse> {
|
|
228
|
+
try {
|
|
229
|
+
const entityType = normalizeEntityType(params.type);
|
|
230
|
+
const filters = params.filters ?? {};
|
|
231
|
+
|
|
232
|
+
// Get all documents and merge results
|
|
233
|
+
const langiumDocs = sharedServices.workspace.LangiumDocuments;
|
|
234
|
+
const documents = Array.from(langiumDocs.all);
|
|
235
|
+
|
|
236
|
+
const allResults: Record<string, unknown>[] = [];
|
|
237
|
+
const seen = new Set<string>(); // Deduplicate by FQN
|
|
238
|
+
|
|
239
|
+
for (const doc of documents) {
|
|
240
|
+
const query = fromDocument(doc as LangiumDocument<Model>);
|
|
241
|
+
const results = executeListQuery(query, entityType, filters);
|
|
242
|
+
|
|
243
|
+
for (const result of results) {
|
|
244
|
+
const fqn = result.fqn as string | undefined;
|
|
245
|
+
if (fqn && seen.has(fqn)) continue;
|
|
246
|
+
if (fqn) seen.add(fqn);
|
|
247
|
+
allResults.push(result);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
entityType,
|
|
253
|
+
count: allResults.length,
|
|
254
|
+
results: allResults,
|
|
255
|
+
};
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error('domainlang/list handler error:', error);
|
|
258
|
+
return { entityType: normalizeEntityType(params.type), count: 0, results: [] };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Handles domainlang/get requests.
|
|
264
|
+
* Retrieves a single element by FQN or returns a model summary.
|
|
265
|
+
*/
|
|
266
|
+
async function handleGet(
|
|
267
|
+
params: GetParams,
|
|
268
|
+
sharedServices: LangiumSharedServices
|
|
269
|
+
): Promise<GetResponse> {
|
|
270
|
+
try {
|
|
271
|
+
if (params.summary) {
|
|
272
|
+
return { result: await getModelSummary(sharedServices) };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!params.fqn) {
|
|
276
|
+
return { result: null };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Search all documents for the element
|
|
280
|
+
const langiumDocs = sharedServices.workspace.LangiumDocuments;
|
|
281
|
+
const documents = Array.from(langiumDocs.all);
|
|
282
|
+
|
|
283
|
+
for (const doc of documents) {
|
|
284
|
+
const query = fromDocument(doc as LangiumDocument<Model>);
|
|
285
|
+
const element = query.byFqn(params.fqn);
|
|
286
|
+
if (element) {
|
|
287
|
+
return { result: serializeNode(element, query) };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return { result: null };
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error('domainlang/get handler error:', error);
|
|
294
|
+
return { result: null };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Handles domainlang/explain requests.
|
|
300
|
+
* Returns rich markdown explanation of a model element.
|
|
301
|
+
*/
|
|
302
|
+
async function handleExplain(
|
|
303
|
+
params: ExplainParams,
|
|
304
|
+
sharedServices: LangiumSharedServices
|
|
305
|
+
): Promise<ExplainResponse> {
|
|
306
|
+
try {
|
|
307
|
+
const langiumDocs = sharedServices.workspace.LangiumDocuments;
|
|
308
|
+
const documents = Array.from(langiumDocs.all);
|
|
309
|
+
|
|
310
|
+
for (const doc of documents) {
|
|
311
|
+
const query = fromDocument(doc as LangiumDocument<Model>);
|
|
312
|
+
const element = query.byFqn(params.fqn);
|
|
313
|
+
if (element) {
|
|
314
|
+
const explanation = generateExplanation(element);
|
|
315
|
+
return { explanation };
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
explanation: `Element not found: ${params.fqn}`,
|
|
321
|
+
};
|
|
322
|
+
} catch (error) {
|
|
323
|
+
console.error('domainlang/explain handler error:', error);
|
|
324
|
+
return { explanation: `Error explaining element: ${params.fqn}` };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
329
|
+
// Helper Functions
|
|
330
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Executes a list query for a specific entity type.
|
|
334
|
+
*/
|
|
335
|
+
function executeListQuery(
|
|
336
|
+
query: Query,
|
|
337
|
+
entityType: QueryEntityType,
|
|
338
|
+
filters: QueryFilters
|
|
339
|
+
): Record<string, unknown>[] {
|
|
340
|
+
switch (entityType) {
|
|
341
|
+
case 'domains': {
|
|
342
|
+
let builder = query.domains();
|
|
343
|
+
if (filters.name) builder = builder.withName(filters.name);
|
|
344
|
+
if (filters.fqn) builder = builder.withFqn(filters.fqn);
|
|
345
|
+
return builder.toArray().map((d) => serializeNode(d, query));
|
|
346
|
+
}
|
|
347
|
+
case 'bcs': {
|
|
348
|
+
let builder = query.boundedContexts();
|
|
349
|
+
if (filters.domain) builder = builder.inDomain(filters.domain);
|
|
350
|
+
if (filters.team) builder = builder.withTeam(filters.team);
|
|
351
|
+
if (filters.classification)
|
|
352
|
+
builder = builder.withClassification(filters.classification);
|
|
353
|
+
if (filters.metadata) {
|
|
354
|
+
const [key, value] = filters.metadata.split('=');
|
|
355
|
+
builder = builder.withMetadata(key, value);
|
|
356
|
+
}
|
|
357
|
+
if (filters.name) builder = builder.withName(filters.name) as ReturnType<Query['boundedContexts']>;
|
|
358
|
+
if (filters.fqn) builder = builder.withFqn(filters.fqn) as ReturnType<Query['boundedContexts']>;
|
|
359
|
+
return builder.toArray().map((bc) => serializeNode(bc, query));
|
|
360
|
+
}
|
|
361
|
+
case 'teams': {
|
|
362
|
+
let builder = query.teams();
|
|
363
|
+
if (filters.name) builder = builder.withName(filters.name);
|
|
364
|
+
return builder.toArray().map((t) => serializeNode(t, query));
|
|
365
|
+
}
|
|
366
|
+
case 'classifications': {
|
|
367
|
+
let builder = query.classifications();
|
|
368
|
+
if (filters.name) builder = builder.withName(filters.name);
|
|
369
|
+
return builder.toArray().map((c) => serializeNode(c, query));
|
|
370
|
+
}
|
|
371
|
+
case 'relationships': {
|
|
372
|
+
const rels = query.relationships().toArray();
|
|
373
|
+
return rels.map((r) => serializeRelationship(r));
|
|
374
|
+
}
|
|
375
|
+
case 'context-maps': {
|
|
376
|
+
let builder = query.contextMaps();
|
|
377
|
+
if (filters.name) builder = builder.withName(filters.name);
|
|
378
|
+
return builder.toArray().map((cm) => serializeNode(cm, query));
|
|
379
|
+
}
|
|
380
|
+
case 'domain-maps': {
|
|
381
|
+
let builder = query.domainMaps();
|
|
382
|
+
if (filters.name) builder = builder.withName(filters.name);
|
|
383
|
+
return builder.toArray().map((dm) => serializeNode(dm, query));
|
|
384
|
+
}
|
|
385
|
+
default:
|
|
386
|
+
return [];
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Generates a model summary with counts of major entities.
|
|
392
|
+
*/
|
|
393
|
+
async function getModelSummary(
|
|
394
|
+
sharedServices: LangiumSharedServices
|
|
395
|
+
): Promise<Record<string, unknown>> {
|
|
396
|
+
const langiumDocs = sharedServices.workspace.LangiumDocuments;
|
|
397
|
+
const documents = Array.from(langiumDocs.all);
|
|
398
|
+
|
|
399
|
+
let domains = 0;
|
|
400
|
+
let bcs = 0;
|
|
401
|
+
let teams = 0;
|
|
402
|
+
let classifications = 0;
|
|
403
|
+
let relationships = 0;
|
|
404
|
+
let contextMaps = 0;
|
|
405
|
+
let domainMaps = 0;
|
|
406
|
+
|
|
407
|
+
for (const doc of documents) {
|
|
408
|
+
const query = fromDocument(doc as LangiumDocument<Model>);
|
|
409
|
+
domains += query.domains().count();
|
|
410
|
+
bcs += query.boundedContexts().count();
|
|
411
|
+
teams += query.teams().count();
|
|
412
|
+
classifications += query.classifications().count();
|
|
413
|
+
relationships += query.relationships().count();
|
|
414
|
+
contextMaps += query.contextMaps().count();
|
|
415
|
+
domainMaps += query.domainMaps().count();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
$type: 'ModelSummary',
|
|
420
|
+
documentCount: documents.length,
|
|
421
|
+
domains,
|
|
422
|
+
boundedContexts: bcs,
|
|
423
|
+
teams,
|
|
424
|
+
classifications,
|
|
425
|
+
relationships,
|
|
426
|
+
contextMaps,
|
|
427
|
+
domainMaps,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Converts diagnostic severity number to string.
|
|
433
|
+
*/
|
|
434
|
+
function severityToString(severity: number): 'error' | 'warning' | 'info' {
|
|
435
|
+
switch (severity) {
|
|
436
|
+
case 1:
|
|
437
|
+
return 'error';
|
|
438
|
+
case 2:
|
|
439
|
+
return 'warning';
|
|
440
|
+
default:
|
|
441
|
+
return 'info';
|
|
442
|
+
}
|
|
443
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { createConnection, ProposedFeatures, FileChangeType } from 'vscode-langu
|
|
|
4
4
|
import { createDomainLangServices } from './domain-lang-module.js';
|
|
5
5
|
import { ensureImportGraphFromEntryFile } from './utils/import-utils.js';
|
|
6
6
|
import { DomainLangIndexManager } from './lsp/domain-lang-index-manager.js';
|
|
7
|
+
import { registerToolHandlers } from './lsp/tool-handlers.js';
|
|
7
8
|
import { URI } from 'langium';
|
|
8
9
|
|
|
9
10
|
// Create a connection to the client
|
|
@@ -12,6 +13,9 @@ const connection = createConnection(ProposedFeatures.all);
|
|
|
12
13
|
// Inject the shared services and language-specific services
|
|
13
14
|
const { shared, DomainLang } = createDomainLangServices({ connection, ...NodeFileSystem });
|
|
14
15
|
|
|
16
|
+
// Register custom LSP request handlers for VS Code Language Model Tools (PRS-015)
|
|
17
|
+
registerToolHandlers(connection, shared);
|
|
18
|
+
|
|
15
19
|
// Initialize workspace manager when language server initializes
|
|
16
20
|
// Uses Langium's LanguageServer.onInitialize hook (not raw connection handler)
|
|
17
21
|
// This integrates properly with Langium's initialization flow
|
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()
|
|
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
|
-
*
|
|
33
|
-
* - `
|
|
34
|
-
* - `
|
|
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
|
-
*
|
|
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
|
|
46
|
-
* import { loadModel } from '
|
|
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,19 @@ export type {
|
|
|
125
141
|
BcQueryBuilder,
|
|
126
142
|
RelationshipView,
|
|
127
143
|
} from './types.js';
|
|
144
|
+
|
|
145
|
+
// Serializers for tool responses (browser-safe)
|
|
146
|
+
export {
|
|
147
|
+
serializeNode,
|
|
148
|
+
serializeRelationship,
|
|
149
|
+
resolveName,
|
|
150
|
+
resolveMultiReference,
|
|
151
|
+
normalizeEntityType,
|
|
152
|
+
ENTITY_ALIASES,
|
|
153
|
+
} from './serializers.js';
|
|
154
|
+
export type { QueryEntityType, QueryEntityInput, QueryFilters } from './serializers.js';
|
|
155
|
+
|
|
156
|
+
// Node.js-specific exports (will fail in browser environments)
|
|
157
|
+
export { loadModel } from './loader-node.js';
|
|
158
|
+
export { validateFile, validateWorkspace } from './validator.js';
|
|
159
|
+
export type { ValidationResult, ValidationDiagnostic, ValidationOptions, WorkspaceValidationResult } from './validator.js';
|
package/src/sdk/loader-node.ts
CHANGED
|
@@ -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';
|