@hydranium/core 1.0.0-next.267 → 1.0.0-next.269

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 (121) hide show
  1. package/lib/documents/ast-document-manager.d.ts.map +1 -1
  2. package/lib/documents/ast-document-manager.js +4 -3
  3. package/lib/documents/ast-document-manager.js.map +1 -1
  4. package/lib/documents/document-release-handler.d.ts +2 -2
  5. package/lib/documents/document-release-handler.d.ts.map +1 -1
  6. package/lib/documents/document-release-handler.js +14 -19
  7. package/lib/documents/document-release-handler.js.map +1 -1
  8. package/lib/documents/hydranium-text-documents.d.ts.map +1 -1
  9. package/lib/documents/hydranium-text-documents.js +7 -10
  10. package/lib/documents/hydranium-text-documents.js.map +1 -1
  11. package/lib/documents/language-client-shadow.d.ts.map +1 -1
  12. package/lib/documents/language-client-shadow.js +2 -1
  13. package/lib/documents/language-client-shadow.js.map +1 -1
  14. package/lib/documents/version-sync-service.d.ts +18 -14
  15. package/lib/documents/version-sync-service.d.ts.map +1 -1
  16. package/lib/documents/version-sync-service.js +15 -13
  17. package/lib/documents/version-sync-service.js.map +1 -1
  18. package/lib/langium/diagnostics/lsp-logger.d.ts.map +1 -1
  19. package/lib/langium/diagnostics/lsp-logger.js +2 -2
  20. package/lib/langium/diagnostics/lsp-logger.js.map +1 -1
  21. package/lib/langium/document-builder/document-builder.d.ts +2 -2
  22. package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
  23. package/lib/langium/document-builder/document-builder.js +23 -10
  24. package/lib/langium/document-builder/document-builder.js.map +1 -1
  25. package/lib/langium/integrity/integrity-service.d.ts.map +1 -1
  26. package/lib/langium/integrity/integrity-service.js +5 -1
  27. package/lib/langium/integrity/integrity-service.js.map +1 -1
  28. package/lib/langium/model-service/client-session.d.ts.map +1 -1
  29. package/lib/langium/model-service/client-session.js +20 -3
  30. package/lib/langium/model-service/client-session.js.map +1 -1
  31. package/lib/langium/model-service/model-service.d.ts +4 -3
  32. package/lib/langium/model-service/model-service.d.ts.map +1 -1
  33. package/lib/langium/model-service/model-service.js +49 -19
  34. package/lib/langium/model-service/model-service.js.map +1 -1
  35. package/lib/langium/project/abstract-project-manager.d.ts.map +1 -1
  36. package/lib/langium/project/abstract-project-manager.js +4 -4
  37. package/lib/langium/project/abstract-project-manager.js.map +1 -1
  38. package/lib/langium/scope/hydranium-scope-computation.d.ts +6 -0
  39. package/lib/langium/scope/hydranium-scope-computation.d.ts.map +1 -1
  40. package/lib/langium/scope/hydranium-scope-computation.js +6 -10
  41. package/lib/langium/scope/hydranium-scope-computation.js.map +1 -1
  42. package/lib/langium/validation/document-validator.d.ts.map +1 -1
  43. package/lib/langium/validation/document-validator.js +5 -3
  44. package/lib/langium/validation/document-validator.js.map +1 -1
  45. package/lib/langium/workspace/hydranium-workspace-lock.d.ts +1 -1
  46. package/lib/langium/workspace/hydranium-workspace-lock.js +1 -1
  47. package/lib/langium/workspace/hydranium-workspace-lock.js.map +1 -1
  48. package/lib/langium/workspace/hydranium-workspace-manager.d.ts.map +1 -1
  49. package/lib/langium/workspace/hydranium-workspace-manager.js +4 -5
  50. package/lib/langium/workspace/hydranium-workspace-manager.js.map +1 -1
  51. package/lib/langium/workspace/persistent-file-system-provider.d.ts.map +1 -1
  52. package/lib/langium/workspace/persistent-file-system-provider.js +2 -1
  53. package/lib/langium/workspace/persistent-file-system-provider.js.map +1 -1
  54. package/lib/launcher/integrated-server.d.ts +3 -1
  55. package/lib/launcher/integrated-server.d.ts.map +1 -1
  56. package/lib/lsp/hydranium-document-update-handler.d.ts.map +1 -1
  57. package/lib/lsp/hydranium-document-update-handler.js +5 -5
  58. package/lib/lsp/hydranium-document-update-handler.js.map +1 -1
  59. package/lib/node/event-loop-monitor.d.ts.map +1 -1
  60. package/lib/node/event-loop-monitor.js +2 -1
  61. package/lib/node/event-loop-monitor.js.map +1 -1
  62. package/lib/node/log-preamble-node.d.ts +5 -3
  63. package/lib/node/log-preamble-node.d.ts.map +1 -1
  64. package/lib/node/log-preamble-node.js +13 -10
  65. package/lib/node/log-preamble-node.js.map +1 -1
  66. package/lib/node/memory-monitor.d.ts +2 -2
  67. package/lib/node/memory-monitor.d.ts.map +1 -1
  68. package/lib/node/memory-monitor.js +3 -5
  69. package/lib/node/memory-monitor.js.map +1 -1
  70. package/lib/node/server-diagnostics.js +1 -1
  71. package/lib/node/server-diagnostics.js.map +1 -1
  72. package/lib/node/socket-launcher.d.ts.map +1 -1
  73. package/lib/node/socket-launcher.js +4 -4
  74. package/lib/node/socket-launcher.js.map +1 -1
  75. package/lib/node/stdio-launcher.d.ts.map +1 -1
  76. package/lib/node/stdio-launcher.js +12 -3
  77. package/lib/node/stdio-launcher.js.map +1 -1
  78. package/lib/testing/playwright/server-log-capture.d.ts +7 -0
  79. package/lib/testing/playwright/server-log-capture.d.ts.map +1 -1
  80. package/lib/testing/playwright/server-log-capture.js +12 -0
  81. package/lib/testing/playwright/server-log-capture.js.map +1 -1
  82. package/lib/testing/playwright/server-log-fixture.d.ts +16 -0
  83. package/lib/testing/playwright/server-log-fixture.d.ts.map +1 -1
  84. package/lib/testing/playwright/server-log-fixture.js +25 -6
  85. package/lib/testing/playwright/server-log-fixture.js.map +1 -1
  86. package/lib/testing/playwright/server-log-region.d.ts +15 -0
  87. package/lib/testing/playwright/server-log-region.d.ts.map +1 -0
  88. package/lib/testing/playwright/server-log-region.js +30 -0
  89. package/lib/testing/playwright/server-log-region.js.map +1 -0
  90. package/lib/util/connection-liveness.d.ts.map +1 -1
  91. package/lib/util/connection-liveness.js +3 -2
  92. package/lib/util/connection-liveness.js.map +1 -1
  93. package/package.json +5 -5
  94. package/src/documents/ast-document-manager.ts +7 -3
  95. package/src/documents/document-release-handler.ts +15 -21
  96. package/src/documents/hydranium-text-documents.ts +13 -12
  97. package/src/documents/language-client-shadow.ts +2 -1
  98. package/src/documents/version-sync-service.ts +32 -24
  99. package/src/langium/diagnostics/lsp-logger.ts +2 -1
  100. package/src/langium/document-builder/document-builder.ts +30 -11
  101. package/src/langium/integrity/integrity-service.ts +4 -1
  102. package/src/langium/model-service/client-session.ts +20 -3
  103. package/src/langium/model-service/model-service.ts +55 -24
  104. package/src/langium/project/abstract-project-manager.ts +4 -4
  105. package/src/langium/scope/hydranium-scope-computation.ts +12 -12
  106. package/src/langium/validation/document-validator.ts +5 -3
  107. package/src/langium/workspace/hydranium-workspace-lock.ts +2 -2
  108. package/src/langium/workspace/hydranium-workspace-manager.ts +4 -5
  109. package/src/langium/workspace/persistent-file-system-provider.ts +2 -1
  110. package/src/launcher/integrated-server.ts +3 -1
  111. package/src/lsp/hydranium-document-update-handler.ts +15 -7
  112. package/src/node/event-loop-monitor.ts +2 -1
  113. package/src/node/log-preamble-node.ts +20 -10
  114. package/src/node/memory-monitor.ts +4 -5
  115. package/src/node/server-diagnostics.ts +1 -1
  116. package/src/node/socket-launcher.ts +4 -4
  117. package/src/node/stdio-launcher.ts +11 -3
  118. package/src/testing/playwright/server-log-capture.ts +14 -0
  119. package/src/testing/playwright/server-log-fixture.ts +46 -6
  120. package/src/testing/playwright/server-log-region.ts +32 -0
  121. package/src/util/connection-liveness.ts +4 -2
@@ -13,7 +13,7 @@ import { type ServerSharedServicesMinimal } from '../langium/shared-services.js'
13
13
 
14
14
  /** Heap usage ratio (heapUsed / heap_size_limit) above which we emit a warning. */
15
15
  export const DEFAULT_MEMORY_WARN_THRESHOLD = 0.85;
16
- /** Heap usage ratio above which we emit an error-level warning (imminent OOM likely). */
16
+ /** Heap usage ratio above which the warning says out-of-memory is imminent. */
17
17
  export const DEFAULT_MEMORY_CRITICAL_THRESHOLD = 0.95;
18
18
  /** Cooldown between repeated warnings at the same level, in ms. */
19
19
  export const DEFAULT_COOLDOWN_MS = 30_000;
@@ -25,7 +25,7 @@ type Severity = 'ok' | 'warn' | 'critical';
25
25
  export interface MemoryMonitorOptions {
26
26
  /** Heap-usage ratio (0..1) above which a warning is emitted. Default {@link DEFAULT_MEMORY_WARN_THRESHOLD}. */
27
27
  warnThreshold?: number;
28
- /** Heap-usage ratio above which an error is emitted (imminent OOM likely). Default {@link DEFAULT_MEMORY_CRITICAL_THRESHOLD}. */
28
+ /** Heap-usage ratio above which the warning says out-of-memory is imminent. Default {@link DEFAULT_MEMORY_CRITICAL_THRESHOLD}. */
29
29
  criticalThreshold?: number;
30
30
  /** Sample interval, in ms. Default {@link DEFAULT_MEMORY_INTERVAL_MS}. */
31
31
  intervalMs?: number;
@@ -86,9 +86,8 @@ export function startMemoryMonitor(shared: ServerSharedServicesMinimal, options:
86
86
  : severity === 'warn'
87
87
  ? ' - garbage collection pressure may slow operations'
88
88
  : ' - recovered below warning threshold');
89
- if (severity === 'critical') {
90
- logger.error(message);
91
- } else if (severity === 'warn') {
89
+ // Critical is still a warning: nothing has failed yet.
90
+ if (severity !== 'ok') {
92
91
  logger.warn(message);
93
92
  } else {
94
93
  logger.info(message);
@@ -49,7 +49,7 @@ export function startServerDiagnostics(shared: ServerSharedServicesMinimal, opti
49
49
  productName,
50
50
  version,
51
51
  phaseDetailThresholdMs,
52
- ...nodeLogPreambleSections(phaseDetailThresholdMs)
52
+ ...nodeLogPreambleSections(phaseDetailThresholdMs, options)
53
53
  });
54
54
  if (options.logInitialMemory ?? true) {
55
55
  shared.Tracer.for('Process').memory('Initial Memory');
@@ -8,7 +8,7 @@
8
8
  ********************************************************************************/
9
9
 
10
10
  import * as net from 'node:net';
11
- import { Deferred, type Disposable, type Logger } from '@hydranium/protocol';
11
+ import { Deferred, describeErrorWithStack, type Disposable, type Logger } from '@hydranium/protocol';
12
12
  import { createMessageConnection, SocketMessageReader, SocketMessageWriter } from 'vscode-jsonrpc/node';
13
13
  import type { MessageConnection } from 'vscode-jsonrpc';
14
14
  import type { IntegratedServer } from '../launcher/integrated-server.js';
@@ -131,7 +131,7 @@ export function startSocketServer(options: SocketServerOptions, onClientConnecti
131
131
  try {
132
132
  adopterDisposable.dispose();
133
133
  } catch (err) {
134
- logger?.warn(`[${tag}] Adopter disposable threw on cleanup: ${(err as Error)?.message ?? err}`);
134
+ logger?.warn(`[${tag}] Adopter disposable threw on cleanup: ${describeErrorWithStack(err)}`);
135
135
  }
136
136
  connection.dispose();
137
137
  const idx = activeConnections.indexOf(entry);
@@ -164,7 +164,7 @@ export function startSocketServer(options: SocketServerOptions, onClientConnecti
164
164
  started.resolve();
165
165
  });
166
166
  netServer.on('error', err => {
167
- logger?.error(`[${tag}] Error: ${err.message}`);
167
+ logger?.error(`[${tag}] Error: ${describeErrorWithStack(err)}`);
168
168
  started.reject(err);
169
169
  });
170
170
  netServer.on('close', () => {
@@ -174,7 +174,7 @@ export function startSocketServer(options: SocketServerOptions, onClientConnecti
174
174
  try {
175
175
  entry.adopterDisposable.dispose();
176
176
  } catch (err) {
177
- logger?.warn(`[${tag}] Adopter disposable threw on shutdown: ${(err as Error)?.message ?? err}`);
177
+ logger?.warn(`[${tag}] Adopter disposable threw on shutdown: ${describeErrorWithStack(err)}`);
178
178
  }
179
179
  entry.connection.dispose();
180
180
  }
@@ -7,7 +7,7 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
- import { Deferred, type Disposable, type Logger } from '@hydranium/protocol';
10
+ import { Deferred, type Disposable, type Logger, describeError, describeErrorWithStack } from '@hydranium/protocol';
11
11
  import type { MessageConnection } from 'vscode-jsonrpc';
12
12
  import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node';
13
13
  import type { IntegratedServer } from '../launcher/integrated-server.js';
@@ -115,17 +115,21 @@ export function startStdioServer(
115
115
  try {
116
116
  adopterDisposable.dispose();
117
117
  } catch (err: unknown) {
118
- logger?.warn(`[${tag}] Adopter disposable threw on cleanup: ${err instanceof Error ? err.message : String(err)}`);
118
+ logger?.warn(`[${tag}] Adopter disposable threw on cleanup: ${describeErrorWithStack(err)}`);
119
119
  }
120
120
  connection.dispose();
121
121
  stopped.resolve();
122
122
  };
123
123
  connection.onClose(() => close());
124
124
 
125
+ // The workspace manager logs a failed initialization itself.
126
+ let initializing = false;
125
127
  const begin = async (): Promise<void> => {
126
128
  if (options.workspace !== undefined) {
127
129
  logger?.info(`[${tag}] Initializing workspace`);
130
+ initializing = true;
128
131
  await initializeWorkspaceProgrammatically(options.shared, options.workspace);
132
+ initializing = false;
129
133
  }
130
134
  // Only now attach the reader — see the ordering note in the doc above.
131
135
  connection.listen();
@@ -138,7 +142,11 @@ export function startStdioServer(
138
142
  // A failed startup must not leave a half-live head: report it on
139
143
  // `started` and tear the connection down rather than listening over an
140
144
  // uninitialized workspace.
141
- logger?.error(`[${tag}] Startup failed: ${err instanceof Error ? err.message : String(err)}`);
145
+ if (initializing) {
146
+ logger?.debug(`[${tag}] Startup failed: the workspace did not initialize. ${describeError(err)}`);
147
+ } else {
148
+ logger?.error(`[${tag}] Startup failed: ${describeErrorWithStack(err)}`);
149
+ }
142
150
  started.reject(err);
143
151
  close();
144
152
  }
@@ -415,6 +415,20 @@ export function forwardBrowserConsole(
415
415
  });
416
416
  }
417
417
 
418
+ /** The label a `Warn` or `Error` entry starts with; continuation lines of a stack never do. */
419
+ const WARN_OR_ERROR_LINE = /^\[(Warn|Error)\s*-/;
420
+
421
+ /**
422
+ * The `Warn` and `Error` lines of `text` that no pattern of `allowlist`
423
+ * matches; its keys are the reasons the lines are expected. A clean run logs
424
+ * neither: `warn` is an anomaly that was handled and `error` is work that
425
+ * failed, so either one in a passing spec is a defect or a missing allowance.
426
+ */
427
+ export function findUnexpectedServerLogLines(text: string, allowlist: Readonly<Record<string, RegExp>> = {}): string[] {
428
+ const patterns = Object.values(allowlist);
429
+ return text.split('\n').filter(line => WARN_OR_ERROR_LINE.test(line) && !patterns.some(pattern => pattern.test(line)));
430
+ }
431
+
418
432
  /**
419
433
  * Per-test server-log capture, called from a spec's `afterEach` with the test's
420
434
  * workspace path:
@@ -11,7 +11,14 @@
11
11
  // The plain functions in `server-log-capture.ts` stay playwright-free; only
12
12
  // adopters that use this fixture need `@playwright/test` installed.
13
13
  import { test as base, type Fixtures, type PlaywrightTestArgs, type PlaywrightTestOptions } from '@playwright/test';
14
- import { attachServerLog, markServerLog } from './server-log-capture.js';
14
+ import {
15
+ attachServerLog,
16
+ DEFAULT_SERVER_LOG_ATTACH_ENV,
17
+ findUnexpectedServerLogLines,
18
+ markServerLog,
19
+ resolveServerLogPath
20
+ } from './server-log-capture.js';
21
+ import { readServerLogRegion } from './server-log-region.js';
15
22
 
16
23
  export interface ServerLogFixtures {
17
24
  /**
@@ -22,6 +29,21 @@ export interface ServerLogFixtures {
22
29
  * `attachServerLog` in `beforeEach` / `afterEach` instead.
23
30
  */
24
31
  serverLogWorkspace: string;
32
+ /**
33
+ * Fail a test whose part of the server log holds a `Warn` or `Error` line
34
+ * that {@link serverLogAllowlist} does not match. Off by default; reads
35
+ * nothing when capture is off.
36
+ */
37
+ serverLogClean: boolean;
38
+ /**
39
+ * The `Warn` and `Error` lines {@link serverLogClean} lets through: each key
40
+ * is why the line is expected, and its pattern is matched against the whole
41
+ * line, label and timestamp included. A pattern takes neither the `g` nor
42
+ * the `y` flag, which make `test` resume from its last match. An object
43
+ * rather than a list, because Playwright reads an array whose second entry
44
+ * is an object as a fixture's `[value, options]`.
45
+ */
46
+ serverLogAllowlist: Readonly<Record<string, RegExp>>;
25
47
  }
26
48
 
27
49
  /** Full fixture set including the internal side-effect-only auto fixture. */
@@ -36,17 +58,35 @@ interface ServerLogTestFixtures extends ServerLogFixtures {
36
58
  * (teardown) — i.e. the `beforeEach` + `afterEach` pair fused into one auto
37
59
  * fixture. Compose onto any test base, or use the pre-extended {@link test}.
38
60
  * Override `serverLogWorkspace` to wire the workspace; otherwise it's inert.
61
+ * Set `serverLogClean` to also fail a test on an unexpected `Warn` or `Error`.
39
62
  */
40
63
  export const serverLogFixtures: Fixtures<ServerLogTestFixtures, object, PlaywrightTestArgs & PlaywrightTestOptions> = {
41
64
  serverLogWorkspace: ['', { option: true }],
65
+ serverLogClean: [false, { option: true }],
66
+ serverLogAllowlist: [{}, { option: true }],
42
67
  serverLogCapture: [
43
- async ({ serverLogWorkspace }, use): Promise<void> => {
44
- if (serverLogWorkspace) {
45
- markServerLog(base.info(), serverLogWorkspace);
68
+ async ({ serverLogWorkspace, serverLogClean, serverLogAllowlist }, use): Promise<void> => {
69
+ if (!serverLogWorkspace) {
70
+ await use(undefined);
71
+ return;
46
72
  }
73
+ markServerLog(base.info(), serverLogWorkspace);
47
74
  await use(undefined);
48
- if (serverLogWorkspace) {
49
- await attachServerLog(base.info(), serverLogWorkspace);
75
+ const file = resolveServerLogPath(serverLogWorkspace);
76
+ const region = file ? readServerLogRegion(file) : '';
77
+ let problem: string | undefined;
78
+ if (serverLogClean) {
79
+ const unexpected = findUnexpectedServerLogLines(region, serverLogAllowlist);
80
+ if (unexpected.length > 0) {
81
+ problem = `The server logged ${unexpected.length} unexpected Warn or Error line(s):\n${unexpected.join('\n')}`;
82
+ }
83
+ }
84
+ // The failure-only policy reads the test's status, which failing here
85
+ // does not change; a run set to attach nothing still attaches nothing.
86
+ const attachOn = process.env[DEFAULT_SERVER_LOG_ATTACH_ENV] === 'never' ? 'never' : 'always';
87
+ await attachServerLog(base.info(), serverLogWorkspace, problem ? { attachOn } : {});
88
+ if (problem) {
89
+ throw new Error(problem);
50
90
  }
51
91
  },
52
92
  { auto: true }
@@ -0,0 +1,32 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+
10
+ import { existsSync, readFileSync } from 'node:fs';
11
+
12
+ /**
13
+ * How far each log has been read. A suite starts its app before its first
14
+ * test's marker, so reading from the marker would leave startup unread.
15
+ */
16
+ const readUpTo = new Map<string, number>();
17
+
18
+ /**
19
+ * The part of `file` written since the last read of it, up to its last complete
20
+ * line, since the server may be mid-write. Every test reads, checked or not, so
21
+ * a test that is not checked leaves no lines to the next one.
22
+ */
23
+ export function readServerLogRegion(file: string): string {
24
+ if (!existsSync(file)) {
25
+ return '';
26
+ }
27
+ const log = readFileSync(file);
28
+ const end = log.lastIndexOf('\n') + 1;
29
+ const text = log.subarray(readUpTo.get(file) ?? 0, end).toString('utf-8');
30
+ readUpTo.set(file, end);
31
+ return text;
32
+ }
@@ -7,6 +7,8 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
+ import { describeError } from '@hydranium/protocol';
11
+
10
12
  /**
11
13
  * Phrases that mean a call lost its connection. Matched on the message because
12
14
  * the condition surfaces in shapes that share no discriminator:
@@ -64,7 +66,7 @@ export function isConnectionGoneError(err: unknown): boolean {
64
66
  if (isDestroyedStreamError(err)) {
65
67
  return true;
66
68
  }
67
- const message = err instanceof Error ? err.message : String(err);
69
+ const message = describeError(err);
68
70
  const normalized = message.toLowerCase();
69
71
  return CONNECTION_GONE_PATTERNS.some(pattern => pattern.test(normalized));
70
72
  }
@@ -92,6 +94,6 @@ export function isClientGoneError(err: unknown): boolean {
92
94
  if (err instanceof Error && 'code' in err && typeof err.code === 'string' && SOCKET_GONE_CODES.has(err.code)) {
93
95
  return true;
94
96
  }
95
- const message = err instanceof Error ? err.message : String(err);
97
+ const message = describeError(err);
96
98
  return SOCKET_GONE_PATTERN.test(message.toLowerCase());
97
99
  }