@likec4/language-server 1.44.0 → 1.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/LikeC4LanguageServices.d.ts +1 -15
  2. package/dist/LikeC4LanguageServices.js +2 -32
  3. package/dist/Rpc.js +32 -20
  4. package/dist/ast.js +6 -2
  5. package/dist/browser.js +2 -2
  6. package/dist/bundled.js +2 -0
  7. package/dist/bundled.mjs +3184 -3162
  8. package/dist/filesystem/ChokidarWatcher.d.ts +2 -0
  9. package/dist/filesystem/ChokidarWatcher.js +27 -16
  10. package/dist/filesystem/LikeC4FileSystem.js +1 -1
  11. package/dist/index.d.ts +3 -1
  12. package/dist/index.js +5 -3
  13. package/dist/mcp/server/StdioLikeC4MCPServer.js +10 -6
  14. package/dist/mcp/server/StreamableLikeC4MCPServer.js +97 -97
  15. package/dist/mcp/server/WithMCPServer.js +5 -5
  16. package/dist/mcp/tools/search-element.js +5 -5
  17. package/dist/mcp/utils.js +1 -1
  18. package/dist/model/deployments-index.js +2 -2
  19. package/dist/model/fqn-index.d.ts +1 -2
  20. package/dist/model/fqn-index.js +13 -16
  21. package/dist/model/model-builder.js +0 -2
  22. package/dist/model/model-parser.js +34 -27
  23. package/dist/model/parser/SpecificationParser.js +4 -0
  24. package/dist/model/parser/ViewsParser.js +3 -1
  25. package/dist/model-change/ModelChanges.d.ts +2 -2
  26. package/dist/model-change/ModelChanges.js +36 -9
  27. package/dist/protocol.d.ts +33 -10
  28. package/dist/protocol.js +13 -4
  29. package/dist/view-utils/manual-layout.js +2 -4
  30. package/dist/views/LikeC4ManualLayouts.d.ts +16 -2
  31. package/dist/views/LikeC4ManualLayouts.js +99 -22
  32. package/dist/views/LikeC4Views.d.ts +26 -5
  33. package/dist/views/LikeC4Views.js +49 -33
  34. package/dist/workspace/AstNodeDescriptionProvider.js +6 -3
  35. package/dist/workspace/IndexManager.js +1 -1
  36. package/dist/workspace/LangiumDocuments.d.ts +3 -2
  37. package/dist/workspace/LangiumDocuments.js +29 -15
  38. package/dist/workspace/ProjectsManager.d.ts +19 -15
  39. package/dist/workspace/ProjectsManager.js +137 -41
  40. package/dist/workspace/WorkspaceManager.js +5 -0
  41. package/package.json +16 -15
@@ -7,8 +7,10 @@ export declare const chokidarFileSystemWatcher: FileSystemWatcherModuleContext;
7
7
  export declare class ChokidarFileSystemWatcher implements FileSystemWatcher {
8
8
  protected services: LikeC4SharedServices;
9
9
  private watcher?;
10
+ private queue;
10
11
  constructor(services: LikeC4SharedServices);
11
12
  watch(folder: string): void;
12
13
  dispose(): Promise<void>;
13
14
  private createWatcher;
15
+ private enqueueFileOp;
14
16
  }
@@ -2,6 +2,7 @@ import { isLikeC4Config } from '@likec4/config/node';
2
2
  import { loggable } from '@likec4/log';
3
3
  import chokidar from 'chokidar';
4
4
  import { URI } from 'langium';
5
+ import PQueue from 'p-queue';
5
6
  import { logger as mainLogger } from '../logger';
6
7
  import { isManualLayoutFile } from '../views/LikeC4ManualLayouts';
7
8
  import { isLikeC4File } from './LikeC4FileSystem';
@@ -16,6 +17,7 @@ const isAnyLikeC4File = (path) => isLikeC4File(path) || isLikeC4Config(path) ||
16
17
  export class ChokidarFileSystemWatcher {
17
18
  services;
18
19
  watcher;
20
+ queue = new PQueue({ concurrency: 1, timeout: 5000 });
19
21
  constructor(services) {
20
22
  this.services = services;
21
23
  }
@@ -30,8 +32,9 @@ export class ChokidarFileSystemWatcher {
30
32
  }
31
33
  async dispose() {
32
34
  if (this.watcher) {
33
- await this.watcher.close();
35
+ const watcher = this.watcher;
34
36
  this.watcher = undefined;
37
+ await watcher.close();
35
38
  }
36
39
  return;
37
40
  }
@@ -41,10 +44,11 @@ export class ChokidarFileSystemWatcher {
41
44
  path => path.includes('node_modules') || path.includes('.git'),
42
45
  (path, stats) => (!!stats && stats.isFile() && !isAnyLikeC4File(path)),
43
46
  ],
47
+ followSymlinks: true,
44
48
  ignoreInitial: true,
45
49
  });
46
- const onAddOrChange = async (path) => {
47
- try {
50
+ const onAddOrChange = (path) => {
51
+ this.enqueueFileOp('addOrChange: ' + path, async () => {
48
52
  if (isLikeC4Config(path)) {
49
53
  logger.debug `project file changed: ${path}`;
50
54
  await this.services.workspace.ProjectsManager.reloadProjects();
@@ -61,16 +65,14 @@ export class ChokidarFileSystemWatcher {
61
65
  else {
62
66
  logger.warn `Unknown file change: ${path}`;
63
67
  }
64
- }
65
- catch (error) {
66
- logger.error(loggable(error));
67
- }
68
+ });
68
69
  };
69
- const onRemove = async (path) => {
70
- try {
70
+ const onRemove = (path) => {
71
+ this.enqueueFileOp('remove: ' + path, async () => {
72
+ const pm = this.services.workspace.ProjectsManager;
71
73
  if (isLikeC4Config(path)) {
72
74
  logger.debug `project file removed: ${path}`;
73
- await this.services.workspace.ProjectsManager.reloadProjects();
75
+ await pm.reloadProjects();
74
76
  }
75
77
  else if (isLikeC4File(path)) {
76
78
  logger.debug `file removed: ${path}`;
@@ -78,20 +80,29 @@ export class ChokidarFileSystemWatcher {
78
80
  }
79
81
  else if (isManualLayoutFile(path)) {
80
82
  logger.debug `manual layout file removed: ${path}`;
81
- // TODO: optimize to only reload manual layouts instead of all projects
82
- await this.services.workspace.ProjectsManager.reloadProjects();
83
+ const project = pm.belongsTo(path);
84
+ await pm.rebuidProject(project);
83
85
  }
84
86
  else {
85
87
  logger.warn `Unknown file removal: ${path}`;
86
88
  }
87
- }
88
- catch (error) {
89
- logger.error(loggable(error));
90
- }
89
+ });
91
90
  };
92
91
  watcher.on('add', onAddOrChange)
93
92
  .on('change', onAddOrChange)
94
93
  .on('unlink', onRemove);
95
94
  return watcher;
96
95
  }
96
+ enqueueFileOp(fileop, fn) {
97
+ this.queue.add(async () => {
98
+ try {
99
+ await fn();
100
+ }
101
+ catch (error) {
102
+ logger.warn(`Failed on ${fileop}`, { error });
103
+ }
104
+ }).catch(error => {
105
+ logger.error(loggable(error));
106
+ });
107
+ }
97
108
  }
@@ -4,7 +4,7 @@ import { URI } from 'langium';
4
4
  import { NodeFileSystemProvider } from 'langium/node';
5
5
  import { mkdirSync } from 'node:fs';
6
6
  import { stat, unlink, writeFile } from 'node:fs/promises';
7
- import { dirname } from 'node:path';
7
+ import { basename, dirname } from 'node:path';
8
8
  import { LikeC4LanguageMetaData } from '../generated/module';
9
9
  import { Content, isLikeC4Builtin } from '../likec4lib';
10
10
  import { logger as rootLogger } from '../logger';
package/dist/index.d.ts CHANGED
@@ -23,7 +23,9 @@ type StartLanguageServerOptions = {
23
23
  * Whether to enable the MCP server.
24
24
  * @default 'sse'
25
25
  */
26
- enableMCP?: false | 'sse' | 'stdio';
26
+ enableMCP?: false | 'stdio' | 'sse' | {
27
+ port: number;
28
+ };
27
29
  /**
28
30
  * Whether to enable manual layouts, stored in json5 files.
29
31
  * @default true
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
- import { configureLogger, getConsoleSink, getTextFormatter } from '@likec4/log';
1
+ import { configureLogger, getConsoleSink, getConsoleStderrSink, getTextFormatter } from '@likec4/log';
2
2
  import { defu } from 'defu';
3
+ import { DEV } from 'esm-env';
3
4
  import { startLanguageServer as startLanguim } from 'langium/lsp';
4
5
  import { createConnection, ProposedFeatures } from 'vscode-languageserver/node';
5
6
  import { NoopFileSystem } from './filesystem';
@@ -23,7 +24,7 @@ export function startLanguageServer(options) {
23
24
  const connection = createConnection(ProposedFeatures.all);
24
25
  configureLogger({
25
26
  sinks: {
26
- console: getConsoleSink({
27
+ console: opts.enableMCP === 'stdio' ? getConsoleStderrSink() : getConsoleSink({
27
28
  formatter: getTextFormatter(),
28
29
  }),
29
30
  telemetry: getTelemetrySink(connection),
@@ -32,6 +33,7 @@ export function startLanguageServer(options) {
32
33
  {
33
34
  category: ['likec4'],
34
35
  sinks: ['console', 'telemetry'],
36
+ lowestLevel: DEV ? 'trace' : 'debug',
35
37
  },
36
38
  ],
37
39
  });
@@ -40,7 +42,7 @@ export function startLanguageServer(options) {
40
42
  const services = createLanguageServices({
41
43
  connection,
42
44
  ...LikeC4FileSystem(opts.enableWatcher),
43
- ...opts.enableMCP && WithMCPServer(opts.enableMCP),
45
+ ...!!opts.enableMCP && WithMCPServer(opts.enableMCP),
44
46
  ...opts.enableManualLayouts && WithLikeC4ManualLayouts,
45
47
  }, {
46
48
  likec4: {
@@ -36,12 +36,16 @@ export class StdioLikeC4MCPServer {
36
36
  if (!this.transport) {
37
37
  return;
38
38
  }
39
- logger.info('Stopping MCP stdio server');
40
- await this.transport.close();
41
- if (this._mcp) {
42
- await this._mcp.close();
39
+ try {
40
+ logger.info('Stopping MCP stdio server');
41
+ await this.transport.close();
42
+ if (this._mcp) {
43
+ await this._mcp.close();
44
+ }
45
+ }
46
+ finally {
47
+ this._mcp = undefined;
48
+ this.transport = undefined;
43
49
  }
44
- this._mcp = undefined;
45
- this.transport = undefined;
46
50
  }
47
51
  }
@@ -1,10 +1,91 @@
1
1
  import { serve } from '@hono/node-server';
2
- import { promiseNextTick } from '@likec4/core/utils';
3
2
  import { loggable } from '@likec4/log';
4
3
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
5
4
  import { toFetchResponse, toReqRes } from 'fetch-to-node';
6
5
  import { Hono } from 'hono';
7
6
  import { logger } from '../utils';
7
+ function createHonoApp(factory) {
8
+ return new Hono()
9
+ .post('/mcp', async (c) => {
10
+ const { req, res } = toReqRes(c.req.raw);
11
+ const mcp = factory.create();
12
+ try {
13
+ const transport = new StreamableHTTPServerTransport({
14
+ sessionIdGenerator: undefined,
15
+ });
16
+ // Added for extra debuggability
17
+ transport.onerror = (err) => {
18
+ logger.error(loggable(err));
19
+ };
20
+ await mcp.connect(transport);
21
+ await transport.handleRequest(req, res, await c.req.json());
22
+ res.on('close', async () => {
23
+ await transport.close();
24
+ await mcp.close();
25
+ });
26
+ return toFetchResponse(res);
27
+ }
28
+ catch (e) {
29
+ logger.error(loggable(e));
30
+ return c.json({
31
+ jsonrpc: '2.0',
32
+ error: {
33
+ code: -32603,
34
+ message: 'Internal server error',
35
+ },
36
+ id: null,
37
+ }, { status: 500 });
38
+ }
39
+ })
40
+ .all('/mcp', async (c) => {
41
+ return c.json({
42
+ jsonrpc: '2.0',
43
+ error: {
44
+ code: -32000,
45
+ message: 'Method not allowed.',
46
+ },
47
+ id: null,
48
+ }, { status: 405 });
49
+ })
50
+ .notFound((c) => {
51
+ logger.debug(`${c.req.method} ${c.req.url} not found`);
52
+ return c.json({
53
+ jsonrpc: '2.0',
54
+ error: {
55
+ code: -32000,
56
+ message: 'Method not found.',
57
+ },
58
+ id: null,
59
+ }, { status: 404 });
60
+ })
61
+ .onError((e, c) => {
62
+ logger.error(loggable(e));
63
+ return c.json({
64
+ jsonrpc: '2.0',
65
+ error: {
66
+ code: -32603,
67
+ message: 'Internal server error',
68
+ },
69
+ id: null,
70
+ }, { status: 500 });
71
+ });
72
+ }
73
+ function startServer(params) {
74
+ return new Promise((resolve, reject) => {
75
+ const { factory, port } = params;
76
+ const app = createHonoApp(factory);
77
+ const server = serve({
78
+ fetch: app.fetch,
79
+ hostname: '0.0.0.0',
80
+ port,
81
+ })
82
+ .prependOnceListener('error', reject)
83
+ .prependOnceListener('listening', () => {
84
+ server.removeListener('error', reject);
85
+ resolve(server.unref());
86
+ });
87
+ });
88
+ }
8
89
  export class StreamableLikeC4MCPServer {
9
90
  services;
10
91
  _port;
@@ -27,107 +108,24 @@ export class StreamableLikeC4MCPServer {
27
108
  await this.stop();
28
109
  }
29
110
  async start(port = this._port) {
30
- try {
31
- if (this.server) {
32
- if (this.port === port) {
33
- return;
34
- }
35
- await this.stop();
111
+ if (this.server) {
112
+ if (this.port === port) {
113
+ return;
36
114
  }
37
- logger.info('Starting MCP server on port {port}', { port });
38
- this._port = port;
39
- await promiseNextTick();
40
- const app = new Hono()
41
- .post('/mcp', async (c) => {
42
- const { req, res } = toReqRes(c.req.raw);
43
- const server = this.services.mcp.ServerFactory.create();
44
- try {
45
- const transport = new StreamableHTTPServerTransport({
46
- sessionIdGenerator: undefined,
47
- });
48
- // Added for extra debuggability
49
- transport.onerror = (err) => {
50
- logger.error(loggable(err));
51
- };
52
- await server.connect(transport);
53
- await transport.handleRequest(req, res, await c.req.json());
54
- res.on('close', async () => {
55
- logger.debug('Request closed');
56
- await transport.close();
57
- await server.close();
58
- });
59
- return toFetchResponse(res);
60
- }
61
- catch (e) {
62
- logger.error(loggable(e));
63
- return c.json({
64
- jsonrpc: '2.0',
65
- error: {
66
- code: -32603,
67
- message: 'Internal server error',
68
- },
69
- id: null,
70
- }, { status: 500 });
71
- }
72
- })
73
- .get('/mcp', async (c) => {
74
- logger.debug('Received GET MCP request');
75
- return c.json({
76
- jsonrpc: '2.0',
77
- error: {
78
- code: -32000,
79
- message: 'Method not allowed.',
80
- },
81
- id: null,
82
- }, { status: 405 });
83
- })
84
- .delete('/mcp', async (c) => {
85
- logger.debug('Received DELETE MCP request');
86
- return c.json({
87
- jsonrpc: '2.0',
88
- error: {
89
- code: -32000,
90
- message: 'Method not allowed.',
91
- },
92
- id: null,
93
- }, { status: 405 });
94
- })
95
- .notFound((c) => {
96
- logger.debug(`${c.req.method} ${c.req.url} not found`);
97
- return c.json({
98
- jsonrpc: '2.0',
99
- error: {
100
- code: -32000,
101
- message: 'Method not found.',
102
- },
103
- id: null,
104
- }, { status: 404 });
105
- })
106
- .onError((e, c) => {
107
- logger.error(loggable(e));
108
- return c.json({
109
- jsonrpc: '2.0',
110
- error: {
111
- code: -32603,
112
- message: 'Internal server error',
113
- },
114
- id: null,
115
- }, { status: 500 });
116
- });
117
- this.server = serve({
118
- fetch: app.fetch,
119
- hostname: '0.0.0.0',
120
- port: this._port,
121
- }, (info) => logger.info('MCP server ready at http://0.0.0.0:{port}/mcp', { port: info.port }));
122
- }
123
- catch (err) {
124
- logger.warn('Failed to start MCP server', { err });
125
- return Promise.reject(err);
115
+ await this.stop();
126
116
  }
117
+ logger.info('Starting MCP server on port {port}', { port });
118
+ this._port = port;
119
+ this.server = await startServer({
120
+ factory: this.services.mcp.ServerFactory,
121
+ port,
122
+ });
123
+ logger.info('MCP server ready at http://0.0.0.0:{port}/mcp', { port });
127
124
  }
128
125
  stop() {
129
126
  const server = this.server;
130
127
  if (!server) {
128
+ logger.info('MCP server is not running, nothing to stop');
131
129
  return Promise.resolve();
132
130
  }
133
131
  logger.info('Stopping MCP server');
@@ -137,7 +135,9 @@ export class StreamableLikeC4MCPServer {
137
135
  if (err) {
138
136
  logger.error('Failed to stop MCP server', { err });
139
137
  }
140
- logger.info('MCP server stopped');
138
+ else {
139
+ logger.info('MCP server stopped');
140
+ }
141
141
  resolve();
142
142
  });
143
143
  });
@@ -1,4 +1,5 @@
1
1
  import { loggable } from '@likec4/log';
2
+ import { error } from 'console';
2
3
  import { isError } from 'remeda';
3
4
  import { logger } from '../utils';
4
5
  import { StdioLikeC4MCPServer } from './StdioLikeC4MCPServer';
@@ -13,7 +14,6 @@ const streamableLikeC4MCPServer = (services, defaultPort = 33335) => {
13
14
  logger.warn('Unexpected configuration update: {update}', { update });
14
15
  return;
15
16
  }
16
- logger.debug('Configuration update: {update}', { update });
17
17
  const { enabled = false, port = defaultPort, } = update.configuration.mcp;
18
18
  if (!enabled) {
19
19
  void server.stop();
@@ -28,15 +28,15 @@ const streamableLikeC4MCPServer = (services, defaultPort = 33335) => {
28
28
  });
29
29
  })
30
30
  .catch(err => {
31
- const message = isError(err) ? err.message : undefined;
31
+ const message = loggable(err);
32
32
  connection?.telemetry?.logEvent({
33
33
  eventName: 'mcp-server-start-failed',
34
34
  mcpPort: port,
35
- ...message && { message },
35
+ message,
36
36
  });
37
- logger.error('Failed to start LikeC4 MCP Server', { err });
37
+ logger.warn(`Failed to start LikeC4 MCP Server: \n${message}`);
38
38
  if (connection) {
39
- connection.window.showErrorMessage(`LikeC4: Failed to start MCP Server\n\n${loggable(err)}`);
39
+ connection.window.showErrorMessage(`LikeC4: Failed to start MCP Server\n\n${message}`);
40
40
  }
41
41
  });
42
42
  });
@@ -41,11 +41,11 @@ export const searchElement = likec4Tool({
41
41
  Search LikeC4 elements and deployment nodes across all projects.
42
42
 
43
43
  Query syntax (case-insensitive):
44
- - kind:<value>: filters by kind
45
- - shape:<value>: filters by shape
46
- - meta:<key>: filters by having metadata with the given key
47
- - #<value>: matches assigned tags
48
- - Free text: matches id (FQN) or title
44
+ - kind:<value> filters by kind
45
+ - shape:<value> filters by shape
46
+ - meta:<key> filters by having metadata with the given key
47
+ - #<value> matches assigned tags
48
+ - <value> matches id (FQN) or title
49
49
 
50
50
  Request:
51
51
  - search: string — at least 2 characters
package/dist/mcp/utils.js CHANGED
@@ -39,7 +39,7 @@ function mkcallTool(name, languageServices, cb) {
39
39
  return {
40
40
  content: [{
41
41
  type: 'text',
42
- text: err instanceof Error ? err.message : loggable(err),
42
+ text: loggable(err),
43
43
  }],
44
44
  isError: true,
45
45
  };
@@ -9,7 +9,7 @@ export class DeploymentsIndex extends FqnIndex {
9
9
  services;
10
10
  Names;
11
11
  constructor(services) {
12
- super(services, 'deployments-index');
12
+ super(services);
13
13
  this.services = services;
14
14
  this.Names = services.references.NameProvider;
15
15
  }
@@ -18,7 +18,7 @@ export class DeploymentsIndex extends FqnIndex {
18
18
  if (rootNodes.length === 0) {
19
19
  return DocumentFqnIndex.EMPTY;
20
20
  }
21
- const projectId = document.likec4ProjectId ??= this.projects.belongsTo(document);
21
+ const projectId = this.projects.belongsTo(document);
22
22
  const root = new Array();
23
23
  const children = new MultiMap();
24
24
  const descendants = new MultiMap();
@@ -7,12 +7,11 @@ import { ADisposable } from '../utils';
7
7
  import { type LangiumDocuments, ProjectsManager } from '../workspace';
8
8
  export declare class FqnIndex<AstNd = ast.Element> extends ADisposable {
9
9
  protected services: LikeC4Services;
10
- private cachePrefix;
11
10
  protected projects: ProjectsManager;
12
11
  protected langiumDocuments: LangiumDocuments;
13
12
  protected documentCache: DefaultWeakMap<LikeC4LangiumDocument, DocumentFqnIndex>;
14
13
  protected workspaceCache: WorkspaceCache<string, AstNodeDescriptionWithFqn[]>;
15
- constructor(services: LikeC4Services, cachePrefix?: string);
14
+ constructor(services: LikeC4Services);
16
15
  private documents;
17
16
  get(document: LikeC4LangiumDocument): DocumentFqnIndex;
18
17
  resolve(reference: ast.Referenceable): Fqn;
@@ -10,22 +10,20 @@ import { readStrictFqn } from '../utils/elementRef';
10
10
  import { ProjectsManager } from '../workspace';
11
11
  export class FqnIndex extends ADisposable {
12
12
  services;
13
- cachePrefix;
14
13
  projects;
15
14
  langiumDocuments;
16
15
  documentCache;
17
16
  workspaceCache;
18
- constructor(services, cachePrefix = 'fqn-index') {
17
+ constructor(services) {
19
18
  super();
20
19
  this.services = services;
21
- this.cachePrefix = cachePrefix;
22
20
  this.langiumDocuments = services.shared.workspace.LangiumDocuments;
23
21
  this.projects = services.shared.workspace.ProjectsManager;
24
22
  this.documentCache = new DefaultWeakMap(doc => this.createDocumentIndex(doc));
25
23
  this.workspaceCache = new WorkspaceCache(services.shared, DocumentState.IndexedContent);
26
24
  this.onDispose(services.shared.workspace.DocumentBuilder.onDocumentPhase(DocumentState.IndexedContent, (doc) => {
27
25
  if (isLikeC4LangiumDocument(doc)) {
28
- this.documentCache.set(doc, this.createDocumentIndex(doc));
26
+ this.documentCache.delete(doc);
29
27
  }
30
28
  }));
31
29
  }
@@ -57,23 +55,23 @@ export class FqnIndex extends ADisposable {
57
55
  // Document index is not yet created
58
56
  const doc = AstUtils.getDocument(el);
59
57
  invariant(isLikeC4LangiumDocument(doc));
60
- logWarnError(`Document ${doc.uri.path} is not indexed, but getFqn was called`);
61
- // This will create the document index
58
+ // Ensure the document is indexed
62
59
  this.get(doc);
60
+ // This will create the document index
63
61
  return nonNullable(ElementOps.readId(el), 'Element fqn must be set, invalid state');
64
62
  }
65
63
  byFqn(projectId, fqn) {
66
- return stream(this.workspaceCache.get(`${this.cachePrefix}:${projectId}:fqn:${fqn}`, () => {
64
+ return stream(this.workspaceCache.get(`${projectId}:fqn:${fqn}`, () => {
67
65
  return this
68
66
  .documents(projectId)
69
- .toArray()
70
67
  .flatMap(doc => {
71
68
  return this.get(doc).byFqn(fqn);
72
- });
69
+ })
70
+ .toArray();
73
71
  }));
74
72
  }
75
73
  rootElements(projectId) {
76
- return stream(this.workspaceCache.get(`${this.cachePrefix}:${projectId}:rootElements`, () => {
74
+ return stream(this.workspaceCache.get(`${projectId}:rootElements`, () => {
77
75
  const allchildren = this.documents(projectId)
78
76
  .reduce((map, doc) => {
79
77
  this.get(doc).rootElements().forEach(desc => {
@@ -85,7 +83,7 @@ export class FqnIndex extends ADisposable {
85
83
  }));
86
84
  }
87
85
  directChildrenOf(projectId, parent) {
88
- return stream(this.workspaceCache.get(`${this.cachePrefix}:${projectId}:directChildrenOf:${parent}`, () => {
86
+ return stream(this.workspaceCache.get(`${projectId}:directChildrenOf:${parent}`, () => {
89
87
  const allchildren = this.documents(projectId)
90
88
  .reduce((map, doc) => {
91
89
  this.get(doc).children(parent).forEach(desc => {
@@ -100,7 +98,7 @@ export class FqnIndex extends ADisposable {
100
98
  * Returns descedant elements with unique names in the scope
101
99
  */
102
100
  uniqueDescedants(projectId, parent) {
103
- return stream(this.workspaceCache.get(`${this.cachePrefix}:${projectId}:uniqueDescedants:${parent}`, () => {
101
+ return stream(this.workspaceCache.get(`${projectId}:uniqueDescedants:${parent}`, () => {
104
102
  const { children, descendants } = this.documents(projectId)
105
103
  .reduce((map, doc) => {
106
104
  const docIndex = this.get(doc);
@@ -129,18 +127,17 @@ export class FqnIndex extends ADisposable {
129
127
  if (rootElements.length === 0) {
130
128
  return DocumentFqnIndex.EMPTY;
131
129
  }
132
- const projectId = document.likec4ProjectId ??= this.projects.belongsTo(document);
130
+ const projectId = document.likec4ProjectId ?? this.projects.belongsTo(document);
133
131
  const root = new Array();
134
132
  const children = new MultiMap();
135
133
  const descendants = new MultiMap();
136
134
  const byfqn = new MultiMap();
137
135
  const Descriptions = this.services.workspace.AstNodeDescriptionProvider;
138
136
  const createAndSaveDescription = (node, name, fqn) => {
139
- const desc = {
140
- ...Descriptions.createDescription(node, name, document),
137
+ const desc = Object.assign(Descriptions.createDescription(node, name, document), {
141
138
  id: fqn,
142
139
  likec4ProjectId: projectId,
143
- };
140
+ });
144
141
  ElementOps.writeId(node, fqn);
145
142
  byfqn.set(fqn, desc);
146
143
  return desc;
@@ -132,7 +132,6 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
132
132
  unsafeSyncComputeModel(projectId, manualLayouts) {
133
133
  const logger = builderLogger.getChild(projectId);
134
134
  const cache = this.cache;
135
- const viewsCache = this.cache;
136
135
  const hasManualLayouts = !!manualLayouts && !isEmpty(manualLayouts);
137
136
  const key = computedModelCacheKey(projectId) + (hasManualLayouts ? '+manualLayouts' : '');
138
137
  if (cache.has(key)) {
@@ -163,7 +162,6 @@ export class DefaultLikeC4ModelBuilder extends ADisposable {
163
162
  const previous = this.previousViews[key];
164
163
  const view = previous && eq(v, previous) ? previous : v;
165
164
  this.previousViews[key] = view;
166
- viewsCache.set(key, view);
167
165
  return [v.id, view];
168
166
  });
169
167
  const data = {