@likec4/language-server 1.32.0 → 1.32.2

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 (45) hide show
  1. package/dist/ast.d.ts +1 -0
  2. package/dist/ast.js +4 -1
  3. package/dist/bundled.mjs +2314 -2312
  4. package/dist/config/schema.d.ts +2 -2
  5. package/dist/config/schema.js +8 -11
  6. package/dist/formatting/LikeC4Formatter.js +2 -2
  7. package/dist/generated/ast.d.ts +30 -6
  8. package/dist/generated/ast.js +44 -9
  9. package/dist/generated/grammar.js +1 -1
  10. package/dist/mcp/LikeC4MCPServerFactory.d.ts +4 -0
  11. package/dist/mcp/LikeC4MCPServerFactory.js +6 -0
  12. package/dist/mcp/sseserver/MCPServer.d.ts +4 -2
  13. package/dist/mcp/sseserver/MCPServer.js +12 -6
  14. package/dist/mcp/sseserver/with-mcp-server.js +22 -6
  15. package/dist/model/model-builder.js +20 -19
  16. package/dist/model/model-parser.d.ts +9 -0
  17. package/dist/model/parser/Base.d.ts +1 -0
  18. package/dist/model/parser/Base.js +22 -0
  19. package/dist/model/parser/DeploymentModelParser.d.ts +1 -0
  20. package/dist/model/parser/DeploymentModelParser.js +7 -5
  21. package/dist/model/parser/DeploymentViewParser.d.ts +1 -0
  22. package/dist/model/parser/FqnRefParser.d.ts +1 -0
  23. package/dist/model/parser/GlobalsParser.d.ts +1 -0
  24. package/dist/model/parser/ImportsParser.d.ts +1 -0
  25. package/dist/model/parser/ModelParser.d.ts +1 -0
  26. package/dist/model/parser/ModelParser.js +7 -5
  27. package/dist/model/parser/PredicatesParser.d.ts +1 -0
  28. package/dist/model/parser/SpecificationParser.d.ts +1 -0
  29. package/dist/model/parser/SpecificationParser.js +4 -2
  30. package/dist/model/parser/ViewsParser.d.ts +1 -0
  31. package/dist/module.d.ts +2 -2
  32. package/dist/module.js +4 -4
  33. package/dist/utils/index.d.ts +5 -0
  34. package/dist/utils/index.js +19 -0
  35. package/dist/validation/index.d.ts +1 -1
  36. package/dist/validation/index.js +10 -3
  37. package/dist/validation/property-checks.d.ts +1 -0
  38. package/dist/validation/property-checks.js +62 -0
  39. package/dist/views/configurable-layouter.d.ts +2 -2
  40. package/dist/views/configurable-layouter.js +4 -2
  41. package/dist/views/likec4-views.d.ts +2 -3
  42. package/dist/views/likec4-views.js +29 -50
  43. package/dist/workspace/ProjectsManager.d.ts +0 -1
  44. package/dist/workspace/ProjectsManager.js +15 -4
  45. package/package.json +16 -16
@@ -2,6 +2,8 @@ import type { ServerOptions } from '@modelcontextprotocol/sdk/server/index.js';
2
2
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import type { LikeC4Services } from '../module';
4
4
  export interface LikeC4MCPServer {
5
+ readonly isStarted: boolean;
6
+ readonly port: number;
5
7
  start(port: number): Promise<void>;
6
8
  stop(): Promise<void>;
7
9
  }
@@ -9,6 +11,8 @@ export interface LikeC4MCPServerFactory {
9
11
  create(options?: ServerOptions): McpServer;
10
12
  }
11
13
  export declare class NoopLikeC4MCPServer implements LikeC4MCPServer {
14
+ get isStarted(): boolean;
15
+ get port(): number;
12
16
  start(port: number): Promise<never>;
13
17
  stop(): Promise<never>;
14
18
  }
@@ -1,4 +1,10 @@
1
1
  export class NoopLikeC4MCPServer {
2
+ get isStarted() {
3
+ return false;
4
+ }
5
+ get port() {
6
+ return NaN;
7
+ }
2
8
  start(port) {
3
9
  return Promise.reject(new Error("Not implemented"));
4
10
  }
@@ -3,10 +3,12 @@ import type { LikeC4Services } from '../../module';
3
3
  import type { LikeC4MCPServer } from '../LikeC4MCPServerFactory';
4
4
  export declare class SSELikeC4MCPServer implements LikeC4MCPServer, AsyncDisposable {
5
5
  private services;
6
- private readonly transports;
6
+ private transports;
7
7
  private server;
8
- private port;
8
+ private _port;
9
9
  constructor(services: LikeC4Services);
10
+ get isStarted(): boolean;
11
+ get port(): number;
10
12
  dispose(): Promise<void>;
11
13
  start(port?: number): Promise<void>;
12
14
  stop(): Promise<void>;
@@ -9,7 +9,13 @@ export class SSELikeC4MCPServer {
9
9
  // Store transports by session ID to send notifications
10
10
  transports = {};
11
11
  server = void 0;
12
- port = 33335;
12
+ _port = 33335;
13
+ get isStarted() {
14
+ return this.server?.listening === true;
15
+ }
16
+ get port() {
17
+ return this._port;
18
+ }
13
19
  async dispose() {
14
20
  await this.stop();
15
21
  }
@@ -20,8 +26,8 @@ export class SSELikeC4MCPServer {
20
26
  }
21
27
  await this.stop();
22
28
  }
23
- logger.info("Starting server on port {port}", { port });
24
- this.port = port;
29
+ logger.info("Starting MCP server on port {port}", { port });
30
+ this._port = port;
25
31
  const mcp = this.services.mcp.ServerFactory.create();
26
32
  const app = express();
27
33
  app.get("/sse", async (_, res) => {
@@ -44,13 +50,12 @@ export class SSELikeC4MCPServer {
44
50
  }
45
51
  });
46
52
  return new Promise((resolve, reject) => {
47
- this.server = app.listen(port, (err) => {
53
+ this.server = app.listen(this._port, (err) => {
48
54
  if (err) {
49
- logger.error("Failed to start server", { err });
50
55
  reject(err);
51
56
  return;
52
57
  }
53
- logger.info("server listening on port {port}", { port });
58
+ logger.info("MCP server listening on port {port}", { port: this._port });
54
59
  resolve();
55
60
  });
56
61
  });
@@ -62,6 +67,7 @@ export class SSELikeC4MCPServer {
62
67
  }
63
68
  logger.info("Stopping server");
64
69
  this.server = void 0;
70
+ this.transports = {};
65
71
  return new Promise((resolve) => {
66
72
  server.close((err) => {
67
73
  if (err) {
@@ -1,3 +1,5 @@
1
+ import { loggable } from "@likec4/log";
2
+ import { isError } from "remeda";
1
3
  import { logger } from "../../logger.js";
2
4
  import { SSELikeC4MCPServer } from "./MCPServer.js";
3
5
  import { LikeC4MCPServerFactory } from "./MCPServerFactory.js";
@@ -14,19 +16,33 @@ export const WithMCPServer = {
14
16
  return;
15
17
  }
16
18
  logger.debug("Configuration update: {update}", { update });
17
- const { enabled, port } = update.configuration.mcp ?? {
18
- enabled: false,
19
- port: 33335
20
- };
19
+ const {
20
+ enabled = false,
21
+ port = 33335
22
+ } = update.configuration.mcp;
21
23
  if (!enabled) {
22
24
  server.stop();
23
25
  return;
24
26
  }
25
27
  Promise.resolve().then(() => server.start(port)).then(() => {
26
28
  connection?.telemetry?.logEvent({
27
- eventName: "mcp-server-started"
29
+ eventName: "mcp-server-started",
30
+ mcpPort: port
28
31
  });
29
- }).catch((err) => logger.error("Failed to start LikeC4 MCP Server", { err }));
32
+ }).catch((err) => {
33
+ const message = isError(err) ? err.message : void 0;
34
+ connection?.telemetry?.logEvent({
35
+ eventName: "mcp-server-start-failed",
36
+ mcpPort: port,
37
+ ...message && { message }
38
+ });
39
+ logger.error("Failed to start LikeC4 MCP Server", { err });
40
+ if (connection) {
41
+ connection.window.showErrorMessage(`LikeC4: Failed to start MCP Server
42
+
43
+ ${loggable(err)}`);
44
+ }
45
+ });
30
46
  });
31
47
  return server;
32
48
  },
@@ -11,7 +11,6 @@ import {
11
11
  DocumentState,
12
12
  interruptAndCheck
13
13
  } from "langium";
14
- import prettyMs from "pretty-ms";
15
14
  import {
16
15
  filter,
17
16
  flatMap,
@@ -26,13 +25,13 @@ import {
26
25
  import { CancellationToken } from "vscode-jsonrpc";
27
26
  import { isLikeC4Builtin } from "../likec4lib.js";
28
27
  import { logger as mainLogger, logWarnError } from "../logger.js";
29
- import { ADisposable } from "../utils/index.js";
28
+ import { ADisposable, performanceMark } from "../utils/index.js";
30
29
  import { assignNavigateTo } from "../view-utils/index.js";
31
30
  import { buildModelData } from "./builder/buildModel.js";
32
31
  const parsedWithoutImportsCacheKey = (projectId) => `parsed-without-imports-${projectId}`;
33
32
  const parsedModelCacheKey = (projectId) => `parsed-model-${projectId}`;
34
33
  const computedModelCacheKey = (projectId) => `computed-model-${projectId}`;
35
- const logger = mainLogger.getChild("ModelBuilder");
34
+ const builderLogger = mainLogger.getChild("model-builder");
36
35
  export class DefaultLikeC4ModelBuilder extends ADisposable {
37
36
  projects;
38
37
  parser;
@@ -65,7 +64,7 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
65
64
  }
66
65
  )
67
66
  );
68
- logger.debug`created`;
67
+ builderLogger.debug`created`;
69
68
  }
70
69
  /**
71
70
  * WARNING:
@@ -76,19 +75,19 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
76
75
  */
77
76
  unsafeSyncParseModelData(projectId) {
78
77
  const cache = this.cache;
79
- const log = logger.getChild(["project", projectId]);
78
+ const logger = builderLogger.getChild(projectId);
80
79
  const key = parsedWithoutImportsCacheKey(projectId);
81
80
  if (cache.has(key)) {
82
- log.debug`unsafeSyncBuildModelData from cache, project ${projectId}`;
81
+ logger.debug`unsafeSyncBuildModelData from cache`;
83
82
  }
84
83
  return cache.get(key, () => {
85
84
  try {
86
85
  const docs = this.documents(projectId);
87
86
  if (docs.length === 0) {
88
- logger.debug`no documents to build model, project ${projectId}`;
87
+ logger.debug`no documents to build model`;
89
88
  return null;
90
89
  }
91
- log.debug`unsafeSyncBuildModelData, project ${projectId}`;
90
+ logger.debug`unsafeSyncBuildModelData`;
92
91
  return buildModelData(projectId, docs);
93
92
  } catch (e) {
94
93
  logWarnError(e);
@@ -100,6 +99,7 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
100
99
  * To avoid circular dependencies, first we parse all documents and then we join them.
101
100
  */
102
101
  unsafeSyncJoinedModelData(projectId) {
102
+ const logger = builderLogger.getChild(projectId);
103
103
  const cache = this.cache;
104
104
  const key = parsedModelCacheKey(projectId);
105
105
  return cache.get(key, () => {
@@ -133,18 +133,18 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
133
133
  }
134
134
  async parseModel(projectId, cancelToken = CancellationToken.None) {
135
135
  const project = this.projects.ensureProjectId(projectId);
136
- const log = logger.getChild(["project", project]);
136
+ const logger = builderLogger.getChild(project);
137
137
  const cache = this.cache;
138
138
  const cached = cache.get(parsedModelCacheKey(project));
139
139
  if (cached) {
140
- log.debug`parseModel from cache, project ${project}`;
140
+ logger.debug`parseModel from cache`;
141
141
  return cached;
142
142
  }
143
- const t0 = performance.now();
143
+ const t0 = performanceMark();
144
144
  return await this.mutex.read(async () => {
145
145
  await interruptAndCheck(cancelToken);
146
146
  const result = this.unsafeSyncJoinedModelData(project);
147
- log.debug(`parseModel, project ${project} in ${prettyMs(performance.now() - t0)}`);
147
+ logger.debug`parseModel in ${t0.pretty}`;
148
148
  return result;
149
149
  });
150
150
  }
@@ -155,6 +155,7 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
155
155
  * Otherwise, the model may be incomplete.
156
156
  */
157
157
  unsafeSyncBuildModel(projectId) {
158
+ const logger = builderLogger.getChild(projectId);
158
159
  const cache = this.cache;
159
160
  const viewsCache = this.cache;
160
161
  return cache.get(computedModelCacheKey(projectId), () => {
@@ -189,24 +190,24 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
189
190
  }
190
191
  async buildLikeC4Model(projectId, cancelToken = CancellationToken.None) {
191
192
  const project = this.projects.ensureProjectId(projectId);
192
- const log = logger.getChild(["project", project]);
193
+ const logger = builderLogger.getChild(project);
193
194
  const cache = this.cache;
194
195
  const cached = cache.get(computedModelCacheKey(project));
195
196
  if (cached) {
196
- log.debug("buildLikeC4Model from cache");
197
+ logger.debug("buildLikeC4Model from cache");
197
198
  return cached;
198
199
  }
199
- const t0 = performance.now();
200
+ const t0 = performanceMark();
200
201
  return await this.mutex.read(async () => {
201
202
  await interruptAndCheck(cancelToken);
202
203
  const result = this.unsafeSyncBuildModel(project);
203
- log.debug(`buildLikeC4Model in ${prettyMs(performance.now() - t0)}`);
204
+ logger.debug(`buildLikeC4Model in ${t0.pretty}`);
204
205
  return result;
205
206
  });
206
207
  }
207
208
  async computeView(viewId, projectId, cancelToken = CancellationToken.None) {
208
209
  const project = this.projects.ensureProjectId(projectId);
209
- const log = logger.getChild(["project", project]);
210
+ const logger = builderLogger.getChild(project);
210
211
  const cache = this.cache;
211
212
  const cacheKey = computedViewKey(project, viewId);
212
213
  if (cache.has(cacheKey)) {
@@ -219,10 +220,10 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
219
220
  return cache.get(cacheKey, () => {
220
221
  const view = parsed.$data.views[viewId];
221
222
  if (!view) {
222
- log.warn`computeView: cant find view ${viewId}`;
223
+ logger.warn`computeView: cant find view ${viewId}`;
223
224
  return null;
224
225
  }
225
- log.debug`computeView: ${viewId}`;
226
+ logger.debug`computeView: ${viewId}`;
226
227
  const result = computeView(view, parsed);
227
228
  if (!result.isSuccess) {
228
229
  logWarnError(result.error);
@@ -72,6 +72,7 @@ declare const DocumentParserFromMixins: {
72
72
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
73
73
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
74
74
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
75
+ parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
75
76
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
76
77
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
77
78
  parseDeploymentView(astNode: import("../generated/ast").DeploymentView): import("../ast").ParsedAstDeploymentView;
@@ -148,6 +149,7 @@ declare const DocumentParserFromMixins: {
148
149
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
149
150
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
150
151
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
152
+ parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
151
153
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
152
154
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
153
155
  parseDeploymentView(astNode: import("../generated/ast").DeploymentView): import("../ast").ParsedAstDeploymentView;
@@ -188,6 +190,7 @@ declare const DocumentParserFromMixins: {
188
190
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
189
191
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
190
192
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
193
+ parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
191
194
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
192
195
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
193
196
  };
@@ -240,6 +243,7 @@ declare const DocumentParserFromMixins: {
240
243
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
241
244
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
242
245
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
246
+ parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
243
247
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
244
248
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
245
249
  };
@@ -285,6 +289,7 @@ declare const DocumentParserFromMixins: {
285
289
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
286
290
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
287
291
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
292
+ parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
288
293
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
289
294
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
290
295
  parseDeployment(): void;
@@ -338,6 +343,7 @@ declare const DocumentParserFromMixins: {
338
343
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
339
344
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
340
345
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
346
+ parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
341
347
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
342
348
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
343
349
  };
@@ -384,6 +390,7 @@ declare const DocumentParserFromMixins: {
384
390
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
385
391
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
386
392
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
393
+ parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
387
394
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
388
395
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
389
396
  };
@@ -412,6 +419,7 @@ declare const DocumentParserFromMixins: {
412
419
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
413
420
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
414
421
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
422
+ parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
415
423
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
416
424
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
417
425
  };
@@ -453,6 +461,7 @@ declare const DocumentParserFromMixins: {
453
461
  convertLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
454
462
  parseLinks(source?: import("../generated/ast").LinkProperty["$container"]): ProjectId[] | undefined;
455
463
  parseIconProperty(prop: import("../generated/ast").IconProperty | undefined): ProjectId | undefined;
464
+ parseColorLiteral(astNode: import("../generated/ast").ColorLiteral): ProjectId | undefined;
456
465
  parseElementStyle(elementProps: Array<import("../generated/ast").ElementProperty> | import("../generated/ast").ElementStyleProperty | undefined): import("../ast").ParsedElementStyle;
457
466
  parseStyleProps(styleProps: Array<import("../generated/ast").StyleProperty> | undefined): import("../ast").ParsedElementStyle;
458
467
  };
@@ -32,6 +32,7 @@ export declare class BaseParser {
32
32
  convertLinks(source?: ast.LinkProperty['$container']): c4.Link[] | undefined;
33
33
  parseLinks(source?: ast.LinkProperty['$container']): c4.Link[] | undefined;
34
34
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
35
+ parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
35
36
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): ParsedElementStyle;
36
37
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): ParsedElementStyle;
37
38
  }
@@ -7,6 +7,8 @@ import {
7
7
  isBoolean,
8
8
  isEmpty,
9
9
  isNonNullish,
10
+ isNumber,
11
+ isString,
10
12
  isTruthy,
11
13
  map,
12
14
  pipe,
@@ -17,6 +19,7 @@ import { hasLeadingSlash, hasProtocol, isRelative, joinRelativeURL, joinURL } fr
17
19
  import {
18
20
  ast,
19
21
  parseAstOpacityProperty,
22
+ parseAstPercent,
20
23
  parseAstSizeValue,
21
24
  toColor
22
25
  } from "../../ast.js";
@@ -154,6 +157,25 @@ export class BaseParser {
154
157
  }
155
158
  }
156
159
  }
160
+ parseColorLiteral(astNode) {
161
+ if (!this.isValid(astNode)) {
162
+ return void 0;
163
+ }
164
+ if (ast.isHexColor(astNode)) {
165
+ return `#${astNode.hex}`;
166
+ }
167
+ if (ast.isRGBAColor(astNode)) {
168
+ let alpha = isNumber(astNode.alpha) ? astNode.alpha : void 0;
169
+ if (isString(astNode.alpha)) {
170
+ alpha = parseAstPercent(astNode.alpha) / 100;
171
+ }
172
+ if (alpha !== void 0) {
173
+ return `rgba(${astNode.red},${astNode.green},${astNode.blue},${alpha})`;
174
+ }
175
+ return `rgb(${astNode.red},${astNode.green},${astNode.blue})`;
176
+ }
177
+ nonexhaustive(astNode);
178
+ }
157
179
  parseElementStyle(elementProps) {
158
180
  if (!elementProps) {
159
181
  return {};
@@ -47,6 +47,7 @@ export declare function DeploymentModelParser<TBase extends WithExpressionV2>(B:
47
47
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
48
48
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
49
49
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
50
+ parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
50
51
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
51
52
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
52
53
  };
@@ -164,9 +164,11 @@ export function DeploymentModelParser(B) {
164
164
  const links = this.convertLinks(astNode.body);
165
165
  const kind = (astNode.kind ?? astNode.dotKind?.kind)?.ref?.name;
166
166
  const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty));
167
- const bodyProps = mapToObj(
168
- astNode.body?.props.filter(ast.isRelationStringProperty) ?? [],
169
- (p) => [p.key, p.value]
167
+ const bodyProps = pipe(
168
+ astNode.body?.props ?? [],
169
+ filter(ast.isRelationStringProperty),
170
+ filter((p) => isTruthy(p.value)),
171
+ mapToObj((p) => [p.key, p.value || void 0])
170
172
  );
171
173
  const navigateTo = pipe(
172
174
  astNode.body?.props ?? [],
@@ -175,8 +177,8 @@ export function DeploymentModelParser(B) {
175
177
  filter(isTruthy),
176
178
  first()
177
179
  );
178
- const title = removeIndent(astNode.title ?? bodyProps.title);
179
- const description = removeIndent(bodyProps.description);
180
+ const title = removeIndent(astNode.title ?? bodyProps.title) ?? "";
181
+ const description = removeIndent(astNode.description ?? bodyProps.description);
180
182
  const technology = toSingleLine(astNode.technology) ?? removeIndent(bodyProps.technology);
181
183
  const styleProp = astNode.body?.props.find(ast.isRelationStyleProperty);
182
184
  const id = stringHash(
@@ -45,6 +45,7 @@ export declare function DeploymentViewParser<TBase extends WithExpressionV2 & Wi
45
45
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
46
46
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
47
47
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
48
+ parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
48
49
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
49
50
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
50
51
  parseDeployment(): void;
@@ -40,6 +40,7 @@ export declare function ExpressionV2Parser<TBase extends Base>(B: TBase): {
40
40
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
41
41
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
42
42
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
43
+ parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
43
44
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
44
45
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
45
46
  };
@@ -69,6 +69,7 @@ export declare function GlobalsParser<TBase extends WithViewsParser>(B: TBase):
69
69
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
70
70
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
71
71
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
72
+ parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
72
73
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
73
74
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
74
75
  parseDeploymentView(astNode: ast.DeploymentView): import("../../ast").ParsedAstDeploymentView;
@@ -26,6 +26,7 @@ export declare function ImportsParser<TBase extends Base>(B: TBase): {
26
26
  convertLinks(source?: ast.LinkProperty["$container"]): ProjectId[] | undefined;
27
27
  parseLinks(source?: ast.LinkProperty["$container"]): ProjectId[] | undefined;
28
28
  parseIconProperty(prop: ast.IconProperty | undefined): ProjectId | undefined;
29
+ parseColorLiteral(astNode: ast.ColorLiteral): ProjectId | undefined;
29
30
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
30
31
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
31
32
  };
@@ -46,6 +46,7 @@ export declare function ModelParser<TBase extends WithExpressionV2>(B: TBase): {
46
46
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
47
47
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
48
48
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
49
+ parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
49
50
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
50
51
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
51
52
  };
@@ -1,7 +1,7 @@
1
1
  import { invariant, isNonEmptyArray, LinkedList, nonexhaustive, nonNullable } from "@likec4/core";
2
2
  import { FqnRef } from "@likec4/core/types";
3
3
  import { loggable } from "@likec4/log";
4
- import { filter, first, isDefined, isEmpty, isNonNullish, isTruthy, map, mapToObj, pipe } from "remeda";
4
+ import { filter, first, isDefined, isEmpty, isTruthy, map, mapToObj, pipe } from "remeda";
5
5
  import {
6
6
  ast,
7
7
  toRelationshipStyleExcludeDefaults
@@ -140,9 +140,11 @@ ${error}`, {
140
140
  const kind = (astNode.kind ?? astNode.dotKind?.kind)?.ref?.name;
141
141
  const metadata = this.getMetadata(astNode.body?.props.find(ast.isMetadataProperty));
142
142
  const astPath = this.getAstNodePath(astNode);
143
- const bodyProps = mapToObj(
144
- astNode.body?.props.filter(ast.isRelationStringProperty).filter((p) => isNonNullish(p.value)) ?? [],
145
- (p) => [p.key, p.value]
143
+ const bodyProps = pipe(
144
+ astNode.body?.props ?? [],
145
+ filter(ast.isRelationStringProperty),
146
+ filter((p) => isTruthy(p.value)),
147
+ mapToObj((p) => [p.key, p.value || void 0])
146
148
  );
147
149
  const navigateTo = pipe(
148
150
  astNode.body?.props ?? [],
@@ -152,7 +154,7 @@ ${error}`, {
152
154
  first()
153
155
  );
154
156
  const title = removeIndent(astNode.title ?? bodyProps.title) ?? "";
155
- const description = removeIndent(bodyProps.description);
157
+ const description = removeIndent(astNode.description ?? bodyProps.description);
156
158
  const technology = toSingleLine(astNode.technology) ?? removeIndent(bodyProps.technology);
157
159
  const styleProp = astNode.body?.props.find(ast.isRelationStyleProperty);
158
160
  const id = stringHash(
@@ -51,6 +51,7 @@ export declare function PredicatesParser<TBase extends WithExpressionV2>(B: TBas
51
51
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
52
52
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
53
53
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
54
+ parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
54
55
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
55
56
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
56
57
  };
@@ -32,6 +32,7 @@ export declare function SpecificationParser<TBase extends Base>(B: TBase): {
32
32
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
33
33
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
34
34
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
35
+ parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
35
36
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
36
37
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
37
38
  };
@@ -1,3 +1,4 @@
1
+ import { nonNullable } from "@likec4/core/utils";
1
2
  import { filter, isNonNullish, isTruthy, mapToObj, pipe } from "remeda";
2
3
  import { ast, toRelationshipStyleExcludeDefaults } from "../../ast.js";
3
4
  import { logger, logWarnError } from "../../logger.js";
@@ -56,10 +57,11 @@ export function SpecificationParser(B) {
56
57
  for (const tagSpec of tags_specs) {
57
58
  const tag = tagSpec.tag.name;
58
59
  const astPath = this.getAstNodePath(tagSpec.tag);
60
+ const color = tagSpec.color && this.parseColorLiteral(tagSpec.color);
59
61
  if (isTruthy(tag)) {
60
62
  c4Specification.tags[tag] = {
61
63
  astPath,
62
- ...tagSpec.color ? { color: tagSpec.color } : {}
64
+ ...color ? { color } : {}
63
65
  };
64
66
  }
65
67
  }
@@ -72,7 +74,7 @@ export function SpecificationParser(B) {
72
74
  continue;
73
75
  }
74
76
  c4Specification.colors[colorName] = {
75
- color
77
+ color: nonNullable(this.parseColorLiteral(color), `Color "${colorName}" is not valid: ${color}`)
76
78
  };
77
79
  } catch (e) {
78
80
  logWarnError(e);
@@ -67,6 +67,7 @@ export declare function ViewsParser<TBase extends WithPredicates & WithDeploymen
67
67
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
68
68
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
69
69
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
70
+ parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
70
71
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
71
72
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
72
73
  parseDeploymentView(astNode: ast.DeploymentView): import("../../ast").ParsedAstDeploymentView;
package/dist/module.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { GraphvizLayouter } from '@likec4/layouts';
1
+ import { QueueGraphvizLayoter } from '@likec4/layouts';
2
2
  import { type Module, WorkspaceCache } from 'langium';
3
3
  import { type DefaultSharedModuleContext, type LangiumServices, type LangiumSharedServices, type PartialLangiumServices } from 'langium/lsp';
4
4
  import { LikeC4DocumentationProvider } from './documentation';
@@ -43,7 +43,7 @@ export interface LikeC4AddedServices {
43
43
  likec4: {
44
44
  LanguageServices: LikeC4LanguageServices;
45
45
  Views: LikeC4Views;
46
- Layouter: GraphvizLayouter;
46
+ Layouter: QueueGraphvizLayoter;
47
47
  DeploymentsIndex: DeploymentsIndex;
48
48
  FqnIndex: FqnIndex;
49
49
  ModelParser: LikeC4ModelParser;
package/dist/module.js CHANGED
@@ -1,4 +1,4 @@
1
- import { GraphvizLayouter, GraphvizWasmAdapter } from "@likec4/layouts";
1
+ import { GraphvizWasmAdapter, QueueGraphvizLayoter } from "@likec4/layouts";
2
2
  import {
3
3
  DocumentState,
4
4
  EmptyFileSystem,
@@ -16,7 +16,6 @@ import {
16
16
  LikeC4GeneratedSharedModule
17
17
  } from "./generated/module.js";
18
18
  import { DefaultLikeC4LanguageServices } from "./LikeC4LanguageServices.js";
19
- import { logger } from "./logger.js";
20
19
  import {
21
20
  LikeC4CodeLensProvider,
22
21
  LikeC4CompletionProvider,
@@ -87,8 +86,9 @@ export const LikeC4Module = {
87
86
  likec4: {
88
87
  LanguageServices: bind(DefaultLikeC4LanguageServices),
89
88
  Layouter: (_services) => {
90
- logger.debug("Creating GraphvizLayouter with GraphvizWasmAdapter");
91
- return new GraphvizLayouter(new GraphvizWasmAdapter());
89
+ return new QueueGraphvizLayoter({
90
+ graphviz: new GraphvizWasmAdapter()
91
+ });
92
92
  },
93
93
  Views: bind(DefaultLikeC4Views),
94
94
  DeploymentsIndex: bind(DeploymentsIndex),
@@ -4,3 +4,8 @@ export * from './fqnRef';
4
4
  export * from './projectId';
5
5
  export * from './stringHash';
6
6
  export declare function safeCall<T>(fn: () => T): T | undefined;
7
+ export declare function performanceNow(): number;
8
+ export declare function performanceMark(): {
9
+ readonly ms: number;
10
+ readonly pretty: string;
11
+ };