@hydranium/core 1.0.0-next.14 → 1.0.0-next.148

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 (489) hide show
  1. package/README.md +5 -1
  2. package/lib/documents/ast-document-manager.d.ts +187 -27
  3. package/lib/documents/ast-document-manager.d.ts.map +1 -1
  4. package/lib/documents/ast-document-manager.js +235 -38
  5. package/lib/documents/ast-document-manager.js.map +1 -1
  6. package/lib/documents/client-ids.d.ts +5 -19
  7. package/lib/documents/client-ids.d.ts.map +1 -1
  8. package/lib/documents/client-ids.js +5 -19
  9. package/lib/documents/client-ids.js.map +1 -1
  10. package/lib/documents/client-session-errors.d.ts +15 -0
  11. package/lib/documents/client-session-errors.d.ts.map +1 -0
  12. package/lib/documents/client-session-errors.js +15 -0
  13. package/lib/documents/client-session-errors.js.map +1 -0
  14. package/lib/documents/client-session-registry.d.ts +138 -0
  15. package/lib/documents/client-session-registry.d.ts.map +1 -0
  16. package/lib/documents/client-session-registry.js +187 -0
  17. package/lib/documents/client-session-registry.js.map +1 -0
  18. package/lib/documents/file-system-task-queue.d.ts +49 -0
  19. package/lib/documents/file-system-task-queue.d.ts.map +1 -0
  20. package/lib/documents/file-system-task-queue.js +34 -0
  21. package/lib/documents/file-system-task-queue.js.map +1 -0
  22. package/lib/documents/hydranium-text-documents.d.ts +386 -57
  23. package/lib/documents/hydranium-text-documents.d.ts.map +1 -1
  24. package/lib/documents/hydranium-text-documents.js +700 -126
  25. package/lib/documents/hydranium-text-documents.js.map +1 -1
  26. package/lib/documents/index.d.ts +3 -0
  27. package/lib/documents/index.d.ts.map +1 -1
  28. package/lib/documents/index.js +3 -0
  29. package/lib/documents/index.js.map +1 -1
  30. package/lib/documents/language-client-text-shadow.d.ts +41 -9
  31. package/lib/documents/language-client-text-shadow.d.ts.map +1 -1
  32. package/lib/documents/language-client-text-shadow.js +56 -11
  33. package/lib/documents/language-client-text-shadow.js.map +1 -1
  34. package/lib/documents/self-save-registry.d.ts +37 -6
  35. package/lib/documents/self-save-registry.d.ts.map +1 -1
  36. package/lib/documents/self-save-registry.js +28 -15
  37. package/lib/documents/self-save-registry.js.map +1 -1
  38. package/lib/index.d.ts +3 -0
  39. package/lib/index.d.ts.map +1 -1
  40. package/lib/index.js +6 -0
  41. package/lib/index.js.map +1 -1
  42. package/lib/langium/ast-extension/ast-node-builder.d.ts +32 -9
  43. package/lib/langium/ast-extension/ast-node-builder.d.ts.map +1 -1
  44. package/lib/langium/ast-extension/ast-node-builder.js +60 -22
  45. package/lib/langium/ast-extension/ast-node-builder.js.map +1 -1
  46. package/lib/langium/bootstrap.d.ts.map +1 -1
  47. package/lib/langium/bootstrap.js +16 -9
  48. package/lib/langium/bootstrap.js.map +1 -1
  49. package/lib/langium/config/configuration-provider.d.ts +81 -0
  50. package/lib/langium/config/configuration-provider.d.ts.map +1 -0
  51. package/lib/langium/config/configuration-provider.js +112 -0
  52. package/lib/langium/config/configuration-provider.js.map +1 -0
  53. package/lib/langium/config/index.d.ts +1 -0
  54. package/lib/langium/config/index.d.ts.map +1 -1
  55. package/lib/langium/config/index.js +1 -0
  56. package/lib/langium/config/index.js.map +1 -1
  57. package/lib/langium/diagnostics/lsp-logger.d.ts +17 -0
  58. package/lib/langium/diagnostics/lsp-logger.d.ts.map +1 -1
  59. package/lib/langium/diagnostics/lsp-logger.js +40 -6
  60. package/lib/langium/diagnostics/lsp-logger.js.map +1 -1
  61. package/lib/langium/document-builder/build-pipeline-integration.d.ts +21 -1
  62. package/lib/langium/document-builder/build-pipeline-integration.d.ts.map +1 -1
  63. package/lib/langium/document-builder/build-pipeline-integration.js +32 -5
  64. package/lib/langium/document-builder/build-pipeline-integration.js.map +1 -1
  65. package/lib/langium/document-builder/build-session.d.ts +93 -0
  66. package/lib/langium/document-builder/build-session.d.ts.map +1 -0
  67. package/lib/langium/document-builder/build-session.js +72 -0
  68. package/lib/langium/document-builder/build-session.js.map +1 -0
  69. package/lib/langium/document-builder/document-builder.d.ts +352 -16
  70. package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
  71. package/lib/langium/document-builder/document-builder.js +616 -40
  72. package/lib/langium/document-builder/document-builder.js.map +1 -1
  73. package/lib/langium/document-builder/index.d.ts +1 -0
  74. package/lib/langium/document-builder/index.d.ts.map +1 -1
  75. package/lib/langium/document-builder/index.js +1 -0
  76. package/lib/langium/document-builder/index.js.map +1 -1
  77. package/lib/langium/integration-services.d.ts +45 -1
  78. package/lib/langium/integration-services.d.ts.map +1 -1
  79. package/lib/langium/integration-services.js +10 -5
  80. package/lib/langium/integration-services.js.map +1 -1
  81. package/lib/langium/integrity/integrity-rule.d.ts +13 -5
  82. package/lib/langium/integrity/integrity-rule.d.ts.map +1 -1
  83. package/lib/langium/integrity/integrity-rule.js.map +1 -1
  84. package/lib/langium/integrity/integrity-service.d.ts +50 -6
  85. package/lib/langium/integrity/integrity-service.d.ts.map +1 -1
  86. package/lib/langium/integrity/integrity-service.js +193 -17
  87. package/lib/langium/integrity/integrity-service.js.map +1 -1
  88. package/lib/langium/keys/containment.d.ts +64 -0
  89. package/lib/langium/keys/containment.d.ts.map +1 -0
  90. package/lib/langium/keys/containment.js +53 -0
  91. package/lib/langium/keys/containment.js.map +1 -0
  92. package/lib/langium/keys/index.d.ts +1 -0
  93. package/lib/langium/keys/index.d.ts.map +1 -1
  94. package/lib/langium/keys/index.js +1 -0
  95. package/lib/langium/keys/index.js.map +1 -1
  96. package/lib/langium/keys/name-based-key-provider.d.ts +4 -0
  97. package/lib/langium/keys/name-based-key-provider.d.ts.map +1 -1
  98. package/lib/langium/keys/name-based-key-provider.js +4 -0
  99. package/lib/langium/keys/name-based-key-provider.js.map +1 -1
  100. package/lib/langium/language-module.d.ts +27 -2
  101. package/lib/langium/language-module.d.ts.map +1 -1
  102. package/lib/langium/language-module.js +16 -2
  103. package/lib/langium/language-module.js.map +1 -1
  104. package/lib/langium/model-service/client-session.d.ts +307 -0
  105. package/lib/langium/model-service/client-session.d.ts.map +1 -0
  106. package/lib/langium/model-service/client-session.js +395 -0
  107. package/lib/langium/model-service/client-session.js.map +1 -0
  108. package/lib/langium/model-service/index.d.ts +1 -0
  109. package/lib/langium/model-service/index.d.ts.map +1 -1
  110. package/lib/langium/model-service/index.js +1 -0
  111. package/lib/langium/model-service/index.js.map +1 -1
  112. package/lib/langium/model-service/model-service.d.ts +261 -198
  113. package/lib/langium/model-service/model-service.d.ts.map +1 -1
  114. package/lib/langium/model-service/model-service.js +232 -344
  115. package/lib/langium/model-service/model-service.js.map +1 -1
  116. package/lib/langium/module.d.ts +107 -24
  117. package/lib/langium/module.d.ts.map +1 -1
  118. package/lib/langium/module.js +30 -12
  119. package/lib/langium/module.js.map +1 -1
  120. package/lib/langium/naming/name-provider.d.ts +25 -4
  121. package/lib/langium/naming/name-provider.d.ts.map +1 -1
  122. package/lib/langium/naming/name-provider.js +2 -1
  123. package/lib/langium/naming/name-provider.js.map +1 -1
  124. package/lib/langium/naming/name-separator-validation.d.ts +23 -0
  125. package/lib/langium/naming/name-separator-validation.d.ts.map +1 -1
  126. package/lib/langium/naming/name-separator-validation.js +30 -1
  127. package/lib/langium/naming/name-separator-validation.js.map +1 -1
  128. package/lib/langium/residency/cst-residency-service.d.ts +31 -11
  129. package/lib/langium/residency/cst-residency-service.d.ts.map +1 -1
  130. package/lib/langium/residency/cst-residency-service.js +13 -56
  131. package/lib/langium/residency/cst-residency-service.js.map +1 -1
  132. package/lib/langium/scope/hydranium-scope-provider.d.ts +48 -13
  133. package/lib/langium/scope/hydranium-scope-provider.d.ts.map +1 -1
  134. package/lib/langium/scope/hydranium-scope-provider.js +56 -20
  135. package/lib/langium/scope/hydranium-scope-provider.js.map +1 -1
  136. package/lib/langium/serialization/abstract-serializer.d.ts +11 -20
  137. package/lib/langium/serialization/abstract-serializer.d.ts.map +1 -1
  138. package/lib/langium/serialization/abstract-serializer.js +12 -21
  139. package/lib/langium/serialization/abstract-serializer.js.map +1 -1
  140. package/lib/langium/service-registry.d.ts +15 -1
  141. package/lib/langium/service-registry.d.ts.map +1 -1
  142. package/lib/langium/service-registry.js +16 -0
  143. package/lib/langium/service-registry.js.map +1 -1
  144. package/lib/langium/shared-services.d.ts +15 -2
  145. package/lib/langium/shared-services.d.ts.map +1 -1
  146. package/lib/langium/shared-services.js.map +1 -1
  147. package/lib/langium/transfer/transfer-encoder.d.ts +60 -17
  148. package/lib/langium/transfer/transfer-encoder.d.ts.map +1 -1
  149. package/lib/langium/transfer/transfer-encoder.js +38 -16
  150. package/lib/langium/transfer/transfer-encoder.js.map +1 -1
  151. package/lib/langium/trivia/comment-preserver.d.ts +423 -0
  152. package/lib/langium/trivia/comment-preserver.d.ts.map +1 -0
  153. package/lib/langium/trivia/comment-preserver.js +906 -0
  154. package/lib/langium/trivia/comment-preserver.js.map +1 -0
  155. package/lib/langium/trivia/document-ending-preserver.d.ts +43 -0
  156. package/lib/langium/trivia/document-ending-preserver.d.ts.map +1 -0
  157. package/lib/langium/trivia/document-ending-preserver.js +48 -0
  158. package/lib/langium/trivia/document-ending-preserver.js.map +1 -0
  159. package/lib/langium/trivia/index.d.ts +14 -0
  160. package/lib/langium/trivia/index.d.ts.map +1 -0
  161. package/lib/langium/trivia/index.js +14 -0
  162. package/lib/langium/trivia/index.js.map +1 -0
  163. package/lib/langium/trivia/trivia-contribution.d.ts +37 -0
  164. package/lib/langium/trivia/trivia-contribution.d.ts.map +1 -0
  165. package/lib/langium/trivia/trivia-contribution.js +10 -0
  166. package/lib/langium/trivia/trivia-contribution.js.map +1 -0
  167. package/lib/langium/trivia/trivia-preserver.d.ts +50 -0
  168. package/lib/langium/trivia/trivia-preserver.d.ts.map +1 -0
  169. package/lib/langium/trivia/trivia-preserver.js +10 -0
  170. package/lib/langium/trivia/trivia-preserver.js.map +1 -0
  171. package/lib/langium/trivia/trivia-service.d.ts +70 -0
  172. package/lib/langium/trivia/trivia-service.d.ts.map +1 -0
  173. package/lib/langium/trivia/trivia-service.js +56 -0
  174. package/lib/langium/trivia/trivia-service.js.map +1 -0
  175. package/lib/langium/update-rewrite/update-rewrite.d.ts +1 -1
  176. package/lib/langium/validation/document-validator.d.ts +294 -10
  177. package/lib/langium/validation/document-validator.d.ts.map +1 -1
  178. package/lib/langium/validation/document-validator.js +448 -5
  179. package/lib/langium/validation/document-validator.js.map +1 -1
  180. package/lib/langium/workspace/document-uri-policy.d.ts +3 -4
  181. package/lib/langium/workspace/document-uri-policy.d.ts.map +1 -1
  182. package/lib/langium/workspace/document-uri-policy.js +3 -4
  183. package/lib/langium/workspace/document-uri-policy.js.map +1 -1
  184. package/lib/langium/workspace/file-system-provider.d.ts +9 -1
  185. package/lib/langium/workspace/file-system-provider.d.ts.map +1 -1
  186. package/lib/langium/workspace/file-system-provider.js +40 -7
  187. package/lib/langium/workspace/file-system-provider.js.map +1 -1
  188. package/lib/langium/workspace/hydranium-langium-document-factory.d.ts +25 -0
  189. package/lib/langium/workspace/hydranium-langium-document-factory.d.ts.map +1 -1
  190. package/lib/langium/workspace/hydranium-langium-document-factory.js +25 -0
  191. package/lib/langium/workspace/hydranium-langium-document-factory.js.map +1 -1
  192. package/lib/langium/workspace/hydranium-workspace-lock.d.ts +13 -0
  193. package/lib/langium/workspace/hydranium-workspace-lock.d.ts.map +1 -1
  194. package/lib/langium/workspace/hydranium-workspace-lock.js +18 -0
  195. package/lib/langium/workspace/hydranium-workspace-lock.js.map +1 -1
  196. package/lib/langium/workspace/hydranium-workspace-manager.d.ts +21 -1
  197. package/lib/langium/workspace/hydranium-workspace-manager.d.ts.map +1 -1
  198. package/lib/langium/workspace/hydranium-workspace-manager.js +28 -0
  199. package/lib/langium/workspace/hydranium-workspace-manager.js.map +1 -1
  200. package/lib/langium/workspace/in-memory-file-system-provider.d.ts +23 -0
  201. package/lib/langium/workspace/in-memory-file-system-provider.d.ts.map +1 -1
  202. package/lib/langium/workspace/in-memory-file-system-provider.js +55 -9
  203. package/lib/langium/workspace/in-memory-file-system-provider.js.map +1 -1
  204. package/lib/langium/workspace/initialize-workspace.d.ts +31 -2
  205. package/lib/langium/workspace/initialize-workspace.d.ts.map +1 -1
  206. package/lib/langium/workspace/initialize-workspace.js +13 -5
  207. package/lib/langium/workspace/initialize-workspace.js.map +1 -1
  208. package/lib/langium/workspace/langium-documents.d.ts +79 -17
  209. package/lib/langium/workspace/langium-documents.d.ts.map +1 -1
  210. package/lib/langium/workspace/langium-documents.js +83 -28
  211. package/lib/langium/workspace/langium-documents.js.map +1 -1
  212. package/lib/langium/workspace/virtual-document.d.ts +18 -3
  213. package/lib/langium/workspace/virtual-document.d.ts.map +1 -1
  214. package/lib/langium/workspace/virtual-document.js +19 -2
  215. package/lib/langium/workspace/virtual-document.js.map +1 -1
  216. package/lib/langium/workspace/write-lock-scope.d.ts +4 -3
  217. package/lib/langium/workspace/write-lock-scope.d.ts.map +1 -1
  218. package/lib/langium/workspace/write-lock-scope.js +4 -3
  219. package/lib/langium/workspace/write-lock-scope.js.map +1 -1
  220. package/lib/locale/index.d.ts +10 -0
  221. package/lib/locale/index.d.ts.map +1 -0
  222. package/lib/locale/index.js +10 -0
  223. package/lib/locale/index.js.map +1 -0
  224. package/lib/locale/server-locale.d.ts +73 -0
  225. package/lib/locale/server-locale.d.ts.map +1 -0
  226. package/lib/locale/server-locale.js +61 -0
  227. package/lib/locale/server-locale.js.map +1 -0
  228. package/lib/lsp/completion/hydranium-completion-provider.d.ts +44 -2
  229. package/lib/lsp/completion/hydranium-completion-provider.d.ts.map +1 -1
  230. package/lib/lsp/completion/hydranium-completion-provider.js +68 -0
  231. package/lib/lsp/completion/hydranium-completion-provider.js.map +1 -1
  232. package/lib/lsp/connection-features.d.ts +23 -0
  233. package/lib/lsp/connection-features.d.ts.map +1 -0
  234. package/lib/lsp/connection-features.js +60 -0
  235. package/lib/lsp/connection-features.js.map +1 -0
  236. package/lib/lsp/diagnostics-connection.d.ts +27 -0
  237. package/lib/lsp/diagnostics-connection.d.ts.map +1 -0
  238. package/lib/lsp/diagnostics-connection.js +53 -0
  239. package/lib/lsp/diagnostics-connection.js.map +1 -0
  240. package/lib/lsp/hydranium-document-update-handler.d.ts +90 -79
  241. package/lib/lsp/hydranium-document-update-handler.d.ts.map +1 -1
  242. package/lib/lsp/hydranium-document-update-handler.js +152 -99
  243. package/lib/lsp/hydranium-document-update-handler.js.map +1 -1
  244. package/lib/lsp/index.d.ts +3 -1
  245. package/lib/lsp/index.d.ts.map +1 -1
  246. package/lib/lsp/index.js +3 -1
  247. package/lib/lsp/index.js.map +1 -1
  248. package/lib/lsp/lsp-latency.d.ts +39 -0
  249. package/lib/lsp/lsp-latency.d.ts.map +1 -0
  250. package/lib/lsp/lsp-latency.js +58 -0
  251. package/lib/lsp/lsp-latency.js.map +1 -0
  252. package/lib/lsp/semantic-token-provider.d.ts +34 -1
  253. package/lib/lsp/semantic-token-provider.d.ts.map +1 -1
  254. package/lib/lsp/semantic-token-provider.js +55 -8
  255. package/lib/lsp/semantic-token-provider.js.map +1 -1
  256. package/lib/lsp/shared-module.d.ts +4 -8
  257. package/lib/lsp/shared-module.d.ts.map +1 -1
  258. package/lib/lsp/shared-module.js.map +1 -1
  259. package/lib/lsp/start-language-server.d.ts +8 -7
  260. package/lib/lsp/start-language-server.d.ts.map +1 -1
  261. package/lib/lsp/start-language-server.js +13 -7
  262. package/lib/lsp/start-language-server.js.map +1 -1
  263. package/lib/messages/carriers.d.ts +54 -0
  264. package/lib/messages/carriers.d.ts.map +1 -0
  265. package/lib/messages/carriers.js +62 -0
  266. package/lib/messages/carriers.js.map +1 -0
  267. package/lib/messages/index.d.ts +25 -0
  268. package/lib/messages/index.d.ts.map +1 -0
  269. package/lib/messages/index.js +25 -0
  270. package/lib/messages/index.js.map +1 -0
  271. package/lib/messages/renderer.d.ts +142 -0
  272. package/lib/messages/renderer.d.ts.map +1 -0
  273. package/lib/messages/renderer.js +165 -0
  274. package/lib/messages/renderer.js.map +1 -0
  275. package/lib/node/heap-ceiling.d.ts +61 -0
  276. package/lib/node/heap-ceiling.d.ts.map +1 -0
  277. package/lib/node/heap-ceiling.js +72 -0
  278. package/lib/node/heap-ceiling.js.map +1 -0
  279. package/lib/node/index.d.ts +1 -0
  280. package/lib/node/index.d.ts.map +1 -1
  281. package/lib/node/index.js +1 -0
  282. package/lib/node/index.js.map +1 -1
  283. package/lib/node/latency-from-env.d.ts +1 -1
  284. package/lib/node/latency-from-env.js +1 -1
  285. package/lib/node/measure-memory.d.ts.map +1 -1
  286. package/lib/node/measure-memory.js +1 -0
  287. package/lib/node/measure-memory.js.map +1 -1
  288. package/lib/node/node-file-system-provider.d.ts +35 -1
  289. package/lib/node/node-file-system-provider.d.ts.map +1 -1
  290. package/lib/node/node-file-system-provider.js +140 -45
  291. package/lib/node/node-file-system-provider.js.map +1 -1
  292. package/lib/node/profile-capture.d.ts +8 -2
  293. package/lib/node/profile-capture.d.ts.map +1 -1
  294. package/lib/node/profile-capture.js.map +1 -1
  295. package/lib/node/profiling-run.d.ts +2 -2
  296. package/lib/node/profiling-run.js +1 -1
  297. package/lib/node/profiling-run.js.map +1 -1
  298. package/lib/node/rename-over-open-readers.d.ts +52 -0
  299. package/lib/node/rename-over-open-readers.d.ts.map +1 -0
  300. package/lib/node/rename-over-open-readers.js +66 -0
  301. package/lib/node/rename-over-open-readers.js.map +1 -0
  302. package/lib/node/socket-launcher.d.ts +16 -1
  303. package/lib/node/socket-launcher.d.ts.map +1 -1
  304. package/lib/node/socket-launcher.js +4 -3
  305. package/lib/node/socket-launcher.js.map +1 -1
  306. package/lib/node/stdio-launcher.d.ts +1 -1
  307. package/lib/node/stdio-launcher.d.ts.map +1 -1
  308. package/lib/node/stdio-launcher.js +1 -1
  309. package/lib/node/stdio-launcher.js.map +1 -1
  310. package/lib/testing/fake-document.d.ts +16 -4
  311. package/lib/testing/fake-document.d.ts.map +1 -1
  312. package/lib/testing/fake-document.js +18 -3
  313. package/lib/testing/fake-document.js.map +1 -1
  314. package/lib/testing/make-noop-shared-services.d.ts +14 -0
  315. package/lib/testing/make-noop-shared-services.d.ts.map +1 -1
  316. package/lib/testing/make-noop-shared-services.js +28 -2
  317. package/lib/testing/make-noop-shared-services.js.map +1 -1
  318. package/lib/testing/make-test-services.d.ts +62 -14
  319. package/lib/testing/make-test-services.d.ts.map +1 -1
  320. package/lib/testing/make-test-services.js +27 -6
  321. package/lib/testing/make-test-services.js.map +1 -1
  322. package/lib/testing/node/index.d.ts +1 -0
  323. package/lib/testing/node/index.d.ts.map +1 -1
  324. package/lib/testing/node/index.js +1 -0
  325. package/lib/testing/node/index.js.map +1 -1
  326. package/lib/testing/node/lsp-harness.d.ts.map +1 -1
  327. package/lib/testing/node/lsp-harness.js +2 -0
  328. package/lib/testing/node/lsp-harness.js.map +1 -1
  329. package/lib/testing/node/lsp-server-connection.d.ts +11 -3
  330. package/lib/testing/node/lsp-server-connection.d.ts.map +1 -1
  331. package/lib/testing/node/lsp-server-connection.js +42 -9
  332. package/lib/testing/node/lsp-server-connection.js.map +1 -1
  333. package/lib/testing/node/scratch-workspace.d.ts.map +1 -1
  334. package/lib/testing/node/scratch-workspace.js +10 -5
  335. package/lib/testing/node/scratch-workspace.js.map +1 -1
  336. package/lib/testing/node/spawned-server.d.ts +14 -0
  337. package/lib/testing/node/spawned-server.d.ts.map +1 -1
  338. package/lib/testing/node/spawned-server.js +33 -1
  339. package/lib/testing/node/spawned-server.js.map +1 -1
  340. package/lib/testing/node/unhandled-rejections.d.ts +20 -0
  341. package/lib/testing/node/unhandled-rejections.d.ts.map +1 -0
  342. package/lib/testing/node/unhandled-rejections.js +32 -0
  343. package/lib/testing/node/unhandled-rejections.js.map +1 -0
  344. package/lib/testing/playwright/flaky-network-proxy.d.ts +76 -0
  345. package/lib/testing/playwright/flaky-network-proxy.d.ts.map +1 -0
  346. package/lib/testing/playwright/flaky-network-proxy.js +164 -0
  347. package/lib/testing/playwright/flaky-network-proxy.js.map +1 -0
  348. package/lib/testing/playwright/index.d.ts +1 -0
  349. package/lib/testing/playwright/index.d.ts.map +1 -1
  350. package/lib/testing/playwright/index.js +1 -0
  351. package/lib/testing/playwright/index.js.map +1 -1
  352. package/lib/testing/playwright/server-log-capture.d.ts +14 -4
  353. package/lib/testing/playwright/server-log-capture.d.ts.map +1 -1
  354. package/lib/testing/playwright/server-log-capture.js +21 -4
  355. package/lib/testing/playwright/server-log-capture.js.map +1 -1
  356. package/lib/testing/playwright/server-log-rename-reporter.d.ts.map +1 -1
  357. package/lib/testing/playwright/server-log-rename-reporter.js +10 -1
  358. package/lib/testing/playwright/server-log-rename-reporter.js.map +1 -1
  359. package/lib/testing/run-update-pipeline.d.ts +4 -4
  360. package/lib/testing/run-update-pipeline.d.ts.map +1 -1
  361. package/lib/testing/stub-ast-document-manager.d.ts +10 -9
  362. package/lib/testing/stub-ast-document-manager.d.ts.map +1 -1
  363. package/lib/testing/stub-ast-document-manager.js +17 -9
  364. package/lib/testing/stub-ast-document-manager.js.map +1 -1
  365. package/lib/testing/stub-document-builder.d.ts +26 -11
  366. package/lib/testing/stub-document-builder.d.ts.map +1 -1
  367. package/lib/testing/stub-document-builder.js +57 -4
  368. package/lib/testing/stub-document-builder.js.map +1 -1
  369. package/lib/testing/stub-hydranium-text-documents.d.ts +22 -7
  370. package/lib/testing/stub-hydranium-text-documents.d.ts.map +1 -1
  371. package/lib/testing/stub-hydranium-text-documents.js +92 -15
  372. package/lib/testing/stub-hydranium-text-documents.js.map +1 -1
  373. package/lib/testing/stub-langium-documents.d.ts +3 -2
  374. package/lib/testing/stub-langium-documents.d.ts.map +1 -1
  375. package/lib/testing/stub-langium-documents.js.map +1 -1
  376. package/lib/testing/stub-model-service.d.ts +7 -6
  377. package/lib/testing/stub-model-service.d.ts.map +1 -1
  378. package/lib/testing/stub-model-service.js +4 -4
  379. package/lib/testing/stub-model-service.js.map +1 -1
  380. package/lib/util/connection-liveness.d.ts +7 -6
  381. package/lib/util/connection-liveness.d.ts.map +1 -1
  382. package/lib/util/connection-liveness.js +35 -14
  383. package/lib/util/connection-liveness.js.map +1 -1
  384. package/package.json +17 -8
  385. package/src/documents/ast-document-manager.ts +345 -62
  386. package/src/documents/client-ids.ts +5 -22
  387. package/src/documents/client-session-errors.ts +24 -0
  388. package/src/documents/client-session-registry.ts +240 -0
  389. package/src/documents/file-system-task-queue.ts +68 -0
  390. package/src/documents/hydranium-text-documents.ts +856 -154
  391. package/src/documents/index.ts +3 -0
  392. package/src/documents/language-client-text-shadow.ts +59 -11
  393. package/src/documents/self-save-registry.ts +52 -13
  394. package/src/index.ts +6 -0
  395. package/src/langium/ast-extension/ast-node-builder.ts +66 -22
  396. package/src/langium/bootstrap.ts +16 -9
  397. package/src/langium/config/configuration-provider.ts +119 -0
  398. package/src/langium/config/index.ts +1 -0
  399. package/src/langium/diagnostics/lsp-logger.ts +41 -6
  400. package/src/langium/document-builder/build-pipeline-integration.ts +50 -5
  401. package/src/langium/document-builder/build-session.ts +87 -0
  402. package/src/langium/document-builder/document-builder.ts +698 -39
  403. package/src/langium/document-builder/index.ts +1 -0
  404. package/src/langium/integration-services.ts +67 -6
  405. package/src/langium/integrity/integrity-rule.ts +13 -5
  406. package/src/langium/integrity/integrity-service.ts +202 -17
  407. package/src/langium/keys/containment.ts +87 -0
  408. package/src/langium/keys/index.ts +1 -0
  409. package/src/langium/keys/name-based-key-provider.ts +4 -0
  410. package/src/langium/language-module.ts +42 -4
  411. package/src/langium/model-service/client-session.ts +606 -0
  412. package/src/langium/model-service/index.ts +1 -0
  413. package/src/langium/model-service/model-service.ts +393 -349
  414. package/src/langium/module.ts +137 -30
  415. package/src/langium/naming/name-provider.ts +27 -5
  416. package/src/langium/naming/name-separator-validation.ts +34 -5
  417. package/src/langium/residency/cst-residency-service.ts +36 -14
  418. package/src/langium/scope/hydranium-scope-provider.ts +60 -22
  419. package/src/langium/serialization/abstract-serializer.ts +13 -32
  420. package/src/langium/service-registry.ts +19 -2
  421. package/src/langium/shared-services.ts +19 -2
  422. package/src/langium/transfer/transfer-encoder.ts +85 -24
  423. package/src/langium/trivia/comment-preserver.ts +1100 -0
  424. package/src/langium/trivia/document-ending-preserver.ts +55 -0
  425. package/src/langium/trivia/index.ts +14 -0
  426. package/src/langium/trivia/trivia-contribution.ts +39 -0
  427. package/src/langium/trivia/trivia-preserver.ts +54 -0
  428. package/src/langium/trivia/trivia-service.ts +99 -0
  429. package/src/langium/update-rewrite/update-rewrite.ts +1 -1
  430. package/src/langium/validation/document-validator.ts +537 -11
  431. package/src/langium/workspace/document-uri-policy.ts +3 -4
  432. package/src/langium/workspace/file-system-provider.ts +48 -8
  433. package/src/langium/workspace/hydranium-langium-document-factory.ts +34 -0
  434. package/src/langium/workspace/hydranium-workspace-lock.ts +21 -0
  435. package/src/langium/workspace/hydranium-workspace-manager.ts +30 -1
  436. package/src/langium/workspace/in-memory-file-system-provider.ts +62 -10
  437. package/src/langium/workspace/initialize-workspace.ts +43 -5
  438. package/src/langium/workspace/langium-documents.ts +106 -33
  439. package/src/langium/workspace/virtual-document.ts +21 -3
  440. package/src/langium/workspace/write-lock-scope.ts +4 -3
  441. package/src/locale/index.ts +10 -0
  442. package/src/locale/server-locale.ts +86 -0
  443. package/src/lsp/completion/hydranium-completion-provider.ts +82 -2
  444. package/src/lsp/connection-features.ts +67 -0
  445. package/src/lsp/diagnostics-connection.ts +56 -0
  446. package/src/lsp/hydranium-document-update-handler.ts +182 -106
  447. package/src/lsp/index.ts +3 -1
  448. package/src/lsp/lsp-latency.ts +62 -0
  449. package/src/lsp/semantic-token-provider.ts +74 -7
  450. package/src/lsp/shared-module.ts +4 -8
  451. package/src/lsp/start-language-server.ts +13 -7
  452. package/src/messages/carriers.ts +78 -0
  453. package/src/messages/index.ts +36 -0
  454. package/src/messages/renderer.ts +212 -0
  455. package/src/node/heap-ceiling.ts +108 -0
  456. package/src/node/index.ts +1 -0
  457. package/src/node/latency-from-env.ts +1 -1
  458. package/src/node/measure-memory.ts +1 -0
  459. package/src/node/node-file-system-provider.ts +154 -47
  460. package/src/node/profile-capture.ts +8 -2
  461. package/src/node/profiling-run.ts +3 -3
  462. package/src/node/rename-over-open-readers.ts +78 -0
  463. package/src/node/socket-launcher.ts +19 -4
  464. package/src/node/stdio-launcher.ts +1 -2
  465. package/src/testing/fake-document.ts +24 -5
  466. package/src/testing/make-noop-shared-services.ts +50 -2
  467. package/src/testing/make-test-services.ts +103 -27
  468. package/src/testing/node/index.ts +1 -0
  469. package/src/testing/node/lsp-harness.ts +2 -0
  470. package/src/testing/node/lsp-server-connection.ts +61 -11
  471. package/src/testing/node/scratch-workspace.ts +10 -5
  472. package/src/testing/node/spawned-server.ts +35 -1
  473. package/src/testing/node/unhandled-rejections.ts +31 -0
  474. package/src/testing/playwright/flaky-network-proxy.ts +240 -0
  475. package/src/testing/playwright/index.ts +1 -0
  476. package/src/testing/playwright/server-log-capture.ts +24 -5
  477. package/src/testing/playwright/server-log-rename-reporter.ts +12 -1
  478. package/src/testing/run-update-pipeline.ts +4 -4
  479. package/src/testing/stub-ast-document-manager.ts +28 -21
  480. package/src/testing/stub-document-builder.ts +83 -17
  481. package/src/testing/stub-hydranium-text-documents.ts +127 -22
  482. package/src/testing/stub-langium-documents.ts +3 -2
  483. package/src/testing/stub-model-service.ts +9 -8
  484. package/src/util/connection-liveness.ts +37 -14
  485. package/lib/lsp/instrument-connection.d.ts +0 -36
  486. package/lib/lsp/instrument-connection.d.ts.map +0 -1
  487. package/lib/lsp/instrument-connection.js +0 -62
  488. package/lib/lsp/instrument-connection.js.map +0 -1
  489. package/src/lsp/instrument-connection.ts +0 -66
@@ -8,15 +8,17 @@
8
8
  ********************************************************************************/
9
9
 
10
10
  import {
11
+ type CanonicalUri,
11
12
  type CloseModelArgs,
12
13
  type Tracer,
13
14
  type TransferSavedEvent,
14
15
  type TransferUpdatedEvent,
15
- type OpenModelArgs
16
+ type OpenModelArgs,
17
+ asSnapshotVersion,
18
+ type SnapshotVersion
16
19
  } from '@hydranium/protocol';
17
20
  import {
18
21
  type AstNode,
19
- type DocumentBuilder,
20
22
  DocumentState,
21
23
  type FileSystemProvider,
22
24
  type LangiumDocument,
@@ -24,6 +26,7 @@ import {
24
26
  UriUtils
25
27
  } from '@hydranium/langium';
26
28
  import { type LogNameOptions } from '../langium/diagnostics/logger.js';
29
+ import { type AstDiagnostic } from '../langium/validation/document-validator.js';
27
30
 
28
31
  /**
29
32
  * File-system provider extension that supports writes. Langium's standard
@@ -79,7 +82,7 @@ import { TextDocumentIdentifier, type TextDocumentItem } from 'vscode-languagese
79
82
  import { type TextDocument } from 'vscode-languageserver-textdocument';
80
83
  import { URI } from '@hydranium/langium';
81
84
  import { type ServerSharedServices } from '../langium/module.js';
82
- import { labelPhaseListener } from '../langium/document-builder/index.js';
85
+ import { type HydraniumDocumentBuilder, labelPhaseListener } from '../langium/document-builder/index.js';
83
86
  import { UNKNOWN_CLIENT_ID } from './client-ids.js';
84
87
  import { type HydraniumTextDocuments } from './hydranium-text-documents.js';
85
88
  import { type SelfSaveRegistry } from './self-save-registry.js';
@@ -102,19 +105,18 @@ export interface UpdateInfo {
102
105
  * twins; the type-system distinction is the generic constraint, and the
103
106
  * conversion seam between them is the framework `TransferEncoder`.
104
107
  */
105
- export interface AstDocument<TAst extends AstNode, TDiagnostic> {
108
+ export interface AstDocument<TAst extends AstNode, TDiagnostic extends AstDiagnostic = AstDiagnostic> {
106
109
  root: TAst;
107
110
  diagnostics: TDiagnostic[];
108
111
  uri: string;
109
112
  /**
110
113
  * Text-document version this snapshot was taken at — read from
111
114
  * `LangiumDocument.textDocument.version`. Symmetric with
112
- * `TransferDocument.version` on the wire side: in-process callers
113
- * that hold an `AstDocument` and mutate it pass this value back as
114
- * `TransferUpdateArgs.baseVersion` / `TransferSaveArgs.baseVersion` to opt
115
- * into the conflict gate.
115
+ * `TransferDocument.version` on the wire side: an in-process caller that
116
+ * holds an `AstDocument` and mutates it sends this straight back as the
117
+ * `basedOn` of its session's write, and the conflict gate arms on it.
116
118
  */
117
- version: number;
119
+ version: SnapshotVersion;
118
120
  }
119
121
 
120
122
  export namespace AstDocument {
@@ -124,13 +126,13 @@ export namespace AstDocument {
124
126
  * don't need to repeat the empty array at every call site. Mirrors
125
127
  * `TransferDocument.create` on the wire side.
126
128
  */
127
- export function create<TAst extends AstNode, TDiagnostic = unknown>(
129
+ export function create<TAst extends AstNode, TDiagnostic extends AstDiagnostic = AstDiagnostic>(
128
130
  uri: string,
129
131
  version: number,
130
132
  root: TAst,
131
133
  diagnostics: TDiagnostic[] = []
132
134
  ): AstDocument<TAst, TDiagnostic> {
133
- return { uri, version, root, diagnostics };
135
+ return { uri, version: asSnapshotVersion(version), root, diagnostics };
134
136
  }
135
137
 
136
138
  /**
@@ -146,25 +148,107 @@ export namespace AstDocument {
146
148
  * carry that identity, not the subscriber's (possibly non-canonical) URI:
147
149
  * a subscriber already knows the URI it subscribed with, so the useful
148
150
  * thing to surface is the canonical one every other layer keys by.
151
+ *
152
+ * Narrowing `TDiagnostic` below the constraint asserts that the validators
153
+ * in play produce that shape, and nothing here checks it: the build fills the
154
+ * array, so a document carrying a diagnostic from elsewhere — a lexer error,
155
+ * another validator — satisfies the declared type and not the narrowed one.
149
156
  */
150
- export function from<TAst extends AstNode, TDiagnostic = unknown>(document: LangiumDocument): AstDocument<TAst, TDiagnostic> {
157
+ export function from<TAst extends AstNode, TDiagnostic extends AstDiagnostic = AstDiagnostic>(
158
+ document: LangiumDocument
159
+ ): AstDocument<TAst, TDiagnostic> {
151
160
  return create<TAst, TDiagnostic>(
152
161
  document.textDocument.uri,
153
162
  document.textDocument.version,
154
163
  document.parseResult.value as TAst,
155
- (document.diagnostics ?? []) as unknown as TDiagnostic[]
164
+ (document.diagnostics ?? []) as TDiagnostic[]
156
165
  );
157
166
  }
158
167
  }
159
168
 
160
169
  /** Update event delivered by {@link AstDocumentManager.onUpdate} — typed alias over the generic event wrapper. */
161
- export type AstDocumentUpdatedEvent<TAst extends AstNode, TDiagnostic> = TransferUpdatedEvent<AstDocument<TAst, TDiagnostic>>;
170
+ export type AstDocumentUpdatedEvent<TAst extends AstNode, TDiagnostic extends AstDiagnostic = AstDiagnostic> = TransferUpdatedEvent<
171
+ AstDocument<TAst, TDiagnostic>
172
+ >;
162
173
 
163
174
  /** Save event delivered by {@link AstDocumentManager.onSave} — typed alias over the generic event wrapper. */
164
- export type AstDocumentSavedEvent<TAst extends AstNode, TDiagnostic> = TransferSavedEvent<AstDocument<TAst, TDiagnostic>>;
175
+ export type AstDocumentSavedEvent<TAst extends AstNode, TDiagnostic extends AstDiagnostic = AstDiagnostic> = TransferSavedEvent<
176
+ AstDocument<TAst, TDiagnostic>
177
+ >;
178
+
179
+ /**
180
+ * Why an update event fired and whose write it echoes, as
181
+ * {@link AstDocumentManager.attributeUpdate} answers for a document. The
182
+ * fields mean what they mean on `TransferUpdatedEvent`.
183
+ */
184
+ export interface UpdateAttribution {
185
+ reason: 'changed' | 'rebuilt';
186
+ sourceClientId: string;
187
+ causedBy: string;
188
+ }
165
189
 
166
190
  /** Construction options for {@link AstDocumentManager}. */
167
- export type AstDocumentManagerOptions = LogNameOptions;
191
+ export interface AstDocumentManagerOptions extends LogNameOptions {
192
+ /**
193
+ * Skip a queued save of a URI when a newer save of it is queued behind. The
194
+ * newer save took its text later, so disk ends the same with fewer writes.
195
+ * A skipped save announces nothing and takes the newer save's outcome: it
196
+ * resolves when that one lands and rejects with its error when it fails,
197
+ * since disk then holds neither text. Defaults to `false`: every save
198
+ * writes. The framework binds the manager without options, so turning this
199
+ * on means rebinding `AstDocumentManager` with them.
200
+ */
201
+ readonly coalesceSaves?: boolean;
202
+ }
203
+
204
+ /**
205
+ * The document slot the model server and the integrity service talk to:
206
+ * open/close/update/save plus the AST-typed event streams, with the LSP
207
+ * plumbing hidden.
208
+ *
209
+ * Generic over `<TAst extends AstNode, TDiagnostic>` so each consumer projects
210
+ * its own AST root type and diagnostic shape into the emitted
211
+ * {@link AstDocument} envelopes.
212
+ *
213
+ * **URI contract.** Every URI-accepting member takes a URI in *any* spelling
214
+ * (canonical, symlinked, `..`/case-divergent) and canonicalizes internally
215
+ * through the {@link DocumentUriPolicy}, so a caller never has to canonicalize
216
+ * first and an implementation may not require it.
217
+ */
218
+ export interface AstDocumentManager<TAst extends AstNode, TDiagnostic extends AstDiagnostic = AstDiagnostic> {
219
+ open(args: OpenModelArgs): Promise<Disposable>;
220
+ close(args: CloseModelArgs): Promise<void>;
221
+ isOpen(uri: string): boolean;
222
+
223
+ /** Apply `text` as `clientId`'s edit. Resolves to the resulting text-document version. */
224
+ update(uri: string, text: string, clientId: string): Promise<number>;
225
+ /**
226
+ * Write the document's current text to disk through `FileSystemTaskQueue`.
227
+ * The text is taken when this is called, so saves of one file land in the
228
+ * order they were called.
229
+ */
230
+ save(uri: string, clientId: string): Promise<void>;
231
+
232
+ onUpdate(uri: string, listener: (event: AstDocumentUpdatedEvent<TAst, TDiagnostic>) => void): Disposable;
233
+ onSave(uri: string, listener: (event: AstDocumentSavedEvent<TAst, TDiagnostic>) => void | Promise<void>): Disposable;
234
+ onClientClosed(uri: string, clientId: string, listener: () => void): Disposable;
235
+
236
+ getDocument(uri: string): LangiumDocument | undefined;
237
+
238
+ /** Client id that authored the document's current version, or `undefined` for a framework-internal build. */
239
+ getAuthor(document: LangiumDocument): string | undefined;
240
+
241
+ /**
242
+ * The reason, source and cause of an update event for `document` emitted
243
+ * now, from a phase listener of the build that carries it. Every head takes
244
+ * its update events' attribution from here, so they name the same client
245
+ * for one build. For a document a client opened, while rebuilds validate,
246
+ * the answer changes once its `Validated` listeners have all run: a later
247
+ * event at that version is `'rebuilt'`. `TransferDocumentUpdateReason`
248
+ * names the cases that fall back to the last update.
249
+ */
250
+ attributeUpdate(document: LangiumDocument): UpdateAttribution;
251
+ }
168
252
 
169
253
  /**
170
254
  * Higher-level facade over {@link HydraniumTextDocuments} that speaks the
@@ -186,13 +270,53 @@ export type AstDocumentManagerOptions = LogNameOptions;
186
270
  * resolution (the egress `applyEditToLanguageClient`) stay on
187
271
  * {@link HydraniumTextDocuments}, their owner.
188
272
  */
189
- export class AstDocumentManager<TAst extends AstNode = AstNode, TDiagnostic = unknown> {
273
+ export class DefaultAstDocumentManager<
274
+ TAst extends AstNode,
275
+ TDiagnostic extends AstDiagnostic = AstDiagnostic
276
+ > implements AstDocumentManager<TAst, TDiagnostic> {
277
+ /** The last `DocumentBuilder.update`'s URIs; the reason for a document the store does not track. */
190
278
  protected lastUpdate?: UpdateInfo;
279
+ /**
280
+ * Canonical URIs a client has opened since the file was last deleted,
281
+ * whose versions the store keeps. One no client has opened keeps Langium's
282
+ * version `0` through every change of its content, so its version says
283
+ * nothing. Kept past the last close, since the store continues the version
284
+ * sequence on the next open; a delivery can land after that close.
285
+ */
286
+ protected readonly trackedUris = new Set<CanonicalUri>();
287
+ /**
288
+ * Per tracked URI, the version whose `Validated` listeners last all ran,
289
+ * which is the version an update event was delivered for. Dropped on
290
+ * deletion, so a file created again with the same text is changed again.
291
+ */
292
+ protected readonly deliveredVersions = new Map<CanonicalUri, number>();
293
+ /**
294
+ * Whose writes the build under way carries, set when it starts: the author
295
+ * of every open document's undelivered version, and the unknown-client id
296
+ * for a change no write explains, such as a deletion, a document no client
297
+ * has open, or a write that changed nothing. An undelivered version is one
298
+ * whose build has not validated it yet, so a write whose build was
299
+ * cancelled stays a cause of the build that takes over.
300
+ *
301
+ * Errs toward more than one cause, and so toward the unknown client, which
302
+ * a reader treats as foreign: an answer it repeats, never one it misses.
303
+ * A change no write explains counts for its own build only, so a build
304
+ * that takes over from a cancelled one loses it. One field for every build:
305
+ * a build started outside the workspace lock, such as a re-queue of an
306
+ * orphaned document, reads the causes of whichever build started last.
307
+ * An open document no build delivers, one a `shouldValidate` override
308
+ * skips, stays undelivered, so it is a cause of every build while open.
309
+ */
310
+ protected readonly buildCauses = new Set<string>();
311
+
312
+ /** Per canonical URI, the newest save queued for it, which {@link coalesceSaves} lets older queued saves defer to. */
313
+ protected readonly newestSaves = new Map<CanonicalUri, Promise<void>>();
314
+ protected readonly coalesceSaves: boolean;
191
315
 
192
316
  protected readonly textDocuments: HydraniumTextDocuments<TextDocument>;
193
317
  protected readonly fileSystemProvider: WritableFileSystemProvider;
194
318
  protected readonly langiumDocs: LangiumDocuments;
195
- protected readonly documentBuilder: DocumentBuilder;
319
+ protected readonly documentBuilder: HydraniumDocumentBuilder;
196
320
  protected readonly uriPolicy: DocumentUriPolicy;
197
321
  protected readonly tracer: Tracer;
198
322
 
@@ -206,13 +330,41 @@ export class AstDocumentManager<TAst extends AstNode = AstNode, TDiagnostic = un
206
330
  this.documentBuilder = services.workspace.DocumentBuilder;
207
331
  this.uriPolicy = services.workspace.DocumentUriPolicy;
208
332
  this.tracer = services.Tracer.for(options.logName ?? 'AstDocumentManager').trace('instantiated');
209
- this.textDocuments.onDidOpen(event =>
210
- this.open({ clientId: event.clientId, uri: event.document.uri, languageId: event.document.languageId })
211
- );
333
+ this.coalesceSaves = options.coalesceSaves ?? false;
334
+ this.textDocuments.onDidOpen(event => {
335
+ this.trackedUris.add(this.uriPolicy.canonicalUri(event.document.uri));
336
+ return this.open({ clientId: event.clientId, uri: event.document.uri, languageId: event.document.languageId });
337
+ });
212
338
  this.textDocuments.onDidClose(event => this.close({ clientId: event.clientId, uri: event.document.uri }));
213
339
  this.documentBuilder.onUpdate((changed, deleted) => {
214
340
  this.lastUpdate = { changed, deleted };
341
+ this.buildCauses.clear();
342
+ for (const textDocument of this.textDocuments.all()) {
343
+ if (!this.isDelivered(textDocument.uri, textDocument.version)) {
344
+ this.buildCauses.add(this.textDocuments.getAuthor(textDocument.uri, textDocument.version) ?? UNKNOWN_CLIENT_ID);
345
+ }
346
+ }
347
+ const unexplained = changed.some(uri => {
348
+ const textDocument = this.textDocuments.get(uri.toString());
349
+ return textDocument === undefined || this.isDelivered(textDocument.uri, textDocument.version);
350
+ });
351
+ if (unexplained || deleted.length > 0) {
352
+ this.buildCauses.add(UNKNOWN_CLIENT_ID);
353
+ }
354
+ // The only place a deletion is observable: `DocumentBuilder.update`
355
+ // removes a deleted document before it builds anything, so no phase
356
+ // listener ever sees it.
357
+ for (const uri of deleted) {
358
+ const key = this.uriPolicy.canonicalUri(uri.toString());
359
+ this.trackedUris.delete(key);
360
+ this.deliveredVersions.delete(key);
361
+ this.textDocuments.notifyDocumentDeleted(uri.toString());
362
+ }
215
363
  });
364
+ // Once every listener ran, not in a listener of its own: listeners after
365
+ // it could still be skipped by a cancel, and the next build would then
366
+ // report as rebuilt a version they never delivered.
367
+ this.documentBuilder.onDocumentPhaseDelivered(DocumentState.Validated, (document, version) => this.markDelivered(document, version));
216
368
  // Content transitions for CLOSED documents bypass the text store: the
217
369
  // last-close revert and watched-file changes rebuild from disk into a
218
370
  // factory-fresh text document carrying the factory's own version. At the
@@ -304,20 +456,16 @@ export class AstDocumentManager<TAst extends AstNode = AstNode, TDiagnostic = un
304
456
  if (this.uriPolicy.canonicalUri(document.uri) !== target) {
305
457
  return;
306
458
  }
307
- // Presentation boundary: the event field is a concrete `string`, so an
308
- // unauthored (framework-rebuilt) document surfaces the readable default
309
- // rather than `undefined` to subscribers and logs.
310
- const sourceClientId = this.getAuthor(document) ?? UNKNOWN_CLIENT_ID;
459
+ // No `'deleted'` reason: `DocumentBuilder.update` drops a deleted
460
+ // document from `LangiumDocuments` before deriving the rebuild set
461
+ // from it, so a deleted URI is never built and never reaches this
462
+ // phase listener. A subscriber needing deletions has to be told on
463
+ // a channel that does not require a built document.
311
464
  const event: TransferUpdatedEvent<AstDocument<TAst, TDiagnostic>> = {
312
465
  document: this.toAstDocument(document),
313
- sourceClientId,
314
- reason: this.lastUpdate?.changed.find(changed => UriUtils.equals(changed, document.uri))
315
- ? 'changed'
316
- : this.lastUpdate?.deleted.find(deleted => UriUtils.equals(deleted, document.uri))
317
- ? 'deleted'
318
- : 'rebuilt'
466
+ ...this.attributeUpdate(document)
319
467
  };
320
- this.tracer.with(uri).trace(`emitUpdate start: source=${sourceClientId}, reason=${event.reason}`);
468
+ this.tracer.with(uri).trace(`emitUpdate start: source=${event.sourceClientId}, reason=${event.reason}, cause=${event.causedBy}`);
321
469
  listener(event);
322
470
  this.tracer.with(uri).trace('emitUpdate listener returned');
323
471
  };
@@ -326,23 +474,86 @@ export class AstDocumentManager<TAst extends AstNode = AstNode, TDiagnostic = un
326
474
 
327
475
  /**
328
476
  * The client that authored `document`'s current version, or `undefined` when
329
- * no client wrote it — a framework-internal rebuild (workspace startup, a
330
- * cascade relink, a `didClose`-reload) or a genuine author gap. Honest about
331
- * absence so routing consumers (`ModelService.isNonLanguageClientEdit`)
332
- * test `undefined` directly rather than against a sentinel; the presentation
333
- * default ({@link UNKNOWN_CLIENT_ID}) is applied at the boundary that needs a
334
- * concrete value (the `onUpdate` event's `sourceClientId`, the data-server's
335
- * `resolveSourceClientId`).
477
+ * no client wrote it: a document no client has open. A rebuild that keeps
478
+ * the version keeps its author, so this does not say who caused a build;
479
+ * {@link attributeUpdate} does.
480
+ * Honest about absence so a routing consumer tests `undefined` directly
481
+ * rather than against a sentinel; the presentation default
482
+ * ({@link UNKNOWN_CLIENT_ID}) is applied where an event needs a value.
336
483
  */
337
- getAuthor(document: LangiumDocument<AstNode>): string | undefined {
484
+ getAuthor(document: LangiumDocument): string | undefined {
338
485
  return this.textDocuments.getAuthor(document.textDocument.uri, document.textDocument.version);
339
486
  }
340
487
 
488
+ /**
489
+ * `'changed'` for the first delivery of the document's version, crediting
490
+ * its author; `'rebuilt'` for a later one, crediting nobody, since it echoes
491
+ * no write, and naming the build's single cause if it has one.
492
+ *
493
+ * A document the rule does not apply to (see {@link isTracked}) falls back
494
+ * to the last update: it is `'changed'` when its URI was passed to
495
+ * `DocumentBuilder.update`. An open document that a validating build skips,
496
+ * through a `shouldValidate` override, is never delivered, so each of its
497
+ * events is its author's change.
498
+ */
499
+ attributeUpdate(document: LangiumDocument): UpdateAttribution {
500
+ const key = this.uriPolicy.canonicalUri(document.uri.toString());
501
+ const changed = this.isTracked(key)
502
+ ? this.deliveredVersions.get(key) !== document.textDocument.version
503
+ : this.lastUpdate?.changed.some(uri => UriUtils.equals(uri, document.uri)) === true;
504
+ if (changed) {
505
+ // Presentation boundary: the event field is a concrete `string`, so an
506
+ // unauthored document surfaces the readable default rather than
507
+ // `undefined` to subscribers and logs.
508
+ const author = this.getAuthor(document) ?? UNKNOWN_CLIENT_ID;
509
+ return { reason: 'changed', sourceClientId: author, causedBy: author };
510
+ }
511
+ const [cause] = this.buildCauses;
512
+ return { reason: 'rebuilt', sourceClientId: UNKNOWN_CLIENT_ID, causedBy: this.buildCauses.size === 1 ? cause : UNKNOWN_CLIENT_ID };
513
+ }
514
+
515
+ /**
516
+ * Whether the version rule applies to `uri`: the store keeps its version,
517
+ * and rebuilds validate. With `updateBuildOptions.validation` off no version
518
+ * is ever delivered, and the rule would report every event of an open
519
+ * document as its author's change.
520
+ */
521
+ protected isTracked(uri: CanonicalUri): boolean {
522
+ return this.keepsVersion(uri) && Boolean(this.documentBuilder.updateBuildOptions.validation);
523
+ }
524
+
525
+ /**
526
+ * Whether the store keeps `uri`'s version: a client opened it since its
527
+ * file was last deleted, or has it open still, as an editor keeps a
528
+ * deleted file's buffer.
529
+ */
530
+ protected keepsVersion(uri: CanonicalUri): boolean {
531
+ return this.trackedUris.has(uri) || this.textDocuments.isOpen(uri);
532
+ }
533
+
534
+ /**
535
+ * Record that `version` of `document` was delivered, when the store tracks
536
+ * it. The version the listeners saw, not the document's current one: a
537
+ * write during them has moved that on, and marking it would report the
538
+ * write's own build as rebuilt.
539
+ */
540
+ protected markDelivered(document: LangiumDocument, version: number): void {
541
+ const key = this.uriPolicy.canonicalUri(document.uri.toString());
542
+ if (this.keepsVersion(key)) {
543
+ this.deliveredVersions.set(key, version);
544
+ }
545
+ }
546
+
547
+ /** Whether `version` of `uri` was delivered. */
548
+ protected isDelivered(uri: string, version: number): boolean {
549
+ return this.deliveredVersions.get(this.uriPolicy.canonicalUri(uri)) === version;
550
+ }
551
+
341
552
  async open(args: OpenModelArgs): Promise<Disposable> {
342
553
  // `open()` is the RPC-level "ensure this document is loaded" call. It is issued
343
- // by a view attaching, but also internally by `update()` / `save()` and by every
344
- // additional client attaching to the same URI, including the several sub-editors
345
- // a composite view opens. When the document is already loaded there is nothing to
554
+ // by a session opening the document, including by every additional client
555
+ // attaching to the same URI and the several sub-editors a composite view
556
+ // opens. When the document is already loaded there is nothing to
346
557
  // do here but hand back the close-disposable — callers that want the current
347
558
  // state read it from this method's surrounding RPC response, not from a re-build.
348
559
  //
@@ -355,10 +566,28 @@ export class AstDocumentManager<TAst extends AstNode = AstNode, TDiagnostic = un
355
566
  // transiently breaks cross-document linking mid-rebuild (a dependent briefly
356
567
  // seeing an `extends`/`type` reference unresolved). And because the constructor
357
568
  // wires `onDidOpen -> this.open`, it would fire a second, identical rebuild on
358
- // every first open. So an already-open `open()` is a no-op.
569
+ // every first open.
570
+ //
571
+ // So an already-open `open()` records the attaching client's open and nothing
572
+ // else. The open is per `(uri, clientId)` and the last-close revert counts down
573
+ // to it, so skipping it lets the first client's close tear down a document
574
+ // another client is still reading. A document waiting out the revert grace
575
+ // is still loaded: a client lost from it within its own grace attaches and
576
+ // keeps its text, and for any other client the attach releases it, so the
577
+ // open reads the file.
578
+ if (this.isOpen(args.uri)) {
579
+ this.textDocuments.attachClient(args.uri, args.clientId);
580
+ }
359
581
  if (!this.isOpen(args.uri)) {
360
582
  const textDocument = await this.createDocumentFromTextOrFileSystem(args.uri, args.languageId, args.version, args.text);
583
+ const creates = !this.isOpen(args.uri);
361
584
  this.textDocuments.notifyDidOpenTextDocument({ textDocument }, args.clientId);
585
+ if (creates && args.text !== undefined) {
586
+ // Text the caller supplies was not read from the file, so no file
587
+ // is assumed: the document is dirty until its first save, whatever
588
+ // the file holds.
589
+ this.textDocuments.updateDiskBaseline(args.uri, undefined);
590
+ }
362
591
  }
363
592
  return Disposable.create(() => this.close(args));
364
593
  }
@@ -393,31 +622,80 @@ export class AstDocumentManager<TAst extends AstNode = AstNode, TDiagnostic = un
393
622
  * callers don't need to pass it — the manager owns content and version
394
623
  * sequencing.
395
624
  *
625
+ * The text is taken synchronously, before the save waits in the file's task
626
+ * queue; the compare and the write run in the queue. Taken later, a save
627
+ * that waits behind another writes whatever the store holds when its turn
628
+ * comes, edits made after the call included, and announces that text under
629
+ * its own client id.
630
+ *
631
+ * **The write is skipped when the file already holds that text; the
632
+ * notification is not.** A save announces that the content is on disk, which
633
+ * is true either way — so a subscriber clearing a dirty marker or reacting to
634
+ * a persist behaves the same, while an mtime is not moved for byte-identical
635
+ * content that a downstream mtime-keyed build would then read as new work.
636
+ *
396
637
  * Throws if no document is open for `uri`.
397
638
  */
398
639
  async save(uri: string, clientId: string): Promise<void> {
399
- const document = this.textDocuments.get(uri);
640
+ // Canonical throughout: the write and the read that gates it must address
641
+ // the same file the store was keyed by, or a divergent spelling compares
642
+ // one file and writes another.
643
+ const canonical = this.uriPolicy.canonicalUri(uri);
644
+ const document = this.textDocuments.get(canonical);
400
645
  if (!document) {
401
646
  throw new Error(`Document ${uri} hasn't been opened for saving yet`);
402
647
  }
403
648
  const text = document.getText();
404
- await this.tracer.with(uri).time(
405
- `Write file (${text.length} bytes, from ${clientId})`,
406
- // Async write so a slow disk does not block the event loop for the duration of the fs call.
407
- () => this.fileSystemProvider.writeFile(UriUtils.toUri(uri), text),
408
- 'debug'
409
- );
649
+ let supersededBy: Promise<void> | undefined;
650
+ const saved: Promise<void> = this.services.workspace.FileSystemTaskQueue.enqueue(canonical, async () => {
651
+ const newest = this.newestSaves.get(canonical);
652
+ if (this.coalesceSaves && newest !== saved) {
653
+ supersededBy = newest;
654
+ return;
655
+ }
656
+ await this.writeSave(canonical, text, clientId);
657
+ })
658
+ .then(() => supersededBy)
659
+ .finally(() => {
660
+ if (this.newestSaves.get(canonical) === saved) {
661
+ this.newestSaves.delete(canonical);
662
+ }
663
+ });
664
+ this.newestSaves.set(canonical, saved);
665
+ return saved;
666
+ }
667
+
668
+ /** Write `text` to `uri` unless the file already holds it, then announce the save. Runs inside the file's task queue. */
669
+ protected async writeSave(uri: CanonicalUri, text: string, clientId: string): Promise<void> {
670
+ if (!(await this.matchesDisk(uri, text))) {
671
+ await this.tracer.with(uri).time(
672
+ `Write file (${text.length} bytes, from ${clientId})`,
673
+ // Async write so a slow disk does not block the event loop for the duration of the fs call.
674
+ () => this.fileSystemProvider.writeFile(UriUtils.toUri(uri), text),
675
+ 'debug'
676
+ );
677
+ }
410
678
  this.textDocuments.notifyDidSaveTextDocument({ textDocument: TextDocumentIdentifier.create(uri), text }, clientId);
411
679
  }
412
680
 
413
- isOpen(uri: string): boolean {
414
- return !!this.textDocuments.get(uri);
681
+ /**
682
+ * Whether the file at `uri` already holds `text`.
683
+ *
684
+ * Compared by CONTENT rather than against a remembered version, so a write
685
+ * from outside this server is still corrected: a version this manager already
686
+ * saved says nothing about what is on disk now. `false` when the file cannot
687
+ * be read at all, which is the answer that writes — a document whose file does
688
+ * not exist yet is exactly the one a first save has to create.
689
+ */
690
+ protected async matchesDisk(uri: string, text: string): Promise<boolean> {
691
+ return this.fileSystemProvider
692
+ .readFile(UriUtils.toUri(uri))
693
+ .then(onDisk => onDisk === text)
694
+ .catch(() => false);
415
695
  }
416
696
 
417
- /** True if the URI was in the directly-changed set of the most recent build (not just affected). */
418
- isDirectChange(uri: string): boolean {
419
- const canonical = this.uriPolicy.canonicalUri(uri);
420
- return this.lastUpdate?.changed.some(changed => this.uriPolicy.canonicalUri(changed) === canonical) ?? false;
697
+ isOpen(uri: string): boolean {
698
+ return !!this.textDocuments.get(uri);
421
699
  }
422
700
 
423
701
  /**
@@ -437,11 +715,16 @@ export class AstDocumentManager<TAst extends AstNode = AstNode, TDiagnostic = un
437
715
  version = 0,
438
716
  text?: string
439
717
  ): Promise<TextDocumentItem> {
440
- return { uri, languageId, version, text: text ?? (await this.readFile(uri)) };
441
- }
442
-
443
- async readFile(uri: string): Promise<string> {
444
- return this.fileSystemProvider.readFile(UriUtils.toUri(uri));
718
+ // Read in the file's task queue: a read beside a queued save of the file
719
+ // returns the text from before it, and the document then opens on that.
720
+ return {
721
+ uri,
722
+ languageId,
723
+ version,
724
+ text:
725
+ text ??
726
+ (await this.services.workspace.FileSystemTaskQueue.enqueue(uri, () => this.fileSystemProvider.readFile(UriUtils.toUri(uri))))
727
+ };
445
728
  }
446
729
 
447
730
  /**
@@ -8,26 +8,9 @@
8
8
  ********************************************************************************/
9
9
 
10
10
  /**
11
- * Well-known client-kind identifiers used across the multi-client framework.
12
- *
13
- * The framework tracks each document as belonging to one or more "clients"
14
- * — typically an LSP-text-client (Monaco, VS Code, etc.), a GLSP diagram
15
- * client, and/or a structured-form client. Each kind carries a stable id so
16
- * the server can route outbound corrections, edits, and notifications to the
17
- * right transport.
18
- */
19
-
20
- /** Identifier for an LSP-text client (Monaco, VS Code, …). */
21
- export const LANGUAGE_CLIENT_ID = 'language-client';
22
-
23
- /** Fallback identifier when a document's author is unknown (e.g. cold workspace-init load). */
24
- export const UNKNOWN_CLIENT_ID = 'unknown';
25
-
26
- /**
27
- * Synthetic author id on the `onDocumentUpdated` broadcast a data-server
28
- * emits after the LAST client closed a document and the framework rebuilt it
29
- * from its disk content (discarding unsaved in-session edits). Not a real
30
- * client: it lets consumers distinguish the revert broadcast from
31
- * client-authored updates.
11
+ * Re-export, because these are WIRE values and the side that has to recognise
12
+ * them is the client. They are defined in `@hydranium/protocol` so a frontend
13
+ * can name them without depending on the server tier; this path stays so
14
+ * server-side code keeps importing them from where it already does.
32
15
  */
33
- export const REVERT_ON_CLOSE_CLIENT_ID = 'revert-on-close';
16
+ export { FRAMEWORK_CLIENT_IDS, LANGUAGE_CLIENT_ID, REVERT_ON_CLOSE_CLIENT_ID, UNKNOWN_CLIENT_ID } from '@hydranium/protocol';
@@ -0,0 +1,24 @@
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
+ /**
11
+ * Re-export, because these errors cross the wire and the side that has to
12
+ * recognise them is the client. They are defined in `@hydranium/protocol` so a
13
+ * frontend can name them without depending on the server tier.
14
+ */
15
+ export {
16
+ DocumentNotOpenError,
17
+ DuplicateClientIdError,
18
+ ReservedClientIdError,
19
+ SessionClosedError,
20
+ isDocumentNotOpenError,
21
+ isDuplicateClientIdError,
22
+ isReservedClientIdError,
23
+ isSessionClosedError
24
+ } from '@hydranium/protocol';