@hydranium/glsp-server 1.0.0-next.31 → 1.0.0-next.315

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 +80 -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 +50 -15
  82. package/lib/node/start-glsp-server.d.ts.map +1 -1
  83. package/lib/node/start-glsp-server.js +97 -30
  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 +133 -40
  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
@@ -10,6 +10,7 @@
10
10
  import { type AnyObject, type JsonModelState, JsonRecordingCommand, type MaybePromise } from '@eclipse-glsp/server';
11
11
  import { type AstNode } from '@hydranium/langium';
12
12
  import { type AbstractHydraniumGlspState } from '../state/abstract-hydranium-glsp-state.js';
13
+ import { HydraniumGlspOperationCommand, openOperationOf, runOperation } from './hydranium-glsp-operation-command.js';
13
14
 
14
15
  /**
15
16
  * Source-model state shape consumed by {@link HydraniumGlspRecordingCommand}.
@@ -25,159 +26,77 @@ export type HydraniumGlspRecordingState<TSourceModel extends AnyObject> = Abstra
25
26
  JsonModelState<TSourceModel>;
26
27
 
27
28
  /**
28
- * GLSP {@link JsonRecordingCommand} base shared by all hydranium adopters.
29
- * Adds two things on top of GLSP's class:
30
- *
31
- * 1. A **time-labelled execute wrapper** so log lines record how long a
32
- * user-facing operation takes end-to-end — including the `postChange`
33
- * source-model update that re-parses the document. Otherwise the diff +
34
- * patch + persist work happens silently and is hard to attribute when
35
- * something is slow.
36
- * 2. A typed `state` field that exposes `updateSourceModel` for the
37
- * `postChange` bridge AND the framework `logger` for time-labelled
38
- * logging. GLSP's base class only types `modelState: JsonModelState`,
39
- * which lacks the framework's logger surface.
29
+ * GLSP {@link JsonRecordingCommand} base shared by all hydranium adopters: a
30
+ * handler mutates `sourceRoot` in the command's runnable, and the operation
31
+ * the command executes in writes the result once when it ends.
40
32
  *
41
33
  * **Optional undo/redo bridge hooks.** An adopter command can supply
42
- * `undoAction` / `redoAction` callbacks invoked alongside the patch apply, to
43
- * re-emit a side-effect the patch itself does not carry. Provided as optional
44
- * constructor args so adopters get that composition surface without
45
- * subclassing. Operation handlers that don't need bridge callbacks omit them.
34
+ * `undoAction` / `redoAction` callbacks, run when the operation is undone or
35
+ * redone and when a failed operation rolls back, to re-emit a side effect the
36
+ * model does not carry. During an undo or redo they run against throwaway
37
+ * copies, and a recording command executed there throws.
46
38
  *
47
- * **postChange semantics.** GLSP's `JsonRecordingCommand.postChange` calls
48
- * `modelState.updateSourceModel(newModel)` with the model only. The
49
- * framework lift overrides `postChange` so it also threads the based-on
50
- * version captured at command start — letting the downstream
51
- * `ModelService.update` opt into the `ConflictError` gate. Undo / redo
52
- * postChange calls run with version `undefined` (no gating): the user
53
- * authored against the recorded patch, not against a specific server
54
- * version, so re-applying it should succeed regardless of intervening
55
- * edits. The recorded patch itself encodes the semantic intent.
39
+ * A command executed outside an operation opens one of its own through
40
+ * `runExclusive`, so executing it from inside the boundary with no operation
41
+ * open deadlocks. Its undo and redo then undo and redo that operation without
42
+ * taking the boundary, which the caller holds, as the framework's undo handler
43
+ * does; taking it there would deadlock under that handler.
56
44
  */
57
45
  export class HydraniumGlspRecordingCommand<TSourceModel extends AnyObject> extends JsonRecordingCommand<TSourceModel> {
58
46
  declare protected modelState: HydraniumGlspRecordingState<TSourceModel>;
59
47
 
60
- /**
61
- * Based-on version captured at {@link execute} start; threaded into
62
- * {@link postChange} so {@link AbstractHydraniumGlspState.updateSourceModel}
63
- * sees the version the user authored against. Cleared after `execute`
64
- * returns so undo / redo paths fall through to `updateSourceModel`
65
- * without a version (no gating).
66
- */
67
- protected activeVersion?: number;
68
-
69
- /**
70
- * Source-model snapshots captured at {@link execute} — the state the
71
- * command left ({@link afterSnapshot}) and the state it started from
72
- * ({@link beforeSnapshot}). {@link undo} / {@link redo} reconcile the
73
- * recorded transition against the *current* source model via the state's
74
- * {@link AbstractHydraniumGlspState.conflictResolver}, so under the default
75
- * reconciling policy a concurrent edit to another field survives and a
76
- * same-field collision is dropped rather than clobbered.
77
- */
78
- protected beforeSnapshot?: TSourceModel;
79
- protected afterSnapshot?: TSourceModel;
48
+ /** The operation this command opened, when it executed outside one. */
49
+ protected ownOperation?: HydraniumGlspOperationCommand<TSourceModel>;
80
50
 
81
51
  constructor(
82
52
  modelState: HydraniumGlspRecordingState<TSourceModel>,
83
53
  /** Human-readable label for the operation; appears in the timing-pair log line. */
84
54
  protected readonly label: string,
85
55
  doExecute: () => MaybePromise<void>,
86
- /** Optional bridge invoked alongside undo patch application; for adopters
87
- * that need to re-emit a side-effect the patch does not carry. */
56
+ /** Optional bridge invoked when the command is undone; for adopters
57
+ * that need to re-emit a side-effect the model does not carry. */
88
58
  protected readonly undoAction?: () => MaybePromise<void>,
89
- /** Optional bridge invoked alongside redo patch application. */
59
+ /** Optional bridge invoked when the command is redone. */
90
60
  protected readonly redoAction?: () => MaybePromise<void>
91
61
  ) {
92
62
  super(modelState, doExecute);
93
63
  }
94
64
 
95
- /**
96
- * Time-label the full execute path: pre-state snapshot + `doExecute` +
97
- * post-state snapshot + patch-derive + `postChange` (which writes back
98
- * via {@link AbstractHydraniumGlspState.updateSourceModel}). The label combines
99
- * the operation name with the source-uri-stamped logger.
100
- *
101
- * Captures {@link AbstractHydraniumGlspState.version} at start so {@link postChange}
102
- * can thread the based-on version into `updateSourceModel`. The capture
103
- * is `try`/`finally`-scoped so undo / redo paths invoked later do not
104
- * see a stale captured version.
105
- */
106
65
  override async execute(): Promise<void> {
107
- const logger = this.modelState.logger.for('HydraniumGlspRecordingCommand');
108
- this.activeVersion = this.modelState.version;
109
- logger.debug(`Executing '${this.label}' (based-on doc.version=v${this.activeVersion})`);
110
- this.beforeSnapshot = this.deepClone(await this.getJsonObject());
111
- try {
112
- await this.modelState.tracer.for('HydraniumGlspRecordingCommand').time(`Execute command '${this.label}'`, () => super.execute());
113
- } finally {
114
- this.activeVersion = undefined;
66
+ const open = openOperationOf(this.modelState);
67
+ if (!open) {
68
+ const operation = new HydraniumGlspOperationCommand<TSourceModel>(this.modelState);
69
+ if ((await this.modelState.runExclusive(() => runOperation(operation, () => this))) === 'executed') {
70
+ this.ownOperation = operation;
71
+ }
72
+ return;
115
73
  }
116
- this.afterSnapshot = this.deepClone(await this.getJsonObject());
74
+ open.assertNotDuringUndoRedo(this.label);
75
+ const tracer = this.modelState.tracer.for('HydraniumGlspRecordingCommand');
76
+ tracer.debug(`Executing '${this.label}' (base version v${this.modelState.version})`);
77
+ await tracer.time(`Execute command '${this.label}'`, () => super.execute());
78
+ open.recordExecuted({ undo: () => this.undoAction?.(), redo: () => this.redoAction?.() });
117
79
  }
118
80
 
119
- /**
120
- * Override of GLSP's {@link JsonRecordingCommand.postChange} so the
121
- * call to {@link AbstractHydraniumGlspState.updateSourceModel} threads the
122
- * captured based-on version alongside the new model.
123
- *
124
- * During {@link execute} the version is the snapshot captured at
125
- * command start; during {@link undo} / {@link redo} it is `undefined`
126
- * — replaying a recorded patch does not author against a specific
127
- * server version, so no gate applies.
128
- */
129
- protected override postChange(newModel: TSourceModel): MaybePromise<void> {
130
- return this.modelState.updateSourceModel(newModel, this.activeVersion);
81
+ /** Writes nothing: the operation the command executes in writes once when it ends. */
82
+ protected override postChange(_newModel: TSourceModel): void {
83
+ // Written by the operation.
131
84
  }
132
85
 
133
86
  /**
134
- * Reconcile the recorded `after → before` transition against the
135
- * *current* source model via the state's
136
- * {@link AbstractHydraniumGlspState.conflictResolver}, invoke the optional adopter
137
- * `undoAction`, and persist via `postChange`. Under the default reconciling
138
- * policy the revert is replayed guarded by `test` ops, so a concurrent edit
139
- * to another field is preserved (merged) and a same-field collision is
140
- * dropped rather than clobbered; a force policy clobbers (last-writer-wins).
141
- * On anything but a clean merge the undo is a logged no-op — the side-effect
142
- * bridge and `postChange` only run on a merged result, preserving a
143
- * "bridge before commit" order.
87
+ * Inside an open operation does nothing: the operation runs the bridge, once
88
+ * for every recording command that executed.
144
89
  */
145
90
  override async undo(): Promise<void> {
146
- if (!this.undoPatch || this.beforeSnapshot === undefined || this.afterSnapshot === undefined) {
147
- return;
91
+ if (!openOperationOf(this.modelState)) {
92
+ await this.ownOperation?.undo();
148
93
  }
149
- const logger = this.modelState.logger.for('HydraniumGlspRecordingCommand');
150
- logger.debug(`Undoing '${this.label}' at doc.version=v${this.modelState.version}`);
151
- const outcome = await this.modelState.conflictResolver.resolve(this.afterSnapshot, this.beforeSnapshot, async () =>
152
- this.getJsonObject()
153
- );
154
- if (outcome.status !== 'merged') {
155
- logger.warn(`Undo '${this.label}' skipped (${outcome.status}); a foreign edit changed a shared field since the command ran`);
156
- return;
157
- }
158
- await this.undoAction?.();
159
- await this.postChange?.(outcome.merged);
160
94
  }
161
95
 
162
- /**
163
- * Reconcile the recorded `before → after` transition against the current
164
- * source model, invoke the optional adopter `redoAction`, and persist via
165
- * `postChange`. Same guarded merge / drop semantics as {@link undo}.
166
- */
96
+ /** Inside an open operation does nothing, as {@link undo}. */
167
97
  override async redo(): Promise<void> {
168
- if (!this.redoPatch || this.beforeSnapshot === undefined || this.afterSnapshot === undefined) {
169
- return;
170
- }
171
- const logger = this.modelState.logger.for('HydraniumGlspRecordingCommand');
172
- logger.debug(`Redoing '${this.label}' at doc.version=v${this.modelState.version}`);
173
- const outcome = await this.modelState.conflictResolver.resolve(this.beforeSnapshot, this.afterSnapshot, async () =>
174
- this.getJsonObject()
175
- );
176
- if (outcome.status !== 'merged') {
177
- logger.warn(`Redo '${this.label}' skipped (${outcome.status}); a foreign edit changed a shared field since the undo`);
178
- return;
98
+ if (!openOperationOf(this.modelState)) {
99
+ await this.ownOperation?.redo();
179
100
  }
180
- await this.redoAction?.();
181
- await this.postChange?.(outcome.merged);
182
101
  }
183
102
  }
@@ -0,0 +1,29 @@
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 Action, ModelState, UndoRedoActionHandler } from '@eclipse-glsp/server';
11
+ import { type AstNode } from '@hydranium/langium';
12
+ import { inject, injectable } from 'inversify';
13
+ import { type AbstractHydraniumGlspState } from '../state/abstract-hydranium-glsp-state.js';
14
+
15
+ /**
16
+ * GLSP's undo and redo handler inside the state's `runExclusive`. GLSP's
17
+ * command stack moves its position before it undoes or redoes the entry, so
18
+ * the boundary covers the whole action: taken around the undo or redo alone,
19
+ * an undo arriving while an operation runs would move the position past an
20
+ * entry it then cannot undo.
21
+ */
22
+ @injectable()
23
+ export class HydraniumGlspUndoRedoActionHandler extends UndoRedoActionHandler {
24
+ @inject(ModelState) protected readonly modelState!: AbstractHydraniumGlspState<AstNode, unknown>;
25
+
26
+ override execute(action: Action): Promise<Action[]> {
27
+ return this.modelState.runExclusive(() => super.execute(action));
28
+ }
29
+ }
@@ -8,3 +8,13 @@
8
8
  ********************************************************************************/
9
9
 
10
10
  export * from './hydranium-glsp-recording-command.js';
11
+ export * from './hydranium-glsp-command-stack.js';
12
+ export * from './hydranium-glsp-operation-action-handler.js';
13
+ export {
14
+ HydraniumGlspOperationCommand,
15
+ type OperationSideEffect,
16
+ type OperationTransition,
17
+ type UndoRedoDirection
18
+ } from './hydranium-glsp-operation-command.js';
19
+ export * from './hydranium-glsp-undo-redo-action-handler.js';
20
+ export * from './diagram-edit-dropped-error.js';
@@ -32,7 +32,7 @@ import { type HydraniumGlspSubmissionHandler } from '../submission/hydranium-gls
32
32
  * very first `computedBounds` arrives with a revision the server has already
33
33
  * bumped, the subsequent `SetModelAction` response never fires and the diagram
34
34
  * stays stuck at "model loading…". Adopters need this initial-handshake case
35
- * surfaced as a warning so the failure mode is visible in logs.
35
+ * logged as an error: the diagram stays empty, so the requested load failed.
36
36
  *
37
37
  * **Handshake detection** uses
38
38
  * {@link HydraniumGlspSubmissionHandler.hasPendingInitialRequest} — true
@@ -55,7 +55,7 @@ export class HydraniumGlspComputedBoundsActionHandler extends ComputedBoundsActi
55
55
  const model = this.modelState.root;
56
56
  if (action.revision !== model.revision) {
57
57
  if (this.submissionHandler.hasPendingInitialRequest()) {
58
- this.modelState.logger.warn(
58
+ this.modelState.logger.error(
59
59
  `ComputedBounds rejected (initial handshake): action.revision=${action.revision} model.revision=${model.revision} — ` +
60
60
  'initial SetModelAction will not fire (empty diagram)'
61
61
  );
@@ -7,66 +7,74 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
- import { type Action, DefaultActionDispatcher } from '@eclipse-glsp/server';
10
+ import { type Action, CheckEdgeResultAction, DefaultActionDispatcher } from '@eclipse-glsp/server';
11
11
  import { inject, injectable } from 'inversify';
12
- import { type Tracer } from '@hydranium/protocol';
13
- import { type GlspClientLogger } from '../logging/glsp-client-logger.js';
12
+ import { Logger, type Tracer } from '@hydranium/protocol';
14
13
  import { HydraniumTypes } from '../state/hydranium-shared-core-services.js';
15
14
 
16
15
  /**
17
- * Server-side action dispatcher with built-in traffic observability:
18
- * every {@link DefaultActionDispatcher.dispatch} call is wrapped in a
19
- * {@link Tracer.time} pair that records the dispatch direction
20
- * (`→ client` vs `→ server`) and timing.
16
+ * Server-side action dispatcher with built-in traffic observability: while
17
+ * debug is enabled, each {@link DefaultActionDispatcher.dispatch} call runs
18
+ * inside {@link Tracer.time}, which logs it with its direction (`→ client` vs
19
+ * `→ server`) and timing only when it is slow or fails, and an action that
20
+ * {@link summarize} details gets its own debug line whatever its duration.
21
21
  *
22
22
  * **Why this lives in the framework (vs left as an adopter override).**
23
23
  * Debugging GLSP state, storage, and submission flows depends heavily on
24
- * seeing each action with timing and direction — especially when bringing
25
- * up a new adopter against an empty/partial diagram before its submission
26
- * flow is complete. Providing this dispatcher in the framework gives every
27
- * adopter the same observability primitive from the very first GLSP-head
28
- * launch.
24
+ * seeing slow or failing actions with timing and direction — especially when
25
+ * bringing up a new adopter against an empty/partial diagram before its
26
+ * submission flow is complete. Providing this dispatcher in the framework
27
+ * gives every adopter the same observability primitive from the very first
28
+ * GLSP-head launch.
29
29
  *
30
30
  * **Extension points.**
31
31
  * - {@link loggedKinds} — if set, filter the log lines (the dispatch
32
- * still happens; only the bracketed timing line is suppressed). Default
33
- * `undefined` means **every** action's dispatch is timed and logged.
34
- * - {@link summarize} — return a non-empty string to append `[summary]`
35
- * to the log line; default returns `''` (no extra bracket).
36
- *
37
- * Adopters bind it in a per-diagram module, as the `ActionDispatcher` service.
32
+ * still happens; only its timing and summary lines are suppressed).
33
+ * Default `undefined` times every action's dispatch while debug is enabled.
34
+ * - {@link summarize} — return a non-empty string to log it as a debug line
35
+ * for the dispatch; `''` logs none.
38
36
  */
39
37
  @injectable()
40
38
  export class HydraniumGlspServerActionDispatcher extends DefaultActionDispatcher {
41
- /** Narrowed from `Logger`; DI binds {@link GlspClientLogger}. */
42
- declare protected readonly logger: GlspClientLogger;
43
-
44
39
  /** Caller-tagged tracer (auto-componented with this class name, like the logger). */
45
40
  @inject(HydraniumTypes.Tracer) protected readonly tracer!: Tracer;
46
41
 
47
42
  /**
48
- * If set, the timing log line is emitted only when `action.kind` is in
49
- * this set. Subclasses override via field reassignment.
43
+ * If set, the timing and summary lines are emitted only when `action.kind`
44
+ * is in this set. Subclasses override via field reassignment.
50
45
  */
51
46
  protected readonly loggedKinds: ReadonlySet<string> | undefined = undefined;
52
47
 
53
48
  /**
54
- * Override to enrich the log line with payload-specific detail. Return an
55
- * empty string to suppress the bracket. Default returns `''`.
49
+ * Payload detail logged at debug before the dispatch, as
50
+ * `Dispatch action '<kind>' <direction> [<summary>]`, on every dispatch,
51
+ * fast or slow; `''` logs no line. The default details only an action whose
52
+ * outcome the log otherwise loses, each through a `format*` method. Called
53
+ * only when debug is enabled, so it may serialise the payload.
56
54
  */
57
- protected summarize(_action: Action): string {
55
+ protected summarize(action: Action): string {
56
+ if (CheckEdgeResultAction.is(action)) {
57
+ return this.formatEdgeCheck(action);
58
+ }
58
59
  return '';
59
60
  }
60
61
 
62
+ /** An edge check's verdict, so the log shows whether the server let an edge tool connect. */
63
+ protected formatEdgeCheck(action: CheckEdgeResultAction): string {
64
+ return `edgeType=${action.edgeType} source=${action.sourceElementId} target=${action.targetElementId ?? 'none'} isValid=${action.isValid}`;
65
+ }
66
+
61
67
  override dispatch(action: Action): Promise<void> {
62
- if (this.loggedKinds && !this.loggedKinds.has(action.kind)) {
68
+ if (!Logger.isLevelEnabled('debug') || (this.loggedKinds && !this.loggedKinds.has(action.kind))) {
63
69
  return super.dispatch(action);
64
70
  }
65
71
  const direction = this.clientActionForwarder.shouldForwardToClient(action) ? '→ client' : '→ server';
72
+ const label = `Dispatch action '${action.kind}' ${direction}`;
66
73
  const summary = this.summarize(action);
67
- const label = summary
68
- ? `Dispatch action '${action.kind}' ${direction} [${summary}]`
69
- : `Dispatch action '${action.kind}' ${direction}`;
70
- return this.tracer.time(label, () => super.dispatch(action));
74
+ if (summary) {
75
+ // Its own line: `time` is silent on a fast success.
76
+ this.tracer.debug(`${label} [${summary}]`);
77
+ }
78
+ return this.tracer.time(label, () => super.dispatch(action), 'debug');
71
79
  }
72
80
  }
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export * from './dispatcher/index.js';
14
14
  export * from './launcher/index.js';
15
15
  export * from './logging/index.js';
16
16
  export * from './state/index.js';
17
+ export * from './status/index.js';
17
18
  export * from './storage/index.js';
18
19
  export * from './submission/index.js';
19
20
  export * from './util/index.js';
@@ -7,111 +7,99 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
- import { DiagramModule } from '@eclipse-glsp/server';
10
+ import {
11
+ type ActionDispatcher,
12
+ type ActionHandlerConstructor,
13
+ applyBindingTarget,
14
+ type BindingTarget,
15
+ type ClientSessionInitializer,
16
+ type CommandStack,
17
+ DiagramModule,
18
+ type InstanceMultiBinding,
19
+ type MultiBinding,
20
+ OperationActionHandler,
21
+ RequestModelActionHandler,
22
+ SetEditModeActionHandler,
23
+ UndoRedoActionHandler
24
+ } from '@eclipse-glsp/server';
11
25
  import { injectable, type interfaces } from 'inversify';
12
- import type { LanguageMetaData } from '@hydranium/langium';
13
- import { type ServerLanguageServices, type ServerSharedServices, typedMetadata } from '@hydranium/core';
14
- import { HydraniumTypes } from '../state/hydranium-shared-core-services.js';
26
+ import { HydraniumGlspServerActionDispatcher } from '../dispatcher/server-action-dispatcher.js';
27
+ import { HydraniumGlspRequestSaveModelActionHandler } from '../storage/hydranium-glsp-request-save-model-action-handler.js';
28
+ import { HydraniumGlspCommandStack } from '../command/hydranium-glsp-command-stack.js';
29
+ import { HydraniumGlspOperationActionHandler } from '../command/hydranium-glsp-operation-action-handler.js';
30
+ import { HydraniumGlspUndoRedoActionHandler } from '../command/hydranium-glsp-undo-redo-action-handler.js';
31
+ import { DefaultDiagramStatusReporter, DiagramStatusReporter } from '../status/diagram-status-reporter.js';
32
+ import { HydraniumGlspRequestModelActionHandler } from '../status/hydranium-glsp-request-model-action-handler.js';
33
+ import { HydraniumGlspSetEditModeActionHandler } from '../status/hydranium-glsp-set-edit-mode-action-handler.js';
15
34
 
16
35
  /**
17
- * Bind {@link HydraniumTypes}.DiagramLanguage on a GLSP **session** container
18
- * to the services of `metadata`'s grammar.
36
+ * GLSP {@link DiagramModule} base that wires the framework's diagram status,
37
+ * dispatch timing, command stack, save, and transactional operation, undo and
38
+ * redo handlers.
19
39
  *
20
- * Exported separately from {@link AbstractHydraniumGlspDiagramModule} for adopters
21
- * whose diagram module already extends an intermediate base of their own and
22
- * cannot take the framework base class; call it from `configure` after
23
- * `super.configure(...)`. The base class is the preferred entry point because
24
- * it makes the declaration non-optional.
40
+ * A diagram module declares no grammar: a session's language is the one its
41
+ * loaded document routes to, so one diagram type can serve several grammars.
42
+ * Adopter GLSP components read `modelState.diagramLanguage` for a reference
43
+ * written on the canvas and `modelState.languageServicesFor(node)` for
44
+ * anything reached through a reference; see `HydraniumTypes` for why no
45
+ * per-language service has a token of its own.
25
46
  *
26
- * **Why the session tier.** GLSP builds its app container once per process,
27
- * before any document exists, so a per-language service bound there can only
28
- * ever be one grammar's. The session container is created per open diagram
29
- * with its `diagramType` known, and a diagram type has exactly one grammar —
30
- * so the language is a static fact about the module, resolvable with no URI
31
- * and no ordering hazard. That last part is load-bearing: GLSP's
32
- * `OperationHandlerRegistryInitializer` constructs every operation handler at
33
- * `InitializeClientSession`, i.e. BEFORE `RequestModelAction` supplies the
34
- * source URI, so anything derived from `ModelState.sourceUri` at injection
35
- * time would throw on every diagram open.
36
- *
37
- * **The whole language, not pre-resolved providers.** There is deliberately no
38
- * token for an individual per-language provider; see {@link HydraniumTypes} for
39
- * why. Callers reach `modelState.diagramLanguage` or
40
- * `modelState.languageServicesFor(node)` instead, which name the choice.
41
- *
42
- * Resolution is `toDynamicValue` + `inSingletonScope` — deferred so the parent
43
- * container's `SharedCoreServices` is available, then resolved once per
44
- * session rather than per injection.
45
- */
46
- export function bindDiagramLanguage(bind: interfaces.Bind, metadata: LanguageMetaData): void {
47
- bind(HydraniumTypes.DiagramLanguage)
48
- .toDynamicValue(context => resolveDiagramLanguage(context.container, metadata))
49
- .inSingletonScope();
50
- }
51
-
52
- /**
53
- * The registered services for `metadata`'s language, or a throw naming both
54
- * the declared id and the registered ones.
55
- *
56
- * Failing loudly is the point: the declared language is the one fact this
57
- * module states that the grammar does not, so it can drift. An unregistered id
58
- * means the adopter declared a language they never registered — a wiring
59
- * mistake whose silent form (falling back to some other grammar's providers)
60
- * would surface much later as references that resolve against the wrong scope.
61
- */
62
- function resolveDiagramLanguage(container: interfaces.Container, metadata: LanguageMetaData): ServerLanguageServices {
63
- const registry = container.get<ServerSharedServices>(HydraniumTypes.SharedCoreServices).ServiceRegistry;
64
- const language = registry.getServices(typedMetadata<ServerLanguageServices>(metadata));
65
- if (!language) {
66
- const registered = registry.all.map(candidate => candidate.LanguageMetaData.languageId);
67
- throw new Error(
68
- `Diagram module declares language '${metadata.languageId}', which is not registered on the ServiceRegistry. ` +
69
- `Registered languages: ${registered.length > 0 ? registered.join(', ') : '(none)'}. ` +
70
- 'Declare the languageId of a grammar passed to bootstrapLangiumLanguages.'
71
- );
72
- }
73
- return language;
74
- }
75
-
76
- /**
77
- * GLSP {@link DiagramModule} base that declares which grammar its diagram type
78
- * edits, and binds that language's per-language services on the session
79
- * container.
80
- *
81
- * Subclass instead of `DiagramModule` and implement {@link declareLanguage}
82
- * alongside `diagramType`; the two are the same kind of fact about a diagram
83
- * type, and pairing them is what lets one GLSP head serve N grammars.
84
- *
85
- * Adopter GLSP components then read the language they mean:
86
- * `modelState.diagramLanguage.references.CandidateProvider` for a reference
87
- * written on the canvas, `modelState.languageServicesFor(node).references.X`
88
- * for anything reached through a reference. No per-language service is bound
89
- * to a token of its own — see {@link bindDiagramLanguage} for why.
47
+ * An adopter whose diagram module already extends a base of its own repeats
48
+ * what this class's overrides do. Without the status bindings among them,
49
+ * nothing tells the client why its diagram is read-only; without the operation
50
+ * and undo handlers, operation handlers edit the built root every reader
51
+ * shares.
90
52
  */
91
53
  @injectable()
92
54
  export abstract class AbstractHydraniumGlspDiagramModule extends DiagramModule {
93
- /**
94
- * The `LanguageMetaData` of the grammar this diagram type edits — pass the
95
- * generated `<Grammar>LanguageMetaData` constant directly.
96
- *
97
- * Abstract rather than optional so a new diagram module cannot silently
98
- * omit it: a missing declaration is a compile error here, where the fix is
99
- * obvious, instead of an unbound-token failure at the first diagram open.
100
- *
101
- * NOT named `bindLanguage`: every `bindX()` on GLSP's `DiagramModule`
102
- * returns a `BindingTarget<T>` that GLSP's own `configure` consumes, and a
103
- * method in that family returning a `LanguageMetaData` invites
104
- * `{ service: … }`. This one states a fact; the framework does the binding.
105
- */
106
- protected abstract declareLanguage(): LanguageMetaData;
107
-
108
55
  protected override configure(
109
56
  bind: interfaces.Bind,
110
57
  unbind: interfaces.Unbind,
111
58
  isBound: interfaces.IsBound,
112
59
  rebind: interfaces.Rebind
113
60
  ): void {
61
+ // Bound first: `super.configure` applies the session initializers, and the
62
+ // reporter's initializer entry refers to this binding.
63
+ applyBindingTarget({ bind, isBound }, DiagramStatusReporter, this.bindDiagramStatusReporter()).inSingletonScope();
114
64
  super.configure(bind, unbind, isBound, rebind);
115
- bindDiagramLanguage(bind, this.declareLanguage());
65
+ }
66
+
67
+ /** The {@link DiagramStatusReporter} of the session; {@link DefaultDiagramStatusReporter} by default. */
68
+ protected bindDiagramStatusReporter(): BindingTarget<DiagramStatusReporter> {
69
+ return DefaultDiagramStatusReporter;
70
+ }
71
+
72
+ /** Starts the {@link DiagramStatusReporter} with the session, since nothing else injects it. */
73
+ override configureClientSessionInitializers(binding: MultiBinding<ClientSessionInitializer>): void {
74
+ super.configureClientSessionInitializers(binding);
75
+ binding.add({ service: DiagramStatusReporter });
76
+ }
77
+
78
+ /** {@link HydraniumGlspServerActionDispatcher}, so every dispatch is timed once logging is at debug. */
79
+ protected override bindActionDispatcher(): BindingTarget<ActionDispatcher> {
80
+ return HydraniumGlspServerActionDispatcher;
81
+ }
82
+
83
+ /** {@link HydraniumGlspCommandStack}, so the diagram is dirty exactly while a document it has open is. */
84
+ protected override bindCommandStack(): BindingTarget<CommandStack> {
85
+ return HydraniumGlspCommandStack;
86
+ }
87
+
88
+ /**
89
+ * Adds {@link HydraniumGlspRequestSaveModelActionHandler}, the save the
90
+ * Theia client sends, beside GLSP's own save handler; replaces the GLSP
91
+ * handlers that write the client's status or edit mode with ones that set a
92
+ * diagram status instead; and runs operations, undo and redo through
93
+ * {@link HydraniumGlspOperationActionHandler} and
94
+ * {@link HydraniumGlspUndoRedoActionHandler}, without which handlers edit
95
+ * the built root every reader shares.
96
+ */
97
+ protected override configureActionHandlers(binding: InstanceMultiBinding<ActionHandlerConstructor>): void {
98
+ super.configureActionHandlers(binding);
99
+ binding.add(HydraniumGlspRequestSaveModelActionHandler);
100
+ binding.rebind(RequestModelActionHandler, HydraniumGlspRequestModelActionHandler);
101
+ binding.rebind(SetEditModeActionHandler, HydraniumGlspSetEditModeActionHandler);
102
+ binding.rebind(OperationActionHandler, HydraniumGlspOperationActionHandler);
103
+ binding.rebind(UndoRedoActionHandler, HydraniumGlspUndoRedoActionHandler);
116
104
  }
117
105
  }
@@ -0,0 +1,47 @@
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 { isClientGoneError } from '@hydranium/core';
12
+
13
+ /**
14
+ * `connection` with a notification to a client that has gone dropped, logged
15
+ * at debug, rather than left to throw or reject. GLSP sends every action as a
16
+ * notification and drops the promise, so a client that disconnects mid-write
17
+ * (an `EPIPE`) or a send after GLSP disposed the connection is otherwise an
18
+ * unhandled rejection on every ordinary close. Any other failure still throws
19
+ * or rejects: {@link isClientGoneError} tells the two apart, as on the
20
+ * LSP head. A launcher that overrides `createConnection` applies it, as the
21
+ * framework's own do.
22
+ */
23
+ export function dropNotificationsToGoneClient<T extends { sendNotification(method: string, ...params: unknown[]): Promise<void> }>(
24
+ connection: T,
25
+ logger: GlspLogger
26
+ ): T {
27
+ const dropIfGone = (error: unknown): void => {
28
+ if (!isClientGoneError(error)) {
29
+ throw error;
30
+ }
31
+ logger.debug(`Dropped a notification to a client that has gone: ${String(error)}`);
32
+ };
33
+ const sendNotification = (method: string, ...params: unknown[]): Promise<void> => {
34
+ try {
35
+ return connection.sendNotification(method, ...params).catch(dropIfGone);
36
+ } catch (error: unknown) {
37
+ // A disposed connection throws rather than rejecting.
38
+ dropIfGone(error);
39
+ return Promise.resolve();
40
+ }
41
+ };
42
+ return new Proxy(connection, {
43
+ get(target, property, receiver) {
44
+ return property === 'sendNotification' ? sendNotification : Reflect.get(target, property, receiver);
45
+ }
46
+ });
47
+ }
@@ -7,8 +7,7 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
- import { GLSPModule } from '@eclipse-glsp/server';
11
- import type { BindingContext } from '@eclipse-glsp/protocol/lib/di/inversify-util.js';
10
+ import { type BindingContext, GLSPModule } from '@eclipse-glsp/server';
12
11
  import type { ServerSharedServices } from '@hydranium/core';
13
12
  import { type ConflictResolver, ReconcilingConflictResolver } from '@hydranium/protocol';
14
13
  import { injectable, type interfaces } from 'inversify';