@hydranium/core 1.0.0-next.8 → 1.0.0-next.86

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 (311) hide show
  1. package/lib/documents/ast-document-manager.d.ts +75 -16
  2. package/lib/documents/ast-document-manager.d.ts.map +1 -1
  3. package/lib/documents/ast-document-manager.js +57 -19
  4. package/lib/documents/ast-document-manager.js.map +1 -1
  5. package/lib/documents/client-ids.d.ts +5 -19
  6. package/lib/documents/client-ids.d.ts.map +1 -1
  7. package/lib/documents/client-ids.js +5 -19
  8. package/lib/documents/client-ids.js.map +1 -1
  9. package/lib/documents/hydranium-text-documents.d.ts +55 -21
  10. package/lib/documents/hydranium-text-documents.d.ts.map +1 -1
  11. package/lib/documents/hydranium-text-documents.js +142 -46
  12. package/lib/documents/hydranium-text-documents.js.map +1 -1
  13. package/lib/documents/language-client-text-shadow.d.ts +41 -9
  14. package/lib/documents/language-client-text-shadow.d.ts.map +1 -1
  15. package/lib/documents/language-client-text-shadow.js +56 -11
  16. package/lib/documents/language-client-text-shadow.js.map +1 -1
  17. package/lib/documents/self-save-registry.d.ts +37 -6
  18. package/lib/documents/self-save-registry.d.ts.map +1 -1
  19. package/lib/documents/self-save-registry.js +28 -15
  20. package/lib/documents/self-save-registry.js.map +1 -1
  21. package/lib/index.d.ts +2 -0
  22. package/lib/index.d.ts.map +1 -1
  23. package/lib/index.js +5 -0
  24. package/lib/index.js.map +1 -1
  25. package/lib/langium/ast-extension/ast-node-builder.d.ts +32 -9
  26. package/lib/langium/ast-extension/ast-node-builder.d.ts.map +1 -1
  27. package/lib/langium/ast-extension/ast-node-builder.js +60 -22
  28. package/lib/langium/ast-extension/ast-node-builder.js.map +1 -1
  29. package/lib/langium/bootstrap.d.ts.map +1 -1
  30. package/lib/langium/bootstrap.js +16 -9
  31. package/lib/langium/bootstrap.js.map +1 -1
  32. package/lib/langium/config/configuration-provider.d.ts +81 -0
  33. package/lib/langium/config/configuration-provider.d.ts.map +1 -0
  34. package/lib/langium/config/configuration-provider.js +112 -0
  35. package/lib/langium/config/configuration-provider.js.map +1 -0
  36. package/lib/langium/config/index.d.ts +1 -0
  37. package/lib/langium/config/index.d.ts.map +1 -1
  38. package/lib/langium/config/index.js +1 -0
  39. package/lib/langium/config/index.js.map +1 -1
  40. package/lib/langium/diagnostics/lsp-logger.d.ts +17 -0
  41. package/lib/langium/diagnostics/lsp-logger.d.ts.map +1 -1
  42. package/lib/langium/diagnostics/lsp-logger.js +40 -6
  43. package/lib/langium/diagnostics/lsp-logger.js.map +1 -1
  44. package/lib/langium/document-builder/build-pipeline-integration.d.ts +13 -1
  45. package/lib/langium/document-builder/build-pipeline-integration.d.ts.map +1 -1
  46. package/lib/langium/document-builder/build-pipeline-integration.js +1 -1
  47. package/lib/langium/document-builder/build-pipeline-integration.js.map +1 -1
  48. package/lib/langium/document-builder/build-session.d.ts +93 -0
  49. package/lib/langium/document-builder/build-session.d.ts.map +1 -0
  50. package/lib/langium/document-builder/build-session.js +72 -0
  51. package/lib/langium/document-builder/build-session.js.map +1 -0
  52. package/lib/langium/document-builder/document-builder.d.ts +150 -7
  53. package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
  54. package/lib/langium/document-builder/document-builder.js +298 -11
  55. package/lib/langium/document-builder/document-builder.js.map +1 -1
  56. package/lib/langium/document-builder/index.d.ts +1 -0
  57. package/lib/langium/document-builder/index.d.ts.map +1 -1
  58. package/lib/langium/document-builder/index.js +1 -0
  59. package/lib/langium/document-builder/index.js.map +1 -1
  60. package/lib/langium/integration-services.d.ts +45 -1
  61. package/lib/langium/integration-services.d.ts.map +1 -1
  62. package/lib/langium/integration-services.js +10 -5
  63. package/lib/langium/integration-services.js.map +1 -1
  64. package/lib/langium/integrity/integrity-service.d.ts +5 -0
  65. package/lib/langium/integrity/integrity-service.d.ts.map +1 -1
  66. package/lib/langium/integrity/integrity-service.js +34 -1
  67. package/lib/langium/integrity/integrity-service.js.map +1 -1
  68. package/lib/langium/keys/containment.d.ts +64 -0
  69. package/lib/langium/keys/containment.d.ts.map +1 -0
  70. package/lib/langium/keys/containment.js +53 -0
  71. package/lib/langium/keys/containment.js.map +1 -0
  72. package/lib/langium/keys/index.d.ts +1 -0
  73. package/lib/langium/keys/index.d.ts.map +1 -1
  74. package/lib/langium/keys/index.js +1 -0
  75. package/lib/langium/keys/index.js.map +1 -1
  76. package/lib/langium/keys/name-based-key-provider.d.ts +4 -0
  77. package/lib/langium/keys/name-based-key-provider.d.ts.map +1 -1
  78. package/lib/langium/keys/name-based-key-provider.js +4 -0
  79. package/lib/langium/keys/name-based-key-provider.js.map +1 -1
  80. package/lib/langium/model-service/model-service.d.ts +149 -10
  81. package/lib/langium/model-service/model-service.d.ts.map +1 -1
  82. package/lib/langium/model-service/model-service.js +120 -93
  83. package/lib/langium/model-service/model-service.js.map +1 -1
  84. package/lib/langium/module.d.ts +84 -15
  85. package/lib/langium/module.d.ts.map +1 -1
  86. package/lib/langium/module.js +26 -12
  87. package/lib/langium/module.js.map +1 -1
  88. package/lib/langium/naming/name-provider.d.ts +25 -4
  89. package/lib/langium/naming/name-provider.d.ts.map +1 -1
  90. package/lib/langium/naming/name-provider.js +2 -1
  91. package/lib/langium/naming/name-provider.js.map +1 -1
  92. package/lib/langium/naming/name-separator-validation.d.ts +23 -0
  93. package/lib/langium/naming/name-separator-validation.d.ts.map +1 -1
  94. package/lib/langium/naming/name-separator-validation.js +30 -1
  95. package/lib/langium/naming/name-separator-validation.js.map +1 -1
  96. package/lib/langium/residency/cst-residency-service.d.ts +31 -9
  97. package/lib/langium/residency/cst-residency-service.d.ts.map +1 -1
  98. package/lib/langium/residency/cst-residency-service.js +13 -56
  99. package/lib/langium/residency/cst-residency-service.js.map +1 -1
  100. package/lib/langium/scope/hydranium-scope-provider.d.ts +32 -13
  101. package/lib/langium/scope/hydranium-scope-provider.d.ts.map +1 -1
  102. package/lib/langium/scope/hydranium-scope-provider.js +37 -20
  103. package/lib/langium/scope/hydranium-scope-provider.js.map +1 -1
  104. package/lib/langium/service-registry.d.ts +5 -1
  105. package/lib/langium/service-registry.d.ts.map +1 -1
  106. package/lib/langium/service-registry.js +4 -0
  107. package/lib/langium/service-registry.js.map +1 -1
  108. package/lib/langium/shared-services.d.ts +15 -2
  109. package/lib/langium/shared-services.d.ts.map +1 -1
  110. package/lib/langium/shared-services.js.map +1 -1
  111. package/lib/langium/transfer/transfer-encoder.d.ts +60 -17
  112. package/lib/langium/transfer/transfer-encoder.d.ts.map +1 -1
  113. package/lib/langium/transfer/transfer-encoder.js +38 -16
  114. package/lib/langium/transfer/transfer-encoder.js.map +1 -1
  115. package/lib/langium/validation/document-validator.d.ts +294 -10
  116. package/lib/langium/validation/document-validator.d.ts.map +1 -1
  117. package/lib/langium/validation/document-validator.js +448 -5
  118. package/lib/langium/validation/document-validator.js.map +1 -1
  119. package/lib/langium/workspace/document-uri-policy.d.ts +3 -4
  120. package/lib/langium/workspace/document-uri-policy.d.ts.map +1 -1
  121. package/lib/langium/workspace/document-uri-policy.js +3 -4
  122. package/lib/langium/workspace/document-uri-policy.js.map +1 -1
  123. package/lib/langium/workspace/hydranium-langium-document-factory.d.ts +25 -0
  124. package/lib/langium/workspace/hydranium-langium-document-factory.d.ts.map +1 -1
  125. package/lib/langium/workspace/hydranium-langium-document-factory.js +25 -0
  126. package/lib/langium/workspace/hydranium-langium-document-factory.js.map +1 -1
  127. package/lib/langium/workspace/hydranium-workspace-manager.d.ts +21 -1
  128. package/lib/langium/workspace/hydranium-workspace-manager.d.ts.map +1 -1
  129. package/lib/langium/workspace/hydranium-workspace-manager.js +28 -0
  130. package/lib/langium/workspace/hydranium-workspace-manager.js.map +1 -1
  131. package/lib/langium/workspace/in-memory-file-system-provider.d.ts +8 -0
  132. package/lib/langium/workspace/in-memory-file-system-provider.d.ts.map +1 -1
  133. package/lib/langium/workspace/in-memory-file-system-provider.js +11 -2
  134. package/lib/langium/workspace/in-memory-file-system-provider.js.map +1 -1
  135. package/lib/langium/workspace/initialize-workspace.d.ts +22 -2
  136. package/lib/langium/workspace/initialize-workspace.d.ts.map +1 -1
  137. package/lib/langium/workspace/initialize-workspace.js +13 -5
  138. package/lib/langium/workspace/initialize-workspace.js.map +1 -1
  139. package/lib/langium/workspace/langium-documents.d.ts +79 -17
  140. package/lib/langium/workspace/langium-documents.d.ts.map +1 -1
  141. package/lib/langium/workspace/langium-documents.js +83 -28
  142. package/lib/langium/workspace/langium-documents.js.map +1 -1
  143. package/lib/locale/index.d.ts +10 -0
  144. package/lib/locale/index.d.ts.map +1 -0
  145. package/lib/locale/index.js +10 -0
  146. package/lib/locale/index.js.map +1 -0
  147. package/lib/locale/server-locale.d.ts +73 -0
  148. package/lib/locale/server-locale.d.ts.map +1 -0
  149. package/lib/locale/server-locale.js +61 -0
  150. package/lib/locale/server-locale.js.map +1 -0
  151. package/lib/lsp/hydranium-document-update-handler.js +1 -1
  152. package/lib/lsp/hydranium-document-update-handler.js.map +1 -1
  153. package/lib/lsp/index.d.ts +1 -1
  154. package/lib/lsp/index.d.ts.map +1 -1
  155. package/lib/lsp/index.js +1 -1
  156. package/lib/lsp/index.js.map +1 -1
  157. package/lib/lsp/lsp-latency.d.ts +39 -0
  158. package/lib/lsp/lsp-latency.d.ts.map +1 -0
  159. package/lib/lsp/lsp-latency.js +58 -0
  160. package/lib/lsp/lsp-latency.js.map +1 -0
  161. package/lib/lsp/semantic-token-provider.d.ts +34 -1
  162. package/lib/lsp/semantic-token-provider.d.ts.map +1 -1
  163. package/lib/lsp/semantic-token-provider.js +55 -8
  164. package/lib/lsp/semantic-token-provider.js.map +1 -1
  165. package/lib/messages/carriers.d.ts +54 -0
  166. package/lib/messages/carriers.d.ts.map +1 -0
  167. package/lib/messages/carriers.js +62 -0
  168. package/lib/messages/carriers.js.map +1 -0
  169. package/lib/messages/index.d.ts +25 -0
  170. package/lib/messages/index.d.ts.map +1 -0
  171. package/lib/messages/index.js +25 -0
  172. package/lib/messages/index.js.map +1 -0
  173. package/lib/messages/renderer.d.ts +136 -0
  174. package/lib/messages/renderer.d.ts.map +1 -0
  175. package/lib/messages/renderer.js +159 -0
  176. package/lib/messages/renderer.js.map +1 -0
  177. package/lib/node/heap-ceiling.d.ts +61 -0
  178. package/lib/node/heap-ceiling.d.ts.map +1 -0
  179. package/lib/node/heap-ceiling.js +72 -0
  180. package/lib/node/heap-ceiling.js.map +1 -0
  181. package/lib/node/index.d.ts +1 -0
  182. package/lib/node/index.d.ts.map +1 -1
  183. package/lib/node/index.js +1 -0
  184. package/lib/node/index.js.map +1 -1
  185. package/lib/node/latency-from-env.d.ts +1 -1
  186. package/lib/node/latency-from-env.js +1 -1
  187. package/lib/node/node-file-system-provider.d.ts.map +1 -1
  188. package/lib/node/node-file-system-provider.js +1 -40
  189. package/lib/node/node-file-system-provider.js.map +1 -1
  190. package/lib/node/profiling-run.d.ts +2 -2
  191. package/lib/node/profiling-run.js +1 -1
  192. package/lib/node/profiling-run.js.map +1 -1
  193. package/lib/node/rename-over-open-readers.d.ts +52 -0
  194. package/lib/node/rename-over-open-readers.d.ts.map +1 -0
  195. package/lib/node/rename-over-open-readers.js +66 -0
  196. package/lib/node/rename-over-open-readers.js.map +1 -0
  197. package/lib/testing/fake-document.d.ts +16 -4
  198. package/lib/testing/fake-document.d.ts.map +1 -1
  199. package/lib/testing/fake-document.js +18 -3
  200. package/lib/testing/fake-document.js.map +1 -1
  201. package/lib/testing/make-noop-shared-services.d.ts +14 -0
  202. package/lib/testing/make-noop-shared-services.d.ts.map +1 -1
  203. package/lib/testing/make-noop-shared-services.js +28 -2
  204. package/lib/testing/make-noop-shared-services.js.map +1 -1
  205. package/lib/testing/make-test-services.d.ts +44 -8
  206. package/lib/testing/make-test-services.d.ts.map +1 -1
  207. package/lib/testing/make-test-services.js +19 -3
  208. package/lib/testing/make-test-services.js.map +1 -1
  209. package/lib/testing/node/spawned-server.d.ts +14 -0
  210. package/lib/testing/node/spawned-server.d.ts.map +1 -1
  211. package/lib/testing/node/spawned-server.js +33 -1
  212. package/lib/testing/node/spawned-server.js.map +1 -1
  213. package/lib/testing/playwright/flaky-network-proxy.d.ts +76 -0
  214. package/lib/testing/playwright/flaky-network-proxy.d.ts.map +1 -0
  215. package/lib/testing/playwright/flaky-network-proxy.js +164 -0
  216. package/lib/testing/playwright/flaky-network-proxy.js.map +1 -0
  217. package/lib/testing/playwright/index.d.ts +1 -0
  218. package/lib/testing/playwright/index.d.ts.map +1 -1
  219. package/lib/testing/playwright/index.js +1 -0
  220. package/lib/testing/playwright/index.js.map +1 -1
  221. package/lib/testing/playwright/server-log-capture.d.ts +14 -4
  222. package/lib/testing/playwright/server-log-capture.d.ts.map +1 -1
  223. package/lib/testing/playwright/server-log-capture.js +21 -4
  224. package/lib/testing/playwright/server-log-capture.js.map +1 -1
  225. package/lib/testing/playwright/server-log-rename-reporter.d.ts.map +1 -1
  226. package/lib/testing/playwright/server-log-rename-reporter.js +10 -1
  227. package/lib/testing/playwright/server-log-rename-reporter.js.map +1 -1
  228. package/lib/testing/stub-ast-document-manager.d.ts +5 -5
  229. package/lib/testing/stub-ast-document-manager.d.ts.map +1 -1
  230. package/lib/testing/stub-ast-document-manager.js +4 -6
  231. package/lib/testing/stub-ast-document-manager.js.map +1 -1
  232. package/lib/testing/stub-document-builder.d.ts +8 -0
  233. package/lib/testing/stub-document-builder.d.ts.map +1 -1
  234. package/lib/testing/stub-document-builder.js +22 -3
  235. package/lib/testing/stub-document-builder.js.map +1 -1
  236. package/lib/testing/stub-langium-documents.d.ts +3 -2
  237. package/lib/testing/stub-langium-documents.d.ts.map +1 -1
  238. package/lib/testing/stub-langium-documents.js.map +1 -1
  239. package/lib/testing/stub-model-service.d.ts +5 -4
  240. package/lib/testing/stub-model-service.d.ts.map +1 -1
  241. package/lib/testing/stub-model-service.js +2 -2
  242. package/lib/testing/stub-model-service.js.map +1 -1
  243. package/package.json +17 -8
  244. package/src/documents/ast-document-manager.ts +129 -36
  245. package/src/documents/client-ids.ts +5 -22
  246. package/src/documents/hydranium-text-documents.ts +167 -61
  247. package/src/documents/language-client-text-shadow.ts +59 -11
  248. package/src/documents/self-save-registry.ts +52 -13
  249. package/src/index.ts +5 -0
  250. package/src/langium/ast-extension/ast-node-builder.ts +66 -22
  251. package/src/langium/bootstrap.ts +16 -9
  252. package/src/langium/config/configuration-provider.ts +119 -0
  253. package/src/langium/config/index.ts +1 -0
  254. package/src/langium/diagnostics/lsp-logger.ts +41 -6
  255. package/src/langium/document-builder/build-pipeline-integration.ts +14 -1
  256. package/src/langium/document-builder/build-session.ts +87 -0
  257. package/src/langium/document-builder/document-builder.ts +339 -11
  258. package/src/langium/document-builder/index.ts +1 -0
  259. package/src/langium/integration-services.ts +67 -6
  260. package/src/langium/integrity/integrity-service.ts +35 -1
  261. package/src/langium/keys/containment.ts +87 -0
  262. package/src/langium/keys/index.ts +1 -0
  263. package/src/langium/keys/name-based-key-provider.ts +4 -0
  264. package/src/langium/model-service/model-service.ts +211 -30
  265. package/src/langium/module.ts +106 -21
  266. package/src/langium/naming/name-provider.ts +27 -5
  267. package/src/langium/naming/name-separator-validation.ts +34 -5
  268. package/src/langium/residency/cst-residency-service.ts +36 -12
  269. package/src/langium/scope/hydranium-scope-provider.ts +40 -22
  270. package/src/langium/service-registry.ts +6 -2
  271. package/src/langium/shared-services.ts +19 -2
  272. package/src/langium/transfer/transfer-encoder.ts +85 -24
  273. package/src/langium/validation/document-validator.ts +537 -11
  274. package/src/langium/workspace/document-uri-policy.ts +3 -4
  275. package/src/langium/workspace/hydranium-langium-document-factory.ts +34 -0
  276. package/src/langium/workspace/hydranium-workspace-manager.ts +30 -1
  277. package/src/langium/workspace/in-memory-file-system-provider.ts +13 -3
  278. package/src/langium/workspace/initialize-workspace.ts +34 -5
  279. package/src/langium/workspace/langium-documents.ts +106 -33
  280. package/src/locale/index.ts +10 -0
  281. package/src/locale/server-locale.ts +86 -0
  282. package/src/lsp/hydranium-document-update-handler.ts +1 -1
  283. package/src/lsp/index.ts +1 -1
  284. package/src/lsp/lsp-latency.ts +62 -0
  285. package/src/lsp/semantic-token-provider.ts +74 -7
  286. package/src/messages/carriers.ts +78 -0
  287. package/src/messages/index.ts +36 -0
  288. package/src/messages/renderer.ts +204 -0
  289. package/src/node/heap-ceiling.ts +108 -0
  290. package/src/node/index.ts +1 -0
  291. package/src/node/latency-from-env.ts +1 -1
  292. package/src/node/node-file-system-provider.ts +1 -41
  293. package/src/node/profiling-run.ts +3 -3
  294. package/src/node/rename-over-open-readers.ts +78 -0
  295. package/src/testing/fake-document.ts +24 -5
  296. package/src/testing/make-noop-shared-services.ts +50 -2
  297. package/src/testing/make-test-services.ts +73 -18
  298. package/src/testing/node/spawned-server.ts +35 -1
  299. package/src/testing/playwright/flaky-network-proxy.ts +240 -0
  300. package/src/testing/playwright/index.ts +1 -0
  301. package/src/testing/playwright/server-log-capture.ts +24 -5
  302. package/src/testing/playwright/server-log-rename-reporter.ts +12 -1
  303. package/src/testing/stub-ast-document-manager.ts +10 -14
  304. package/src/testing/stub-document-builder.ts +32 -3
  305. package/src/testing/stub-langium-documents.ts +3 -2
  306. package/src/testing/stub-model-service.ts +7 -6
  307. package/lib/lsp/instrument-connection.d.ts +0 -36
  308. package/lib/lsp/instrument-connection.d.ts.map +0 -1
  309. package/lib/lsp/instrument-connection.js +0 -62
  310. package/lib/lsp/instrument-connection.js.map +0 -1
  311. package/src/lsp/instrument-connection.ts +0 -66
@@ -0,0 +1,78 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, 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 * as fsp from 'node:fs/promises';
11
+
12
+ /**
13
+ * Errnos Windows raises when a replacing `rename` meets a handle already on the
14
+ * destination open. POSIX renames are unaffected by open handles, so these
15
+ * cannot fire there and the retry below costs nothing.
16
+ */
17
+ export const RENAME_CONTENTION_CODES = new Set(['EPERM', 'EACCES', 'EBUSY']);
18
+
19
+ /**
20
+ * How long a replacing rename may keep retrying before it gives up.
21
+ *
22
+ * BOUNDED, not open-ended: a genuine permission fault raises the same errno as
23
+ * contention and is distinguishable from it only by never clearing, so an
24
+ * unbounded wait would convert a hard error into a hang.
25
+ *
26
+ * The figure is a judgement, not a measurement, and the honest bound on it is
27
+ * that the previous 2s was observed EXHAUSTED on a loaded Windows CI runner
28
+ * while no upper bound has been established. What would settle it is recording
29
+ * how long a real contended rename takes to clear. graceful-fs tolerates 60s
30
+ * for Windows rename contention, but that is weaker support than it looks: it
31
+ * retries only where the destination has GONE, which is the opposite of
32
+ * replacing one a reader holds open.
33
+ */
34
+ export const RENAME_BUDGET_MS = 10_000;
35
+
36
+ /** Seams for the test, which cannot make a real rename fail on demand. */
37
+ export interface RenameOverOpenReadersOptions {
38
+ readonly rename?: (from: string, to: string) => Promise<void>;
39
+ readonly budgetMs?: number;
40
+ }
41
+
42
+ /**
43
+ * `rename` the staged content over `destination`, retrying while Windows
44
+ * reports the destination as held open.
45
+ *
46
+ * The staging file exists so a reader never sees a half-written file, and on
47
+ * Windows the very presence of that reader is what makes the replacing rename
48
+ * fail — so the indivisible write breaks under exactly the contention it was
49
+ * built for, and it breaks intermittently, which is worse than never working.
50
+ * A reader's handle is released in milliseconds, so waiting turns a spurious
51
+ * failure into a slightly later success.
52
+ *
53
+ * Exhausting the budget rethrows the last failure, leaving the caller's cleanup
54
+ * and error contract unchanged. An errno OUTSIDE the contention set is rethrown
55
+ * without waiting at all: retrying a fault that will not clear only delays it.
56
+ */
57
+ export async function renameOverOpenReaders(
58
+ staging: string,
59
+ destination: string,
60
+ options: RenameOverOpenReadersOptions = {}
61
+ ): Promise<void> {
62
+ const rename = options.rename ?? fsp.rename;
63
+ const deadline = Date.now() + (options.budgetMs ?? RENAME_BUDGET_MS);
64
+ for (let attempt = 0; ; attempt++) {
65
+ try {
66
+ await rename(staging, destination);
67
+ return;
68
+ } catch (err: unknown) {
69
+ const code = err instanceof Error && 'code' in err ? String((err as NodeJS.ErrnoException).code) : undefined;
70
+ if (code === undefined || !RENAME_CONTENTION_CODES.has(code) || Date.now() >= deadline) {
71
+ throw err;
72
+ }
73
+ // Backs off to keep a long contention window from spinning, capped so
74
+ // a late attempt still lands promptly once the handle is released.
75
+ await new Promise(resolve => setTimeout(resolve, Math.min(2 ** attempt, 50)));
76
+ }
77
+ }
78
+ }
@@ -7,7 +7,9 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
- import { DocumentState, type AstNode, type LangiumDocument, UriUtils, type URI } from '@hydranium/langium';
10
+ import { DocumentState, type AstNode, type AstReflection, type LangiumDocument, UriUtils, type URI } from '@hydranium/langium';
11
+ import { buildAstNode } from '../langium/ast-extension/ast-node-builder.js';
12
+ import { type AstDiagnostic } from '../langium/validation/document-validator.js';
11
13
 
12
14
  /**
13
15
  * Build a typed AST-node fixture for unit tests.
@@ -23,12 +25,29 @@ import { DocumentState, type AstNode, type LangiumDocument, UriUtils, type URI }
23
25
  * framework synthetic node, and marking it would change scope/index behaviour.
24
26
  * A test that wants a genuine synthetic node calls `markSynthetic` explicitly.
25
27
  *
28
+ * **Without a `reflection`, the fixture carries only what the test spells out**,
29
+ * which is a shape production never produces: a parsed or built node always has
30
+ * its grammar-declared containment arrays, so a fixture that omits one lets a
31
+ * test pass against an impossible node, and lets code that walks a list crash
32
+ * only under test. Pass the grammar's reflection whenever the type is a real
33
+ * one and the code under test reads a collection off it. Fixtures typed against
34
+ * ad-hoc names have no reflection to pass and keep the plain form.
35
+ *
26
36
  * @param node the node shape — `$type` is required; every other property is a
27
37
  * value the test cares about (`$container`, cross-reference values, internal
28
- * `_id` fields, …). The object is returned by reference, not copied.
38
+ * `_id` fields, …).
39
+ * @param reflection when given, pre-fills the grammar's declared defaults the
40
+ * same way {@link buildAstNode} does, and the result is a fresh object rather
41
+ * than `node` itself. Omitted, `node` is returned by reference.
29
42
  */
30
- export function makeFakeAstNode<TAst extends AstNode = AstNode>(node: { $type: string } & Record<string, unknown>): TAst {
31
- return node as unknown as TAst;
43
+ export function makeFakeAstNode<TAst extends AstNode = AstNode>(
44
+ node: { $type: string } & Record<string, unknown>,
45
+ reflection?: AstReflection
46
+ ): TAst {
47
+ if (!reflection) {
48
+ return node as unknown as TAst;
49
+ }
50
+ return buildAstNode<TAst>(reflection, node.$type, node);
32
51
  }
33
52
 
34
53
  /**
@@ -86,7 +105,7 @@ function stringifyFakeRoot(root: unknown): string {
86
105
  * staging. Override via `options.state` for tests that gate on earlier
87
106
  * phases.
88
107
  */
89
- export function makeFakeDocument<TAst extends AstNode = AstNode, TDiagnostic = unknown>(
108
+ export function makeFakeDocument<TAst extends AstNode = AstNode, TDiagnostic extends AstDiagnostic = AstDiagnostic>(
90
109
  uri: string | URI,
91
110
  root: TAst,
92
111
  options: FakeDocumentOptions<TAst, TDiagnostic> = {}
@@ -9,6 +9,8 @@
9
9
 
10
10
  import { type Clock, DefaultTracer, type Logger, NoopLogger, SystemClock, type Tracer } from '@hydranium/protocol';
11
11
  import type { ServerSharedServicesMinimal } from '../langium/shared-services.js';
12
+ import { DefaultServerLocale, type ServerLocale } from '../locale/server-locale.js';
13
+ import { DefaultMessageRenderer, type MessageRenderer } from '../messages/renderer.js';
12
14
 
13
15
  /**
14
16
  * Overrides for {@link makeNoopSharedServices}. The three observability slots
@@ -29,6 +31,18 @@ export interface NoopSharedServicesOverrides {
29
31
  Logger?: Logger;
30
32
  /** Bound on the top-level `Tracer` slot. Default: a `DefaultTracer` over `Logger` + `Clock`. */
31
33
  Tracer?: Tracer;
34
+ /** Factory for the `ServerLocale` slot. Default: a real one, reporting no locale. */
35
+ ServerLocale?: (services: ServerSharedServicesMinimal) => ServerLocale;
36
+ /**
37
+ * Factory for the `MessageRenderer` slot. Default: the real
38
+ * {@link DefaultMessageRenderer}, whose no-catalogue behaviour is a
39
+ * pass-through — so the default is the framework's own behaviour, not a stub.
40
+ *
41
+ * A factory rather than an instance, because a renderer reads the tree it is
42
+ * bound into. Both slots are resolved lazily, so an override is honoured
43
+ * however late the caller reads them.
44
+ */
45
+ MessageRenderer?: (services: ServerSharedServicesMinimal) => MessageRenderer;
32
46
  /** Per-slot `workspace` overrides. Slots left out resolve to `undefined`. */
33
47
  workspace?: Record<string, unknown>;
34
48
  /** Any other minimal slot (`ServiceRegistry`, `AstReflection`, `additionalDocuments`, …). */
@@ -63,11 +77,21 @@ export interface NoopSharedServicesOverrides {
63
77
  export function makeNoopSharedServices<T extends ServerSharedServicesMinimal = ServerSharedServicesMinimal>(
64
78
  overrides: NoopSharedServicesOverrides = {}
65
79
  ): T {
66
- const { Clock: clock, Logger: logger, Tracer: tracer, workspace, additionalDocuments, ...restTop } = overrides;
80
+ const {
81
+ Clock: clock,
82
+ Logger: logger,
83
+ Tracer: tracer,
84
+ ServerLocale: serverLocale,
85
+ MessageRenderer: messageRenderer,
86
+ workspace,
87
+ additionalDocuments,
88
+ ...restTop
89
+ } = overrides;
67
90
  const resolvedClock = clock ?? new SystemClock();
68
91
  const resolvedLogger = logger ?? new NoopLogger();
69
92
  const resolvedTracer = tracer ?? new DefaultTracer(resolvedLogger, resolvedClock);
70
- return {
93
+ // Assembled in two steps, because both defaults read the tree they belong to.
94
+ const services = {
71
95
  Clock: resolvedClock,
72
96
  Logger: resolvedLogger,
73
97
  Tracer: resolvedTracer,
@@ -75,4 +99,28 @@ export function makeNoopSharedServices<T extends ServerSharedServicesMinimal = S
75
99
  ...restTop,
76
100
  workspace: { ...(workspace ?? {}) }
77
101
  } as unknown as T;
102
+ // Lazy, like Langium's own per-slot construction — and load-bearing here:
103
+ // both services emit an `instantiated` trace, so building them eagerly puts
104
+ // two lines into the capture of every test that passes a capturing logger
105
+ // and asserts on emptiness.
106
+ defineLazySlot(services, 'ServerLocale', () => serverLocale?.(services) ?? new DefaultServerLocale(services));
107
+ defineLazySlot(services, 'MessageRenderer', () => messageRenderer?.(services) ?? new DefaultMessageRenderer(services));
108
+ return services;
109
+ }
110
+
111
+ /** Install `name` as a memoized getter, so nothing is constructed until it is read. */
112
+ function defineLazySlot(target: object, name: string, create: () => unknown): void {
113
+ let resolved: unknown;
114
+ let built = false;
115
+ Object.defineProperty(target, name, {
116
+ configurable: true,
117
+ enumerable: true,
118
+ get: () => {
119
+ if (!built) {
120
+ resolved = create();
121
+ built = true;
122
+ }
123
+ return resolved;
124
+ }
125
+ });
78
126
  }
@@ -18,11 +18,14 @@ import {
18
18
  type TransferDiagnostic,
19
19
  type TransferElement
20
20
  } from '@hydranium/protocol';
21
+ import { DefaultServerLocale, type ServerLocale } from '../locale/server-locale.js';
22
+ import { DefaultMessageRenderer, type MessageRenderer } from '../messages/renderer.js';
21
23
  import type { Harness } from '@hydranium/protocol/testing';
22
24
  import { type AstNode, type AstNodeDescription, type WorkspaceLock } from '@hydranium/langium';
23
25
  import type { ModelService, ModelServiceOptions } from '../langium/model-service/model-service.js';
24
26
  import type { ServerSharedServices } from '../langium/module.js';
25
- import { TransferEncoder } from '../langium/transfer/transfer-encoder.js';
27
+ import type { AstDiagnostic } from '../langium/validation/document-validator.js';
28
+ import { DefaultTransferEncoder, type TransferEncoder } from '../langium/transfer/transfer-encoder.js';
26
29
  import { DefaultDocumentUriPolicy, type DocumentUriPolicy } from '../langium/workspace/document-uri-policy.js';
27
30
  import { HydraniumWorkspaceLock } from '../langium/workspace/hydranium-workspace-lock.js';
28
31
  import type { FakeDocumentOptions } from './fake-document.js';
@@ -46,12 +49,21 @@ import { makeStubWritableFileSystem, type StubWritableFileSystem } from './stub-
46
49
  * to `ServerSharedServices` in {@link makeTestServices} is therefore the
47
50
  * single named place where the "stub stands in for the full service tree"
48
51
  * assertion happens.
52
+ *
53
+ * **`TDiagnostic` and `TTransferDiagnostic` are the two ends of a conversion
54
+ * and must stay separate.** The first is what the build left on
55
+ * `LangiumDocument.diagnostics` and what `ModelService` hands back on an
56
+ * `AstDocument`; the second is what `TransferEncoder.toTransferDiagnostic`
57
+ * produces for the wire. One parameter serving both makes the tree
58
+ * unbindable for an adopter who extends either shape, since extending one
59
+ * says nothing about the other.
49
60
  */
50
61
  export interface TestSharedServices<
51
62
  TAst extends AstNode = AstNode,
52
- TDiagnostic extends TransferDiagnostic = TransferDiagnostic,
63
+ TDiagnostic extends AstDiagnostic = AstDiagnostic,
53
64
  TTransfer extends TransferElement = TransferElement,
54
- TProject extends Project = Project
65
+ TProject extends Project = Project,
66
+ TTransferDiagnostic extends TransferDiagnostic = TransferDiagnostic
55
67
  > {
56
68
  readonly Clock: Clock;
57
69
  readonly Logger: Logger;
@@ -90,17 +102,27 @@ export interface TestSharedServices<
90
102
  WorkspaceLock: WorkspaceLock;
91
103
  };
92
104
  readonly model: {
93
- TransferEncoder: TransferEncoder<unknown, TDiagnostic>;
105
+ TransferEncoder: TransferEncoder<TTransferDiagnostic>;
94
106
  ModelService: ModelService<TAst, TDiagnostic, TTransfer>;
95
107
  };
108
+ /**
109
+ * The REAL services, not stubs — the framework renderer's no-catalogue
110
+ * behaviour is a pass-through, so the stub tree agrees with a production one
111
+ * unless a test installs a catalogue. Bound rather than omitted because
112
+ * `HydraniumDocumentBuilder` renders through the renderer on every
113
+ * `Validated` phase, where an omitted slot is a `TypeError`.
114
+ */
115
+ readonly ServerLocale: ServerLocale;
116
+ readonly MessageRenderer: MessageRenderer;
96
117
  }
97
118
 
98
119
  /** Optional configuration for {@link makeTestServices}. */
99
120
  export interface MakeTestServicesOptions<
100
121
  TAst extends AstNode = AstNode,
101
- TDiagnostic extends TransferDiagnostic = TransferDiagnostic,
122
+ TDiagnostic extends AstDiagnostic = AstDiagnostic,
102
123
  TTransfer extends TransferElement = TransferElement,
103
- TProject extends Project = Project
124
+ TProject extends Project = Project,
125
+ TTransferDiagnostic extends TransferDiagnostic = TransferDiagnostic
104
126
  > {
105
127
  /**
106
128
  * Serialiser used by the bundled `StubModelService`. The stub tree
@@ -112,6 +134,14 @@ export interface MakeTestServicesOptions<
112
134
  * for tests that don't assert on the produced text.
113
135
  */
114
136
  serialize?: (uri: string, root: TTransfer) => string;
137
+ /**
138
+ * Renderer bound on `MessageRenderer`. Default: the framework's own, which
139
+ * passes every sentence through unchanged. Supply one to install a catalogue
140
+ * or to drive the throwing path.
141
+ */
142
+ messageRenderer?: (services: ServerSharedServices<TProject>) => MessageRenderer;
143
+ /** Locale handed to the bundle's {@link ServerLocale}. Default: none, i.e. the framework's English. */
144
+ locale?: string;
115
145
  /**
116
146
  * Languages to register on a {@link StubServiceRegistry} bound on the
117
147
  * `ServiceRegistry` slot. This is how a test gets multi-language routing:
@@ -187,7 +217,7 @@ export interface MakeTestServicesOptions<
187
217
  * Override the {@link TransferEncoder} factory. Default: framework
188
218
  * {@link TransferEncoder} with no overrides.
189
219
  */
190
- transferEncoder?: (services: ServerSharedServices<TProject>) => TransferEncoder<unknown, TDiagnostic>;
220
+ transferEncoder?: (services: ServerSharedServices<TProject>) => TransferEncoder<TTransferDiagnostic>;
191
221
  }
192
222
 
193
223
  /**
@@ -205,9 +235,10 @@ export interface MakeTestServicesOptions<
205
235
  */
206
236
  export interface TestServicesBundle<
207
237
  TAst extends AstNode = AstNode,
208
- TDiagnostic extends TransferDiagnostic = TransferDiagnostic,
238
+ TDiagnostic extends AstDiagnostic = AstDiagnostic,
209
239
  TTransfer extends TransferElement = TransferElement,
210
- TProject extends Project = Project
240
+ TProject extends Project = Project,
241
+ TTransferDiagnostic extends TransferDiagnostic = TransferDiagnostic
211
242
  > extends Harness {
212
243
  readonly services: ServerSharedServices<TProject>;
213
244
  readonly documents: StubLangiumDocuments<TAst, TDiagnostic>;
@@ -229,10 +260,18 @@ export interface TestServicesBundle<
229
260
  */
230
261
  readonly indexManager: StubIndexManager | undefined;
231
262
  readonly modelService: ModelService<TAst, TDiagnostic, TTransfer>;
232
- readonly transferEncoder: TransferEncoder<unknown, TDiagnostic>;
263
+ readonly transferEncoder: TransferEncoder<TTransferDiagnostic>;
233
264
  readonly logger: Logger;
234
265
  /** The clock bound on the `Clock` slot — a `makeFakeClock()` if one was passed. */
235
266
  readonly clock: Clock;
267
+ /**
268
+ * The service bound on `ServerLocale`, so a test can hand over a locale
269
+ * mid-run — which is also how it verifies the renderer reads the locale per
270
+ * render rather than caching it at construction.
271
+ */
272
+ readonly serverLocale: ServerLocale;
273
+ /** The renderer bound on `MessageRenderer` — the framework's own unless one was supplied. */
274
+ readonly messageRenderer: MessageRenderer;
236
275
  }
237
276
 
238
277
  /**
@@ -247,12 +286,13 @@ export interface TestServicesBundle<
247
286
  */
248
287
  export function makeTestServices<
249
288
  TAst extends AstNode = AstNode,
250
- TDiagnostic extends TransferDiagnostic = TransferDiagnostic,
289
+ TDiagnostic extends AstDiagnostic = AstDiagnostic,
251
290
  TTransfer extends TransferElement = TransferElement,
252
- TProject extends Project = Project
291
+ TProject extends Project = Project,
292
+ TTransferDiagnostic extends TransferDiagnostic = TransferDiagnostic
253
293
  >(
254
- options: MakeTestServicesOptions<TAst, TDiagnostic, TTransfer, TProject> = {}
255
- ): TestServicesBundle<TAst, TDiagnostic, TTransfer, TProject> {
294
+ options: MakeTestServicesOptions<TAst, TDiagnostic, TTransfer, TProject, TTransferDiagnostic> = {}
295
+ ): TestServicesBundle<TAst, TDiagnostic, TTransfer, TProject, TTransferDiagnostic> {
256
296
  const documents = makeStubLangiumDocuments<TAst, TDiagnostic>(options.seedDocuments);
257
297
  const textDocuments = makeStubHydraniumTextDocuments();
258
298
  const documentBuilder = makeStubDocumentBuilder();
@@ -289,7 +329,7 @@ export function makeTestServices<
289
329
  // (after the factories run); the literal uses unsafe casts to
290
330
  // partially-built objects to satisfy TestSharedServices, then patches
291
331
  // them in.
292
- const services: TestSharedServices<TAst, TDiagnostic, TTransfer, TProject> = {
332
+ const services: TestSharedServices<TAst, TDiagnostic, TTransfer, TProject, TTransferDiagnostic> = {
293
333
  Clock: clock,
294
334
  Logger: logger,
295
335
  Tracer: tracer,
@@ -311,20 +351,33 @@ export function makeTestServices<
311
351
  ...(indexManager ? { IndexManager: indexManager } : {}),
312
352
  WorkspaceLock: new HydraniumWorkspaceLock()
313
353
  },
314
- model: {} as TestSharedServices<TAst, TDiagnostic, TTransfer, TProject>['model']
354
+ model: {} as TestSharedServices<TAst, TDiagnostic, TTransfer, TProject, TTransferDiagnostic>['model'],
355
+ ServerLocale: {} as ServerLocale,
356
+ MessageRenderer: {} as DefaultMessageRenderer
315
357
  };
316
358
  const sharedServices = services as unknown as ServerSharedServices<TProject>;
317
359
 
360
+ // Patched in after the literal, like `model` below: both read the tree they
361
+ // belong to, and the renderer reads the locale service.
362
+ const mutableMessages = services as { ServerLocale: ServerLocale; MessageRenderer: MessageRenderer };
363
+ const serverLocale = new DefaultServerLocale(sharedServices);
364
+ if (options.locale) {
365
+ serverLocale.accept(options.locale);
366
+ }
367
+ mutableMessages.ServerLocale = serverLocale;
368
+ const messageRenderer = options.messageRenderer?.(sharedServices) ?? new DefaultMessageRenderer(sharedServices);
369
+ mutableMessages.MessageRenderer = messageRenderer;
370
+
318
371
  const serialize = options.serialize ?? ((_uri: string, root: TTransfer) => JSON.stringify(root));
319
372
  const transferEncoder = options.transferEncoder
320
373
  ? options.transferEncoder(sharedServices)
321
- : new TransferEncoder<unknown, TDiagnostic>(sharedServices);
374
+ : new DefaultTransferEncoder<unknown, TTransferDiagnostic>(sharedServices);
322
375
  const modelService = options.modelService
323
376
  ? options.modelService(sharedServices)
324
377
  : makeStubModelService<TAst, TDiagnostic, TTransfer>(sharedServices, serialize, options.modelServiceOptions);
325
378
 
326
379
  const mutableModel = services.model as {
327
- TransferEncoder: TransferEncoder<unknown, TDiagnostic>;
380
+ TransferEncoder: TransferEncoder<TTransferDiagnostic>;
328
381
  ModelService: ModelService<TAst, TDiagnostic, TTransfer>;
329
382
  };
330
383
  mutableModel.TransferEncoder = transferEncoder;
@@ -346,6 +399,8 @@ export function makeTestServices<
346
399
  transferEncoder,
347
400
  logger,
348
401
  clock,
402
+ serverLocale,
403
+ messageRenderer,
349
404
  dispose: () => {
350
405
  // No-op: the bundled stubs hold only in-memory state (maps, arrays),
351
406
  // released with the bundle when it goes out of scope. The hook exists
@@ -203,6 +203,37 @@ export interface SpawnedServer extends Harness {
203
203
  dispose(): Promise<number | null>;
204
204
  }
205
205
 
206
+ /**
207
+ * Every server started and not yet disposed, so {@link disposeSpawnedServers}
208
+ * can reach one a test forgot.
209
+ *
210
+ * Holds the server — and through it the `ChildProcess` — rather than a pid. A
211
+ * pid identifies a process only while that process is alive: once it exits the
212
+ * OS may reissue the number, and Windows does so within seconds. A teardown
213
+ * that signalled a recorded pid would therefore reach whatever holds it now,
214
+ * which on a concurrent build is another task's compiler or test runner.
215
+ */
216
+ const liveServers = new Set<SpawnedServer>();
217
+
218
+ /**
219
+ * Terminate every server this module started that has not been disposed, and
220
+ * forget them.
221
+ *
222
+ * Safe to call when none are outstanding, and safe to call twice. Nothing here
223
+ * can reach a process this module did not spawn, because every kill goes
224
+ * through the `ChildProcess` handle libuv holds and libuv will not signal a
225
+ * child it has already reaped.
226
+ *
227
+ * A suite that disposes each server as it finishes needs this only as a net; a
228
+ * suite that does not needs it, because an orphan holding a port or a pipe
229
+ * makes the NEXT run's verdict meaningless.
230
+ */
231
+ export async function disposeSpawnedServers(): Promise<void> {
232
+ const outstanding = [...liveServers];
233
+ liveServers.clear();
234
+ await Promise.all(outstanding.map(server => server.dispose()));
235
+ }
236
+
206
237
  /** The captured streams, quoted into a boot-failure message. */
207
238
  function bootDiagnosis(stdoutPrefix: string, stderr: string): string {
208
239
  return (
@@ -390,7 +421,7 @@ export async function startSpawnedServer(options: SpawnedServerOptions): Promise
390
421
  return exitCode;
391
422
  }
392
423
 
393
- return {
424
+ const server: SpawnedServer = {
394
425
  connection,
395
426
  pid: child.pid,
396
427
  initializeResult,
@@ -440,8 +471,11 @@ export async function startSpawnedServer(options: SpawnedServerOptions): Promise
440
471
  });
441
472
  },
442
473
  dispose(): Promise<number | null> {
474
+ liveServers.delete(server);
443
475
  disposal ??= terminate();
444
476
  return disposal;
445
477
  }
446
478
  };
479
+ liveServers.add(server);
480
+ return server;
447
481
  }