@hydranium/glsp-server 1.0.0-next.29 → 1.0.0-next.299

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 (217) hide show
  1. package/README.md +78 -88
  2. package/lib/browser/start-glsp-server-in-worker.d.ts +43 -42
  3. package/lib/browser/start-glsp-server-in-worker.d.ts.map +1 -1
  4. package/lib/browser/start-glsp-server-in-worker.js +62 -13
  5. package/lib/browser/start-glsp-server-in-worker.js.map +1 -1
  6. package/lib/command/diagram-edit-dropped-error.d.ts +31 -0
  7. package/lib/command/diagram-edit-dropped-error.d.ts.map +1 -0
  8. package/lib/command/diagram-edit-dropped-error.js +48 -0
  9. package/lib/command/diagram-edit-dropped-error.js.map +1 -0
  10. package/lib/command/hydranium-glsp-command-stack.d.ts +33 -0
  11. package/lib/command/hydranium-glsp-command-stack.d.ts.map +1 -0
  12. package/lib/command/hydranium-glsp-command-stack.js +63 -0
  13. package/lib/command/hydranium-glsp-command-stack.js.map +1 -0
  14. package/lib/command/hydranium-glsp-operation-action-handler.d.ts +64 -0
  15. package/lib/command/hydranium-glsp-operation-action-handler.d.ts.map +1 -0
  16. package/lib/command/hydranium-glsp-operation-action-handler.js +142 -0
  17. package/lib/command/hydranium-glsp-operation-action-handler.js.map +1 -0
  18. package/lib/command/hydranium-glsp-operation-command.d.ts +250 -0
  19. package/lib/command/hydranium-glsp-operation-command.d.ts.map +1 -0
  20. package/lib/command/hydranium-glsp-operation-command.js +491 -0
  21. package/lib/command/hydranium-glsp-operation-command.js.map +1 -0
  22. package/lib/command/hydranium-glsp-recording-command.d.ts +26 -87
  23. package/lib/command/hydranium-glsp-recording-command.d.ts.map +1 -1
  24. package/lib/command/hydranium-glsp-recording-command.js +40 -117
  25. package/lib/command/hydranium-glsp-recording-command.js.map +1 -1
  26. package/lib/command/hydranium-glsp-undo-redo-action-handler.d.ts +23 -0
  27. package/lib/command/hydranium-glsp-undo-redo-action-handler.d.ts.map +1 -0
  28. package/lib/command/hydranium-glsp-undo-redo-action-handler.js +41 -0
  29. package/lib/command/hydranium-glsp-undo-redo-action-handler.js.map +1 -0
  30. package/lib/command/index.d.ts +5 -0
  31. package/lib/command/index.d.ts.map +1 -1
  32. package/lib/command/index.js +5 -0
  33. package/lib/command/index.js.map +1 -1
  34. package/lib/computed-bounds/hydranium-glsp-computed-bounds-action-handler.d.ts +1 -1
  35. package/lib/computed-bounds/hydranium-glsp-computed-bounds-action-handler.d.ts.map +1 -1
  36. package/lib/computed-bounds/hydranium-glsp-computed-bounds-action-handler.js +2 -2
  37. package/lib/computed-bounds/hydranium-glsp-computed-bounds-action-handler.js.map +1 -1
  38. package/lib/dispatcher/server-action-dispatcher.d.ts +25 -24
  39. package/lib/dispatcher/server-action-dispatcher.d.ts.map +1 -1
  40. package/lib/dispatcher/server-action-dispatcher.js +38 -26
  41. package/lib/dispatcher/server-action-dispatcher.js.map +1 -1
  42. package/lib/index.d.ts +1 -0
  43. package/lib/index.d.ts.map +1 -1
  44. package/lib/index.js +1 -0
  45. package/lib/index.js.map +1 -1
  46. package/lib/launcher/abstract-hydranium-glsp-diagram-module.d.ts +35 -59
  47. package/lib/launcher/abstract-hydranium-glsp-diagram-module.d.ts.map +1 -1
  48. package/lib/launcher/abstract-hydranium-glsp-diagram-module.js +60 -71
  49. package/lib/launcher/abstract-hydranium-glsp-diagram-module.js.map +1 -1
  50. package/lib/launcher/drop-notifications-to-gone-client.d.ts +23 -0
  51. package/lib/launcher/drop-notifications-to-gone-client.d.ts.map +1 -0
  52. package/lib/launcher/drop-notifications-to-gone-client.js +43 -0
  53. package/lib/launcher/drop-notifications-to-gone-client.js.map +1 -0
  54. package/lib/launcher/glsp-app-module.d.ts +1 -2
  55. package/lib/launcher/glsp-app-module.d.ts.map +1 -1
  56. package/lib/launcher/glsp-app-module.js.map +1 -1
  57. package/lib/launcher/glsp-connection-logger.d.ts +19 -0
  58. package/lib/launcher/glsp-connection-logger.d.ts.map +1 -0
  59. package/lib/launcher/glsp-connection-logger.js +24 -0
  60. package/lib/launcher/glsp-connection-logger.js.map +1 -0
  61. package/lib/launcher/glsp-server-overrides.d.ts +10 -8
  62. package/lib/launcher/glsp-server-overrides.d.ts.map +1 -1
  63. package/lib/launcher/glsp-server-overrides.js +25 -11
  64. package/lib/launcher/glsp-server-overrides.js.map +1 -1
  65. package/lib/launcher/hydranium-glsp-server.d.ts +60 -15
  66. package/lib/launcher/hydranium-glsp-server.d.ts.map +1 -1
  67. package/lib/launcher/hydranium-glsp-server.js +145 -22
  68. package/lib/launcher/hydranium-glsp-server.js.map +1 -1
  69. package/lib/launcher/index.d.ts +2 -0
  70. package/lib/launcher/index.d.ts.map +1 -1
  71. package/lib/launcher/index.js +5 -4
  72. package/lib/launcher/index.js.map +1 -1
  73. package/lib/logging/glsp-client-logger.d.ts +10 -6
  74. package/lib/logging/glsp-client-logger.d.ts.map +1 -1
  75. package/lib/logging/glsp-client-logger.js +14 -7
  76. package/lib/logging/glsp-client-logger.js.map +1 -1
  77. package/lib/messages/index.d.ts +11 -6
  78. package/lib/messages/index.d.ts.map +1 -1
  79. package/lib/messages/index.js +11 -6
  80. package/lib/messages/index.js.map +1 -1
  81. package/lib/node/start-glsp-server.d.ts +30 -8
  82. package/lib/node/start-glsp-server.d.ts.map +1 -1
  83. package/lib/node/start-glsp-server.js +49 -10
  84. package/lib/node/start-glsp-server.js.map +1 -1
  85. package/lib/state/abstract-hydranium-glsp-state.d.ts +326 -102
  86. package/lib/state/abstract-hydranium-glsp-state.d.ts.map +1 -1
  87. package/lib/state/abstract-hydranium-glsp-state.js +529 -139
  88. package/lib/state/abstract-hydranium-glsp-state.js.map +1 -1
  89. package/lib/state/diagram-status.d.ts +38 -0
  90. package/lib/state/diagram-status.d.ts.map +1 -0
  91. package/lib/state/diagram-status.js +27 -0
  92. package/lib/state/diagram-status.js.map +1 -0
  93. package/lib/state/full-text-hydranium-glsp-state.d.ts +22 -9
  94. package/lib/state/full-text-hydranium-glsp-state.d.ts.map +1 -1
  95. package/lib/state/full-text-hydranium-glsp-state.js +45 -18
  96. package/lib/state/full-text-hydranium-glsp-state.js.map +1 -1
  97. package/lib/state/hydranium-glsp-index.d.ts +40 -29
  98. package/lib/state/hydranium-glsp-index.d.ts.map +1 -1
  99. package/lib/state/hydranium-glsp-index.js +82 -45
  100. package/lib/state/hydranium-glsp-index.js.map +1 -1
  101. package/lib/state/hydranium-shared-core-services.d.ts +16 -29
  102. package/lib/state/hydranium-shared-core-services.d.ts.map +1 -1
  103. package/lib/state/hydranium-shared-core-services.js +15 -28
  104. package/lib/state/hydranium-shared-core-services.js.map +1 -1
  105. package/lib/state/index.d.ts +1 -0
  106. package/lib/state/index.d.ts.map +1 -1
  107. package/lib/state/index.js +1 -0
  108. package/lib/state/index.js.map +1 -1
  109. package/lib/state/model-ready-timeout-error.d.ts.map +1 -1
  110. package/lib/state/model-ready-timeout-error.js.map +1 -1
  111. package/lib/state/reconcile-source-model-write.d.ts +13 -27
  112. package/lib/state/reconcile-source-model-write.d.ts.map +1 -1
  113. package/lib/state/reconcile-source-model-write.js +44 -32
  114. package/lib/state/reconcile-source-model-write.js.map +1 -1
  115. package/lib/state/reconciling-multi-document-glsp-state.d.ts +125 -69
  116. package/lib/state/reconciling-multi-document-glsp-state.d.ts.map +1 -1
  117. package/lib/state/reconciling-multi-document-glsp-state.js +242 -100
  118. package/lib/state/reconciling-multi-document-glsp-state.js.map +1 -1
  119. package/lib/state/reconciling-transfer-hydranium-glsp-state.d.ts +36 -28
  120. package/lib/state/reconciling-transfer-hydranium-glsp-state.d.ts.map +1 -1
  121. package/lib/state/reconciling-transfer-hydranium-glsp-state.js +59 -42
  122. package/lib/state/reconciling-transfer-hydranium-glsp-state.js.map +1 -1
  123. package/lib/status/diagram-status-reporter.d.ts +44 -0
  124. package/lib/status/diagram-status-reporter.d.ts.map +1 -0
  125. package/lib/status/diagram-status-reporter.js +88 -0
  126. package/lib/status/diagram-status-reporter.js.map +1 -0
  127. package/lib/status/hydranium-glsp-request-model-action-handler.d.ts +27 -0
  128. package/lib/status/hydranium-glsp-request-model-action-handler.d.ts.map +1 -0
  129. package/lib/status/hydranium-glsp-request-model-action-handler.js +52 -0
  130. package/lib/status/hydranium-glsp-request-model-action-handler.js.map +1 -0
  131. package/lib/status/hydranium-glsp-set-edit-mode-action-handler.d.ts +27 -0
  132. package/lib/status/hydranium-glsp-set-edit-mode-action-handler.d.ts.map +1 -0
  133. package/lib/status/hydranium-glsp-set-edit-mode-action-handler.js +53 -0
  134. package/lib/status/hydranium-glsp-set-edit-mode-action-handler.js.map +1 -0
  135. package/lib/status/index.d.ts +12 -0
  136. package/lib/status/index.d.ts.map +1 -0
  137. package/lib/status/index.js +12 -0
  138. package/lib/status/index.js.map +1 -0
  139. package/lib/storage/hydranium-glsp-request-save-model-action-handler.d.ts +30 -0
  140. package/lib/storage/hydranium-glsp-request-save-model-action-handler.d.ts.map +1 -0
  141. package/lib/storage/hydranium-glsp-request-save-model-action-handler.js +47 -0
  142. package/lib/storage/hydranium-glsp-request-save-model-action-handler.js.map +1 -0
  143. package/lib/storage/hydranium-glsp-storage.d.ts +324 -143
  144. package/lib/storage/hydranium-glsp-storage.d.ts.map +1 -1
  145. package/lib/storage/hydranium-glsp-storage.js +569 -213
  146. package/lib/storage/hydranium-glsp-storage.js.map +1 -1
  147. package/lib/storage/index.d.ts +2 -1
  148. package/lib/storage/index.d.ts.map +1 -1
  149. package/lib/storage/index.js +2 -1
  150. package/lib/storage/index.js.map +1 -1
  151. package/lib/storage/save-delivery-policy.d.ts +51 -0
  152. package/lib/storage/save-delivery-policy.d.ts.map +1 -0
  153. package/lib/storage/{save-conflict-policy.js → save-delivery-policy.js} +7 -8
  154. package/lib/storage/save-delivery-policy.js.map +1 -0
  155. package/lib/submission/hydranium-glsp-submission-handler.d.ts +44 -4
  156. package/lib/submission/hydranium-glsp-submission-handler.d.ts.map +1 -1
  157. package/lib/submission/hydranium-glsp-submission-handler.js +82 -6
  158. package/lib/submission/hydranium-glsp-submission-handler.js.map +1 -1
  159. package/lib/testing/glsp-harness.d.ts +34 -7
  160. package/lib/testing/glsp-harness.d.ts.map +1 -1
  161. package/lib/testing/glsp-harness.js +47 -19
  162. package/lib/testing/glsp-harness.js.map +1 -1
  163. package/lib/validation/diagnostic-markers.d.ts +3 -3
  164. package/lib/validation/diagnostic-markers.d.ts.map +1 -1
  165. package/lib/validation/diagnostic-markers.js +1 -1
  166. package/lib/validation/diagnostic-markers.js.map +1 -1
  167. package/lib/validation/hydranium-glsp-model-validator.d.ts +1 -2
  168. package/lib/validation/hydranium-glsp-model-validator.d.ts.map +1 -1
  169. package/lib/validation/hydranium-glsp-model-validator.js +1 -1
  170. package/lib/validation/hydranium-glsp-model-validator.js.map +1 -1
  171. package/package.json +31 -52
  172. package/src/browser/start-glsp-server-in-worker.ts +79 -51
  173. package/src/command/diagram-edit-dropped-error.ts +51 -0
  174. package/src/command/hydranium-glsp-command-stack.ts +47 -0
  175. package/src/command/hydranium-glsp-operation-action-handler.ts +141 -0
  176. package/src/command/hydranium-glsp-operation-command.ts +602 -0
  177. package/src/command/hydranium-glsp-recording-command.ts +40 -121
  178. package/src/command/hydranium-glsp-undo-redo-action-handler.ts +29 -0
  179. package/src/command/index.ts +10 -0
  180. package/src/computed-bounds/hydranium-glsp-computed-bounds-action-handler.ts +2 -2
  181. package/src/dispatcher/server-action-dispatcher.ts +39 -31
  182. package/src/index.ts +1 -0
  183. package/src/launcher/abstract-hydranium-glsp-diagram-module.ts +79 -91
  184. package/src/launcher/drop-notifications-to-gone-client.ts +47 -0
  185. package/src/launcher/glsp-app-module.ts +1 -2
  186. package/src/launcher/glsp-connection-logger.ts +27 -0
  187. package/src/launcher/glsp-server-overrides.ts +27 -11
  188. package/src/launcher/hydranium-glsp-server.ts +153 -23
  189. package/src/launcher/index.ts +5 -4
  190. package/src/logging/glsp-client-logger.ts +16 -9
  191. package/src/messages/index.ts +16 -6
  192. package/src/node/start-glsp-server.ts +60 -14
  193. package/src/state/abstract-hydranium-glsp-state.ts +609 -154
  194. package/src/state/diagram-status.ts +41 -0
  195. package/src/state/full-text-hydranium-glsp-state.ts +46 -18
  196. package/src/state/hydranium-glsp-index.ts +85 -41
  197. package/src/state/hydranium-shared-core-services.ts +16 -29
  198. package/src/state/index.ts +1 -0
  199. package/src/state/reconcile-source-model-write.ts +51 -53
  200. package/src/state/reconciling-multi-document-glsp-state.ts +276 -102
  201. package/src/state/reconciling-transfer-hydranium-glsp-state.ts +67 -45
  202. package/src/status/diagram-status-reporter.ts +106 -0
  203. package/src/status/hydranium-glsp-request-model-action-handler.ts +43 -0
  204. package/src/status/hydranium-glsp-set-edit-mode-action-handler.ts +38 -0
  205. package/src/status/index.ts +12 -0
  206. package/src/storage/hydranium-glsp-request-save-model-action-handler.ts +40 -0
  207. package/src/storage/hydranium-glsp-storage.ts +647 -223
  208. package/src/storage/index.ts +2 -1
  209. package/src/storage/save-delivery-policy.ts +49 -0
  210. package/src/submission/hydranium-glsp-submission-handler.ts +91 -5
  211. package/src/testing/glsp-harness.ts +67 -25
  212. package/src/validation/diagnostic-markers.ts +3 -3
  213. package/src/validation/hydranium-glsp-model-validator.ts +4 -5
  214. package/lib/storage/save-conflict-policy.d.ts +0 -56
  215. package/lib/storage/save-conflict-policy.d.ts.map +0 -1
  216. package/lib/storage/save-conflict-policy.js.map +0 -1
  217. package/src/storage/save-conflict-policy.ts +0 -52
@@ -0,0 +1,27 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+
10
+ import type { Logger as GlspLogger } from '@eclipse-glsp/server';
11
+ import type { Logger as ConnectionLogger } from 'vscode-jsonrpc';
12
+
13
+ /**
14
+ * A GLSP logger in the shape a `vscode-jsonrpc` connection logs to. A launcher
15
+ * that overrides `createConnection` passes it over its injected `logger`, or
16
+ * the connection logs to `console` or nowhere instead of the adopter's logger.
17
+ * GLSP's logger has no `log`; the connection's `log` lines go to `info`, the
18
+ * threshold the framework's own `Logger.log` emits at.
19
+ */
20
+ export function createGlspConnectionLogger(logger: GlspLogger): ConnectionLogger {
21
+ return {
22
+ error: message => logger.error(message),
23
+ warn: message => logger.warn(message),
24
+ info: message => logger.info(message),
25
+ log: message => logger.info(message)
26
+ };
27
+ }
@@ -7,7 +7,7 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
- import { GLSPServer } from '@eclipse-glsp/server';
10
+ import { DefaultGLSPServer, GLSPServer, LoggerFactory } from '@eclipse-glsp/server';
11
11
  import { ContainerModule } from 'inversify';
12
12
  import { HydraniumGlspServer } from './hydranium-glsp-server.js';
13
13
 
@@ -18,15 +18,17 @@ import { HydraniumGlspServer } from './hydranium-glsp-server.js';
18
18
  *
19
19
  * **The tier matters, and the app container cannot stand in for it.** GLSP's
20
20
  * launcher creates the server container as a child and loads the adopter's
21
- * `ServerModule` into it, so a `GLSPServer` binding made further up is shadowed
22
- * rather than consulted, and inversify refuses to rebind a parent's binding
23
- * from a child at all. The framework's app-tier overrides therefore cannot
24
- * reach this symbol.
21
+ * `ServerModule` into it. The framework's server is a singleton of that
22
+ * container; bound in the app container, every connected application would
23
+ * share one server.
25
24
  *
26
- * **Load this AFTER the adopter's `ServerModule`, never beside it.** Both bind
27
- * `GLSPServer`, so a plain second binding resolves to an ambiguous-match error
28
- * rather than to either one; `rebind` needs the first binding to already be
29
- * present.
25
+ * **An adopter's server is kept when it extends {@link HydraniumGlspServer}.**
26
+ * Any other `GLSPServer` the adopter's `ServerModule` binds, upstream's default
27
+ * included, is replaced when the container first creates it, so a server that
28
+ * extends upstream's directly is discarded, with a warning naming its class.
29
+ * The replacement happens on activation because `rebind` would replace an
30
+ * adopter's subclass as well; the discarded server is constructed once and
31
+ * never used.
30
32
  *
31
33
  * Keeping this out of the adopter's hands is the point: an adopter passes their
32
34
  * own `ServerModule` in, so anything they must add themselves is a fix the
@@ -40,7 +42,21 @@ import { HydraniumGlspServer } from './hydranium-glsp-server.js';
40
42
  * a further adopter subclass moves it again.
41
43
  */
42
44
  export function createGlspServerOverrides(): ContainerModule {
43
- return new ContainerModule((_bind, _unbind, _isBound, rebind) => {
44
- rebind(GLSPServer).to(HydraniumGlspServer).inSingletonScope();
45
+ return new ContainerModule((bind, _unbind, _isBound, _rebind, _unbindAsync, onActivation) => {
46
+ bind(HydraniumGlspServer).toSelf().inSingletonScope();
47
+ onActivation<GLSPServer>(GLSPServer, (context, server) => {
48
+ if (server instanceof HydraniumGlspServer) {
49
+ return server;
50
+ }
51
+ if (server.constructor !== DefaultGLSPServer) {
52
+ context.container
53
+ .get<LoggerFactory>(LoggerFactory)('HydraniumGlspServer')
54
+ .warn(
55
+ `Replacing the bound GLSP server ${server.constructor.name} with HydraniumGlspServer; ` +
56
+ 'extend HydraniumGlspServer to keep its behaviour.'
57
+ );
58
+ }
59
+ return context.container.get(HydraniumGlspServer);
60
+ });
45
61
  });
46
62
  }
@@ -8,42 +8,122 @@
8
8
  ********************************************************************************/
9
9
 
10
10
  import {
11
+ type ActionMessage,
12
+ ClientAction,
11
13
  type ClientSession,
12
14
  DefaultGLSPServer,
13
15
  GLSPServerError,
16
+ type InitializeClientSessionParameters,
17
+ MessageAction,
14
18
  RejectAction,
15
- type RequestAction,
16
- type ResponseAction
19
+ RequestAction,
20
+ type ResponseAction,
21
+ SourceModelStorage
17
22
  } from '@eclipse-glsp/server';
23
+ import { describeErrorWithStack, RequestSaveModelAction } from '@hydranium/protocol';
18
24
  import { injectable } from 'inversify';
25
+ import { logActionFailure } from '../command/diagram-edit-dropped-error.js';
26
+ import { HydraniumGlspStorage } from '../storage/hydranium-glsp-storage.js';
19
27
 
20
28
  /**
21
29
  * The GLSP server the framework binds in place of upstream's
22
30
  * {@link DefaultGLSPServer}, so a failed REQUEST reaches its reader with the
23
- * text the thrower wrote.
31
+ * text the thrower wrote, and a diagram whose connection ends keeps its unsaved
32
+ * text for the release grace (see {@link shutdown}).
24
33
  *
25
34
  * **What breaks without it.** Upstream projects a failing request's detail as
26
35
  * `error.cause?.toString()` whenever the error is a {@link GLSPServerError},
27
36
  * and that one value feeds both the server log and the client's
28
37
  * {@link RejectAction}. A {@link GLSPServerError} carrying no `cause` therefore
29
- * reaches no reader at all — not the toast, not the details pane, not the log —
30
- * while a plain `Error` survives, because the other branch reads the error
31
- * itself. The typed error is the trap, and it is the one upstream's own
38
+ * reaches its readers as `undefined` — the log line and the client's
39
+ * action-dispatcher warning both print it — while a plain `Error` survives,
40
+ * because the other branch reads the error itself. The typed error is the trap,
41
+ * and it is the one upstream's own
32
42
  * `getOrThrow` helper raises: that helper's signature cannot pass a `cause`, so
33
43
  * an adopter using the sanctioned helper has no fix available on their side.
34
44
  * Restating the cause at each throw site is the alternative, and it costs
35
45
  * every adopter a rule they must know and cannot apply to `getOrThrow`.
36
46
  *
37
- * Only the REQUEST path needs this. Non-request actions fail through
38
- * upstream's `handleProcessError`, which reads `message` directly.
47
+ * Only the REQUEST path needs this. A non-request action fails through
48
+ * {@link handleProcessError}, which reads `message` directly; it and
49
+ * {@link process} are lifted for their logging only.
39
50
  *
40
- * Adopters who bind their own GLSP server should extend this class rather than
41
- * {@link DefaultGLSPServer}; the framework's server-container override replaces
42
- * whatever the adopter's `ServerModule` bound, so a subclass of upstream's
43
- * server would be discarded.
51
+ * An adopter who binds their own GLSP server extends this class rather than
52
+ * {@link DefaultGLSPServer}: the framework's server-container override keeps a
53
+ * server that extends this one and replaces any other, so a subclass of
54
+ * upstream's server is discarded, and with it both behaviours.
44
55
  */
45
56
  @injectable()
46
57
  export class HydraniumGlspServer extends DefaultGLSPServer {
58
+ /**
59
+ * End each diagram's client session as lost rather than closed, before
60
+ * upstream disposes the sessions, so its unsaved text waits out the release
61
+ * grace for the client to reconnect. A shutdown is how the client's
62
+ * connection ending reaches the server; a client that stops on purpose
63
+ * shuts the server down the same way, and its diagrams are held as well.
64
+ */
65
+ override shutdown(): void {
66
+ for (const session of this.clientSessions.values()) {
67
+ const storage = session.container.isBound(SourceModelStorage) ? session.container.get(SourceModelStorage) : undefined;
68
+ if (storage instanceof HydraniumGlspStorage) {
69
+ storage.dispose('lost');
70
+ } else if (storage) {
71
+ this.logger.debug(`Shutdown leaves ending ${session.id} to its storage, which is not a HydraniumGlspStorage`);
72
+ }
73
+ }
74
+ super.shutdown();
75
+ }
76
+
77
+ /**
78
+ * End a session the id still has before initializing it again. A model
79
+ * source initializes once, so a second initialize under one id is a new
80
+ * widget, a reopened tab whose closed predecessor's dispose never arrived;
81
+ * upstream would hand it that stale session.
82
+ */
83
+ override async initializeClientSession(params: InitializeClientSessionParameters): Promise<void> {
84
+ if (!this.clientSessions.has(params.clientSessionId)) {
85
+ return super.initializeClientSession(params);
86
+ }
87
+ this.logger.debug(`Client session ${params.clientSessionId} initialized again before its dispose arrived; ending the old one`);
88
+ // Not awaited before the initialize: both drop and set the session before
89
+ // their first await, and the model request that follows is read without
90
+ // waiting for this one to settle.
91
+ const disposed = this.disposeClientSession({ clientSessionId: params.clientSessionId });
92
+ const initialized = super.initializeClientSession(params);
93
+ await Promise.all([disposed, initialized]);
94
+ }
95
+
96
+ /**
97
+ * Lifted from `@eclipse-glsp/server` 2.7.0's {@link DefaultGLSPServer.process}:
98
+ * each action is logged at `debug` rather than `info`, and an action that
99
+ * fails once its session has ended, which disposing the session's dispatcher
100
+ * does to every action it holds, is logged at `debug` and told to no client,
101
+ * since the one that sent it has gone. The accompanying test pins upstream's
102
+ * source.
103
+ */
104
+ override process(message: ActionMessage): void {
105
+ this.validateServerInitialized();
106
+ this.logger.debug(`process [action=${message.action.kind}, clientId=${message.clientId}]`);
107
+ const clientSessionId = message.clientId;
108
+ const clientSession = this.clientSessions.get(clientSessionId);
109
+ if (!clientSession) {
110
+ throw new Error(`No client session has been initialized for client id: ${clientSessionId}`);
111
+ }
112
+ const action = message.action;
113
+ ClientAction.mark(action);
114
+ if (RequestAction.is(action)) {
115
+ void this.handleClientRequest(clientSession, action, message.clientId);
116
+ return;
117
+ }
118
+ clientSession.actionDispatcher.dispatch(action).catch((error: unknown) => {
119
+ if (this.clientSessions.get(clientSessionId) === clientSession) {
120
+ void this.handleProcessError(message, error);
121
+ } else {
122
+ this.logger.debug(`Action '${action.kind}' ended with its session: ${describeErrorWithStack(error)}`);
123
+ }
124
+ });
125
+ }
126
+
47
127
  /**
48
128
  * The detail projected into the client's {@link RejectAction} and the server
49
129
  * log when a request fails.
@@ -61,11 +141,40 @@ export class HydraniumGlspServer extends DefaultGLSPServer {
61
141
  }
62
142
 
63
143
  /**
64
- * Lifted verbatim from `@eclipse-glsp/server` 2.7.0's
144
+ * Lifted from `@eclipse-glsp/server` 2.7.0's
145
+ * {@link DefaultGLSPServer.handleProcessError}, logging the failure once
146
+ * with {@link logActionFailure} where upstream logs the action and the
147
+ * reason as two lines.
148
+ *
149
+ * The notification is upstream's, text and all, and this body does not
150
+ * track upstream: the accompanying test pins upstream's source as it does
151
+ * for {@link handleClientRequest}.
152
+ */
153
+ protected override handleProcessError(message: ActionMessage, reason: unknown): void {
154
+ const kind = message.action.kind;
155
+ logActionFailure(this.logger, message.action, reason);
156
+ let errorMsg = `Could not process action: '${kind}`;
157
+ let details = stringifyAsUpstream(reason);
158
+ if (reason instanceof GLSPServerError) {
159
+ details = stringifyAsUpstream(reason.cause);
160
+ errorMsg = reason.message;
161
+ }
162
+ this.sendToClient({ clientId: message.clientId, action: MessageAction.create(errorMsg, { severity: 'ERROR', details }) });
163
+ }
164
+
165
+ /**
166
+ * Lifted from `@eclipse-glsp/server` 2.7.0's
65
167
  * {@link DefaultGLSPServer.handleClientRequest}, with the detail computation
66
- * delegated to {@link requestFailureDetail}. Nothing else here — the
67
- * timeout branch, the response dispatch, the nested send-failure handling —
68
- * relates to that fallback.
168
+ * delegated to {@link requestFailureDetail}, the failure logged once with
169
+ * its stack, a request that fails once its session has ended logged at
170
+ * `debug` and answered to no client, as {@link process} treats an action,
171
+ * and one addition: a failed {@link RequestSaveModelAction} also
172
+ * raises the error notification a failed `SaveModelAction` raises, through
173
+ * {@link handleProcessError}, which then logs it instead. A rejection
174
+ * reaches only the client's log, and a Theia host only logs a rejected save,
175
+ * so without it a save that failed would tell the user nothing. Nothing else
176
+ * here — the timeout branch, the response dispatch, the nested send-failure
177
+ * handling — relates to either.
69
178
  *
70
179
  * **This body does not track upstream.** A release that changes request
71
180
  * handling would silently not apply, on the path every request takes. There
@@ -88,17 +197,38 @@ export class HydraniumGlspServer extends DefaultGLSPServer {
88
197
  this.sendResponseToClient(clientId, response);
89
198
  }
90
199
  } catch (error: unknown) {
200
+ if (this.clientSessions.get(clientSession.id) !== clientSession) {
201
+ this.logger.debug(`Request '${action.kind}' (${action.requestId}) ended with its session: ${describeErrorWithStack(error)}`);
202
+ return;
203
+ }
91
204
  const detail = this.requestFailureDetail(error);
92
- this.logger.error(`Failed to handle request '${action.kind}' (${action.requestId}):`, detail);
205
+ const isSave = action.kind === RequestSaveModelAction.KIND;
206
+ if (!isSave) {
207
+ this.logger.error(`Failed to handle request '${action.kind}' (${action.requestId}): ${describeErrorWithStack(error)}`);
208
+ }
209
+ // Inside the guard: `process` does not await this method, so a throw
210
+ // here would be an unhandled rejection.
93
211
  try {
94
- const reject = RejectAction.create(`Failed to handle request '${action.kind}' (${action.requestId})`, {
95
- responseId: action.requestId,
96
- detail
97
- });
98
- this.sendResponseToClient(clientId, reject);
212
+ try {
213
+ const reject = RejectAction.create(`Failed to handle request '${action.kind}' (${action.requestId})`, {
214
+ responseId: action.requestId,
215
+ detail
216
+ });
217
+ this.sendResponseToClient(clientId, reject);
218
+ } finally {
219
+ // Also when the reject could not be sent, since this logs the save's failure.
220
+ if (isSave) {
221
+ this.handleProcessError({ clientId, action }, error);
222
+ }
223
+ }
99
224
  } catch (sendError: unknown) {
100
- this.logger.error(`Failed to send rejection for request '${action.requestId}':`, sendError);
225
+ this.logger.error(`Failed to send rejection for request '${action.requestId}': ${describeErrorWithStack(sendError)}`);
101
226
  }
102
227
  }
103
228
  }
104
229
  }
230
+
231
+ /** `value?.toString?.()`, as upstream projects a reason into a notification's details. */
232
+ function stringifyAsUpstream(value: unknown): string | undefined {
233
+ return (value as { toString?: () => string } | null | undefined)?.toString?.();
234
+ }
@@ -13,10 +13,11 @@ export * from './abstract-hydranium-glsp-diagram-module.js';
13
13
  // rather than upstream's — the server-container override discards a subclass of
14
14
  // `DefaultGLSPServer`.
15
15
  export * from './hydranium-glsp-server.js';
16
+ export * from './drop-notifications-to-gone-client.js';
17
+ export * from './glsp-connection-logger.js';
16
18
  // The socket bringup lives in `@hydranium/glsp-server/node` and the worker
17
19
  // bringup in `@hydranium/glsp-server/browser`, not here: each pulls the
18
20
  // upstream build for its platform, which the portable `.` entry must stay free
19
- // of. `glsp-framework-overrides.js` and `glsp-server-overrides.js` are
20
- // deliberately NOT re-exported — they are the bindings those two bringups share,
21
- // reached by relative import, and an adopter composing a container by hand wants
22
- // the launcher rather than its internals.
21
+ // of. The container overrides those two bringups share stay unexported, reached
22
+ // by relative import: an adopter composing a container by hand wants the
23
+ // launcher rather than its internals.
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { type Logger as GlspLogger, LogLevel } from '@eclipse-glsp/server';
11
11
  import { LspLogger, type LspLoggerOptions, type ServerSharedServices } from '@hydranium/core';
12
- import { Logger, type LogThreshold } from '@hydranium/protocol';
12
+ import { describeErrorWithStack, LEVEL_ORDER, Logger, type LogThreshold } from '@hydranium/protocol';
13
13
 
14
14
  /**
15
15
  * Options accepted by {@link GlspClientLogger}. Extends
@@ -19,11 +19,11 @@ import { Logger, type LogThreshold } from '@hydranium/protocol';
19
19
  export interface GlspClientLoggerOptions extends LspLoggerOptions {
20
20
  /**
21
21
  * Per-instance NARROWING of the framework threshold. Omit it — the default
22
- * tracks `Logger.getLevel()` live, which is what makes one
22
+ * tracks the framework thresholds live, which is what makes one
23
23
  * `HYDRANIUM_LOG_LEVEL` / one LSP setting govern GLSP output too.
24
24
  *
25
25
  * It can only narrow, never widen, and that is not a policy choice:
26
- * `AbstractLogger.send` gates every emission on the process-wide threshold
26
+ * `AbstractLogger.send` gates every emission on `Logger.isLevelEnabled`
27
27
  * before it reaches the output channel, so a value more verbose than the
28
28
  * global is silently ineffective. Set it only to make ONE component quieter
29
29
  * than the rest.
@@ -66,8 +66,8 @@ export function glspLogLevelOf(threshold: LogThreshold): LogLevel {
66
66
  * GLSP-specific concerns layered on top of {@link LspLogger}:
67
67
  *
68
68
  * - A {@link logLevel} field, because GLSP's `Logger` contract declares one.
69
- * **It tracks the framework's process-wide threshold by default**, resolving
70
- * `Logger.getLevel()` through {@link glspLogLevelOf} on every read — so
69
+ * **It tracks the framework's thresholds by default**, resolving them
70
+ * through {@link glspLogLevelOf} on every read — so
71
71
  * `HYDRANIUM_LOG_LEVEL`, the LSP log-level setting and the Theia preference
72
72
  * all reach GLSP output without a second knob. Assigning the field (or
73
73
  * passing `logLevel`) pins an explicit per-instance NARROWING; see
@@ -104,9 +104,16 @@ export class GlspClientLogger extends LspLogger implements GlspLogger {
104
104
  this.logLevelOverride = options.logLevel;
105
105
  }
106
106
 
107
- /** GLSP's `Logger.logLevel` contract; the framework threshold unless pinned. */
107
+ /**
108
+ * GLSP's `Logger.logLevel` contract; unless pinned, the more verbose of
109
+ * `Logger.getLevel()` and `Logger.getFileLevel()`. The process threshold
110
+ * alone drops GLSP lines a log file with its own threshold asks for.
111
+ */
108
112
  get logLevel(): LogLevel {
109
- return this.logLevelOverride ?? glspLogLevelOf(Logger.getLevel());
113
+ const level = Logger.getLevel();
114
+ const fileLevel = Logger.getFileLevel();
115
+ const widest = fileLevel && LEVEL_ORDER[fileLevel] > LEVEL_ORDER[level] ? fileLevel : level;
116
+ return this.logLevelOverride ?? glspLogLevelOf(widest);
110
117
  }
111
118
 
112
119
  set logLevel(level: LogLevel) {
@@ -156,10 +163,10 @@ export class GlspClientLogger extends LspLogger implements GlspLogger {
156
163
  return params.length === 0 ? message : `${message} ${params.map(p => this.formatParam(p)).join(',\n')}`;
157
164
  }
158
165
 
159
- /** Format a single param: errors → `message\nstack`; everything else → JSON.stringify(p, undefined, 4). */
166
+ /** Format a single param: errors → their stack and causes; everything else → JSON.stringify(p, undefined, 4). */
160
167
  protected formatParam(param: unknown): string {
161
168
  if (param instanceof Error) {
162
- return `${param.message}\n${param.stack ?? ''}`;
169
+ return describeErrorWithStack(param);
163
170
  }
164
171
  try {
165
172
  return JSON.stringify(param, undefined, 4);
@@ -10,11 +10,21 @@
10
10
  /**
11
11
  * Every user-facing message `@hydranium/glsp-server` raises.
12
12
  *
13
- * Enumeration only: GLSP's action protocol carries no slot for a message
14
- * identity on any of its notification actions, so a diagram error reaches the
15
- * client as English by upstream constraint. The declarations still earn their
16
- * place — an adopter rendering their own diagram chrome can key on the code, and
17
- * the barrel is how they discover the codes exist.
13
+ * **Each one is rendered AT ITS RAISE SITE**, through the shared
14
+ * `MessageRenderer`, because GLSP's action protocol carries no slot for an
15
+ * identity on any of its actions — so the identity is gone the moment a raise
16
+ * site formats its text, and there is nothing at the error boundary to render
17
+ * from. That makes this head the one place where a new message can ship
18
+ * unrendered without any single chokepoint noticing, which is why the package's
19
+ * own test suite scans for a declaration reached by `.format()` instead.
20
+ *
21
+ * The barrel is also how an adopter rendering their own diagram chrome
22
+ * discovers the codes exist.
18
23
  */
19
24
 
20
- export { SAVE_TARGET_UNKNOWN, SOURCE_URI_MISSING } from '../storage/hydranium-glsp-storage.js';
25
+ export {
26
+ DIAGRAM_READONLY_PARSE_ERROR,
27
+ DIAGRAM_SESSION_REFUSED,
28
+ SAVE_TARGET_UNKNOWN,
29
+ SOURCE_URI_MISSING
30
+ } from '../storage/hydranium-glsp-storage.js';
@@ -15,15 +15,55 @@ import {
15
15
  createAppModule,
16
16
  defaultSocketLaunchOptions
17
17
  } from '@eclipse-glsp/server/node.js';
18
- import { Container, type ContainerModule } from 'inversify';
19
- import type { Logger } from '@hydranium/protocol';
20
- import { Deferred } from '@hydranium/langium';
18
+ import { Container, ContainerModule, injectable } from 'inversify';
19
+ import { Deferred, describeErrorWithStack, sendByMethodName, type Logger } from '@hydranium/protocol';
21
20
  import type * as net from 'node:net';
21
+ import { createMessageConnection, SocketMessageReader, SocketMessageWriter } from 'vscode-jsonrpc/node';
22
22
  import type { IntegratedServer } from '@hydranium/core';
23
+ import { dropNotificationsToGoneClient } from '../launcher/drop-notifications-to-gone-client.js';
24
+ import { createGlspConnectionLogger } from '../launcher/glsp-connection-logger.js';
23
25
  import { createGlspFrameworkOverrides } from '../launcher/glsp-framework-overrides.js';
24
26
  import { createGlspServerOverrides } from '../launcher/glsp-server-overrides.js';
25
27
  import type { LspConnectionLike } from '@hydranium/core/node';
26
28
 
29
+ /**
30
+ * The default socket launcher of {@link startGlspServer}: GLSP's own,
31
+ * with each connection built from this package's copy of `vscode-jsonrpc`, so a
32
+ * framework error thrown in a handler keeps its code. The connection sends
33
+ * GLSP's typed messages by method name, since GLSP builds them from its copy.
34
+ */
35
+ @injectable()
36
+ export class HydraniumGlspSocketServerLauncher extends SocketServerLauncher {
37
+ protected override createConnection(socket: net.Socket): ReturnType<SocketServerLauncher['createConnection']> {
38
+ return dropNotificationsToGoneClient(
39
+ sendByMethodName(
40
+ createMessageConnection(
41
+ new SocketMessageReader(socket),
42
+ new SocketMessageWriter(socket),
43
+ createGlspConnectionLogger(this.logger)
44
+ )
45
+ ),
46
+ this.logger
47
+ );
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Binds GLSP's own `SocketServerLauncher` token to
53
+ * {@link HydraniumGlspSocketServerLauncher}. {@link startGlspServer} loads it
54
+ * before the adopter's `appModules`, so an adopter replaces the launcher with
55
+ * `rebind`; a second `bind` makes the resolve ambiguous and the start throws.
56
+ * A replacement extends {@link HydraniumGlspSocketServerLauncher}: GLSP's own
57
+ * launcher builds its connection from GLSP's copy, so framework errors lose
58
+ * their code, and GLSP's typed messages throw where its packages nest
59
+ * separate copies.
60
+ */
61
+ export function createGlspSocketLauncherModule(): ContainerModule {
62
+ return new ContainerModule(bind => {
63
+ bind(SocketServerLauncher).to(HydraniumGlspSocketServerLauncher);
64
+ });
65
+ }
66
+
27
67
  /**
28
68
  * Options for {@link startGlspServer}.
29
69
  *
@@ -65,10 +105,11 @@ export interface GlspServerOptions {
65
105
  /**
66
106
  * Additional Inversify modules to load on the app container AFTER GLSP's
67
107
  * own app module (via `createAppModule`, which binds `InjectionContainer`
68
- * plus the GLSP-version-specific app bindings) and the framework overrides
108
+ * plus the GLSP-version-specific app bindings), the framework overrides
69
109
  * that route GLSP's logger and logger factory through the adopter logger
70
- * and add the shared Tracer. Adopters wire language-services bindings
71
- * (e.g. their own shared-services symbol) here.
110
+ * and add the shared Tracer, and {@link createGlspSocketLauncherModule}.
111
+ * Adopters wire language-services bindings (e.g. their own shared-services
112
+ * symbol) here, and replace the launcher with `rebind`.
72
113
  */
73
114
  readonly appModules?: ReadonlyArray<ContainerModule>;
74
115
  /**
@@ -120,11 +161,12 @@ export interface StartedGlspServer extends IntegratedServer {
120
161
  /**
121
162
  * Start a GLSP socket head. Builds the GLSP app container with framework-
122
163
  * default `Logger` / `LoggerFactory` bindings, layers adopter modules,
123
- * resolves {@link SocketServerLauncher}, configures the adopter
164
+ * resolves the launcher bound to {@link SocketServerLauncher}, by default
165
+ * {@link HydraniumGlspSocketServerLauncher}, configures the adopter
124
166
  * {@link ServerModule}, and starts listening.
125
167
  *
126
168
  * **GLSP launcher coupling.** GLSP's {@link SocketServerLauncher} owns the
127
- * `net.Server` and per-connection `MessageConnection` construction;
169
+ * `net.Server` and each connection's server instance;
128
170
  * the framework wrapper composes ABOVE that launcher rather than replacing
129
171
  * it. The framework's own `startSocketServer`
130
172
  * helper is NOT used here — it's designed for stdio/raw-JSON-RPC heads
@@ -150,13 +192,17 @@ export function startGlspServer(options: GlspServerOptions): StartedGlspServer {
150
192
  const glspAppModule = createAppModule({ ...launchOptions, consoleLog: false, fileLog: false });
151
193
 
152
194
  const appContainer = new Container();
153
- appContainer.load(glspAppModule, createGlspFrameworkOverrides(options.createLogger), ...(options.appModules ?? []));
195
+ appContainer.load(
196
+ glspAppModule,
197
+ createGlspFrameworkOverrides(options.createLogger),
198
+ createGlspSocketLauncherModule(),
199
+ ...(options.appModules ?? [])
200
+ );
154
201
 
155
- const launcher = appContainer.resolve<SocketServerLauncher>(SocketServerLauncher);
202
+ const launcher = appContainer.get<SocketServerLauncher>(SocketServerLauncher);
156
203
  // Passed as an additional module rather than folded into the app container:
157
204
  // the launcher loads these into the per-connection SERVER container, which is
158
- // the only tier where the adopter's `ServerModule` binding of `GLSPServer`
159
- // can be rebound.
205
+ // the tier where each connection gets a server of its own.
160
206
  launcher.configure(options.serverModule, createGlspServerOverrides());
161
207
 
162
208
  const started = new Deferred<void>();
@@ -177,7 +223,7 @@ export function startGlspServer(options: GlspServerOptions): StartedGlspServer {
177
223
  if (options.lspConnection && options.portCommand) {
178
224
  options.lspConnection.onRequest(options.portCommand, () => portHandle.port);
179
225
  }
180
- lifecycle?.info(`[GlspServer] Ready to accept new client requests on port: ${address.port}`);
226
+ lifecycle?.info(`[GlspServer] Ready to accept new client requests on ${address.address} port: ${address.port}`);
181
227
  started.resolve();
182
228
  } else {
183
229
  const message = address === null ? 'address is null' : `bound to non-TCP "${String(address)}"`;
@@ -187,7 +233,7 @@ export function startGlspServer(options: GlspServerOptions): StartedGlspServer {
187
233
  }
188
234
  });
189
235
  netServer.on('error', (err: Error) => {
190
- lifecycle?.error(`[GlspServer] Error: ${err.message}`);
236
+ lifecycle?.error(`[GlspServer] Error: ${describeErrorWithStack(err)}`);
191
237
  started.reject(err);
192
238
  });
193
239
  if (options.onClientConnection) {