@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
@@ -7,5 +7,6 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
+ export * from './hydranium-glsp-request-save-model-action-handler.js';
10
11
  export * from './hydranium-glsp-storage.js';
11
- export * from './save-conflict-policy.js';
12
+ export * from './save-delivery-policy.js';
@@ -0,0 +1,49 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+
10
+ /**
11
+ * How `HydraniumGlspStorage.saveSourceModel` delivers its result: whether the
12
+ * GLSP save action awaits the write, and what happens on failure.
13
+ *
14
+ * The two are not independent — fire-and-forget plus rethrow would leave an
15
+ * unhandled rejection — so each arm bundles them into one coherent, nameable
16
+ * policy an adopter selects whole. It is a *selection of configuration*,
17
+ * delivered as a bound option (see the {@link SaveDeliveryPolicy} symbol) rather
18
+ * than a behaviour hook.
19
+ *
20
+ * **Neither arm guards on a base version.** A save flushes the store's
21
+ * settled text rather than authoring from the diagram's captured model, and the
22
+ * store already holds every other client's change — so a guard here refuses a
23
+ * write whose content is already correct. The gate that does exist sits on the
24
+ * update path, per operation, where the diagram authors.
25
+ */
26
+ export type SaveDeliveryPolicy =
27
+ /**
28
+ * Await the write and the language client's settle, and propagate any
29
+ * failure to the GLSP save action. The default. A language client that
30
+ * does not answer holds the first such save, and the diagram's queued
31
+ * actions, for the settle bound; saves after it do not wait for it again.
32
+ */
33
+ | { kind: 'await' }
34
+ /**
35
+ * Fire-and-forget, logging and swallowing every failure. Correct when a
36
+ * failed diagram save must neither block the action nor surface as an error.
37
+ */
38
+ | { kind: 'fire-and-forget' };
39
+
40
+ /** The policy applied when no {@link SaveDeliveryPolicy} option is bound. */
41
+ export const DEFAULT_SAVE_DELIVERY_POLICY: SaveDeliveryPolicy = { kind: 'await' };
42
+
43
+ /**
44
+ * DI token for the {@link SaveDeliveryPolicy} option. Bind a constant value in a
45
+ * `DiagramModule` to select a non-default policy; left unbound,
46
+ * `HydraniumGlspStorage` falls back to {@link DEFAULT_SAVE_DELIVERY_POLICY}.
47
+ * Shares its name with the type, the way a `class` is both a value and a type.
48
+ */
49
+ export const SaveDeliveryPolicy = Symbol('SaveDeliveryPolicy');
@@ -12,15 +12,20 @@ import {
12
12
  type DirtyStateChangeReason,
13
13
  type GModelRootSchema,
14
14
  type LayoutOperation,
15
+ MarkersReason,
15
16
  ModelState,
16
17
  ModelSubmissionHandler,
18
+ type ModelValidator,
17
19
  SetDirtyStateAction,
20
+ SetMarkersAction,
18
21
  type SetModelAction
19
22
  } from '@eclipse-glsp/server';
20
23
  import { IntegrityService } from '@hydranium/core';
21
24
  import { inject, injectable } from 'inversify';
22
25
  import { type AstNode, type DocumentState } from '@hydranium/langium';
26
+ import { describeErrorWithStack } from '@hydranium/protocol';
23
27
  import { type AbstractHydraniumGlspState } from '../state/abstract-hydranium-glsp-state.js';
28
+ import { DiagramStatus } from '../state/diagram-status.js';
24
29
 
25
30
  /**
26
31
  * GLSP {@link ModelSubmissionHandler} base shared by all hydranium adopters.
@@ -79,6 +84,8 @@ export class HydraniumGlspSubmissionHandler<TRoot extends AstNode, TSourceModel
79
84
  }
80
85
 
81
86
  override async submitModel(reason?: DirtyStateChangeReason, layout?: LayoutOperation): Promise<Action[]> {
87
+ // Numbered before the first await, so a caller can predict it.
88
+ const submission = ++this._startedSubmissions;
82
89
  if (this.readyEvent !== undefined) {
83
90
  // Adopters with on-build integrity rules wait so the GModel-factory walk sees
84
91
  // a settled AST; otherwise the GModel could include elements the build pass is
@@ -86,8 +93,15 @@ export class HydraniumGlspSubmissionHandler<TRoot extends AstNode, TSourceModel
86
93
  // via onReadyRefreshed.
87
94
  await this.modelState.ready(this.readyEvent);
88
95
  }
89
- const actions = await super.submitModel(reason, layout);
96
+ const submitted = super.submitModel(reason, layout);
97
+ // Read before awaiting: GLSP bumps the revision synchronously, and another
98
+ // submission may run once this one yields.
99
+ const revision = this.modelState.root?.revision;
100
+ const actions = await submitted;
101
+ this.previousSubmission = { signature: this._lastSubmittedSignature, revision: this.lastSubmittedRevision };
90
102
  this._lastSubmittedSignature = this.signatureOf(actions);
103
+ this.lastSubmittedRevision = revision;
104
+ this.lastCompletedSubmission = submission;
91
105
  this.logSubmit(reason, actions);
92
106
  return actions;
93
107
  }
@@ -107,6 +121,78 @@ export class HydraniumGlspSubmissionHandler<TRoot extends AstNode, TSourceModel
107
121
 
108
122
  protected _lastSubmittedSignature?: string;
109
123
 
124
+ /** GModel revision of the last submission, whatever its reason. */
125
+ protected lastSubmittedRevision?: number;
126
+
127
+ /**
128
+ * How many submissions have started, whatever their reason. A caller that
129
+ * may drop the submission it is about to start names it to
130
+ * {@link withdrawLastSubmission} as this count plus one.
131
+ */
132
+ get startedSubmissions(): number {
133
+ return this._startedSubmissions;
134
+ }
135
+
136
+ protected _startedSubmissions = 0;
137
+
138
+ /** The number of the submission that completed last. */
139
+ protected lastCompletedSubmission?: number;
140
+
141
+ /** What {@link withdrawLastSubmission} restores. */
142
+ protected previousSubmission?: { readonly signature?: string; readonly revision?: number };
143
+
144
+ /**
145
+ * Take back the latest submission, which the caller drops unsent because it
146
+ * matches the one before. `submission` is its number, the
147
+ * {@link startedSubmissions} count when it started. GLSP bumped the GModel
148
+ * revision for it; left bumped, the client's measured bounds for the graph
149
+ * it already has arrive stale and its update is never sent. A no-op once
150
+ * another submission has finished or rebuilt the root since, which the
151
+ * client is given instead.
152
+ *
153
+ * An override of {@link submitModel} may return its own array. If it awaits
154
+ * before calling `super.submitModel`, another submission can take the number
155
+ * first, and the withdraw is then a no-op.
156
+ */
157
+ withdrawLastSubmission(submission: number): void {
158
+ const previous = this.previousSubmission;
159
+ if (!previous || submission !== this.lastCompletedSubmission || this.modelState.root?.revision !== this.lastSubmittedRevision) {
160
+ return;
161
+ }
162
+ this.previousSubmission = undefined;
163
+ this._lastSubmittedSignature = previous.signature;
164
+ this.lastSubmittedRevision = previous.revision;
165
+ const root = this.modelState.root;
166
+ if (root && previous.revision !== undefined) {
167
+ root.revision = previous.revision;
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Live validation as the {@link DiagramStatus.VALIDATION} status, rather than
173
+ * GLSP's own status writes, whose closing clear erases any status still in
174
+ * force, such as the reason a diagram is read-only.
175
+ *
176
+ * Never rejects: GLSP runs it from a debounce that drops the result, so a
177
+ * failure here would go unhandled. A validator that throws is logged as an
178
+ * error, markers that cannot be sent as a warning.
179
+ */
180
+ protected override async performLiveValidation(validator: ModelValidator): Promise<void> {
181
+ this.modelState.setStatus(DiagramStatus.VALIDATION, { message: 'Validate Model...', severity: 'INFO' });
182
+ try {
183
+ const markers = await validator.validate([this.modelState.root], MarkersReason.LIVE);
184
+ await this.actionDispatcher
185
+ .dispatch(SetMarkersAction.create(markers, { reason: MarkersReason.LIVE }))
186
+ .catch((error: unknown) =>
187
+ this.modelState.logger.warn(`Could not send the live-validation markers: ${describeErrorWithStack(error)}`)
188
+ );
189
+ } catch (error: unknown) {
190
+ this.modelState.logger.error(`Live validation failed: ${describeErrorWithStack(error)}`);
191
+ } finally {
192
+ this.modelState.setStatus(DiagramStatus.VALIDATION, undefined);
193
+ }
194
+ }
195
+
110
196
  /**
111
197
  * Serialise the model-bearing actions of a submission into a comparable
112
198
  * signature.
@@ -134,9 +220,9 @@ export class HydraniumGlspSubmissionHandler<TRoot extends AstNode, TSourceModel
134
220
  }
135
221
 
136
222
  /**
137
- * Logs the {@link SetModelAction} dispatch so the initial
138
- * `requestModel → setModel` handshake is observable in the server log
139
- * alongside `Submit model` and `ComputedBounds accepted`.
223
+ * Logs the {@link SetModelAction} dispatch, so a diagram's initial
224
+ * `requestModel → setModel` handshake is a milestone in the server log; the
225
+ * submit and computed-bounds lines around it are diagnosis.
140
226
  */
141
227
  protected override createSetModeAction(newRoot: GModelRootSchema): SetModelAction {
142
228
  const action = super.createSetModeAction(newRoot);
@@ -151,7 +237,7 @@ export class HydraniumGlspSubmissionHandler<TRoot extends AstNode, TSourceModel
151
237
  const gChildren = root?.children?.length ?? 0;
152
238
  const sourceRoot = this.modelState.sourceRoot;
153
239
  const kinds = actions.map(a => a.kind).join(',');
154
- this.modelState.logger.info(
240
+ this.modelState.logger.debug(
155
241
  `Submit model [reason=${reason ?? 'initial'}, actions=[${kinds}], ` +
156
242
  `gmodel={type=${root?.type ?? 'none'}, children=${gChildren}}, ` +
157
243
  `ast={${this.formatSourceRoot(sourceRoot)}}]`
@@ -10,16 +10,20 @@
10
10
  import 'reflect-metadata';
11
11
  import {
12
12
  type Action,
13
+ ActionDispatchScope,
13
14
  type ActionMessage,
14
15
  ClientSessionManager,
15
16
  EndProgressAction,
16
17
  GLSPClientProxy,
17
18
  GLSPServer,
19
+ MessageAction,
18
20
  ModelState,
21
+ RejectAction,
19
22
  RequestBoundsAction,
20
23
  RequestModelAction,
21
24
  SOURCE_URI_ARG,
22
25
  SetDirtyStateAction,
26
+ SetMarkersAction,
23
27
  SetModelAction,
24
28
  StartProgressAction,
25
29
  StatusAction,
@@ -35,7 +39,6 @@ import {
35
39
  type ServerModule,
36
40
  getRequestParentName
37
41
  } from '@eclipse-glsp/server/node.js';
38
- import { ActionDispatchScope } from '@eclipse-glsp/server';
39
42
  import { Container, ContainerModule, type interfaces } from 'inversify';
40
43
  import type { AstNode } from '@hydranium/langium';
41
44
  import type { ServerSharedServices } from '@hydranium/core';
@@ -87,33 +90,49 @@ export interface MakeGlspHarnessOptions {
87
90
  readonly clientSessionId?: string;
88
91
  /**
89
92
  * Adopter-specific client action kinds to forward IN ADDITION to
90
- * {@link DEFAULT_CLIENT_ACTION_KINDS}. The server's
93
+ * {@link MINIMAL_CLIENT_ACTION_KINDS}. The server's
91
94
  * `ClientActionForwarder` only forwards declared kinds to the capturing
92
95
  * `GLSPClientProxy`, so list any custom server→client action a test needs
93
- * to observe. The standard set covers ordinary round-trips, so most tests
96
+ * to observe. The minimal set covers ordinary round-trips, so most tests
94
97
  * leave this unset.
95
98
  */
96
99
  readonly additionalClientActionKinds?: ReadonlyArray<string>;
97
100
  }
98
101
 
99
102
  /**
100
- * The standard client-bound action kinds a real GLSP client handles — the
101
- * minimal set needed for any model round-trip. The harness declares these on
102
- * the session by default so the capturing {@link GLSPClientProxy} receives
103
- * them and the action dispatcher never errors with "no handler registered"
104
- * for an ordinary server→client action. Adopter-specific kinds are added via
103
+ * The fewest client-bound action kinds a session can declare and still finish
104
+ * a model round-trip without the server's dispatcher erroring with "no handler
105
+ * registered" for an ordinary server→client action.
106
+ *
107
+ * A real GLSP client declares every kind its own handler registry holds, which
108
+ * is far more than this, and GLSP ships no fixed list to declare instead. So a
109
+ * test session that leaves one of these out sees errors no deployed client
110
+ * would, or none at all: a dispatch the server does not await, such as the
111
+ * markers after a load, fails without telling the client. The harness declares
112
+ * them by default; adopter-specific kinds are added via
105
113
  * {@link MakeGlspHarnessOptions.additionalClientActionKinds}.
106
114
  */
107
- const DEFAULT_CLIENT_ACTION_KINDS: ReadonlyArray<string> = [
115
+ export const MINIMAL_CLIENT_ACTION_KINDS: ReadonlyArray<string> = Object.freeze([
108
116
  SetModelAction.KIND,
109
117
  UpdateModelAction.KIND,
110
118
  RequestBoundsAction.KIND,
111
119
  SetDirtyStateAction.KIND,
112
120
  StatusAction.KIND,
121
+ MessageAction.KIND,
122
+ SetMarkersAction.KIND,
113
123
  StartProgressAction.KIND,
114
124
  UpdateProgressAction.KIND,
115
125
  EndProgressAction.KIND
116
- ];
126
+ ]);
127
+
128
+ /**
129
+ * Whether the server reported a failure to the client: a rejected request, or
130
+ * an ERROR or FATAL message. A server can fail a request and still send the
131
+ * model, so a test that sees the model has not yet seen the load succeed.
132
+ */
133
+ export function isRejectionOrError(action: Action): boolean {
134
+ return RejectAction.is(action) || (MessageAction.is(action) && (action.severity === 'ERROR' || action.severity === 'FATAL'));
135
+ }
117
136
 
118
137
  /** Options for {@link GlspHarness.nextModelSubmission}. */
119
138
  export interface NextModelSubmissionOptions {
@@ -156,10 +175,11 @@ export interface GlspHarness<TState extends AbstractHydraniumGlspState<AstNode,
156
175
  /** Send an action to the server as `{ clientId, action }`. Fire-and-forget (GLSP `process` is `void`). */
157
176
  dispatch(action: Action): void;
158
177
  /**
159
- * Resolve with the next captured action whose `kind` matches — an
160
- * already-captured-but-unconsumed match resolves immediately, otherwise
161
- * waits for the next arrival. Rejects after `timeoutMs` (default 2000) so
162
- * a missing action fails fast instead of hanging.
178
+ * Resolve with the next action whose `kind` matches and that arrived after
179
+ * the last {@link dispatch}, or since {@link start} before any. One already
180
+ * captured and unconsumed resolves immediately, otherwise the next arrival
181
+ * wins. An earlier action is read from {@link actions}. Rejects after
182
+ * `timeoutMs` (default 2000) so a missing action fails fast.
163
183
  */
164
184
  nextAction<T extends Action = Action>(kind: string, timeoutMs?: number): Promise<T>;
165
185
  /**
@@ -179,7 +199,8 @@ export interface GlspHarness<TState extends AbstractHydraniumGlspState<AstNode,
179
199
  openDocument(sourceUri: string, timeoutMs?: number): Promise<Action>;
180
200
  /**
181
201
  * Resolve with the next action that publishes a model — the settling point
182
- * after an operation, since a successful operation re-submits.
202
+ * after an operation, since a successful operation re-submits. Matches as
203
+ * {@link nextAction} does, only after the last dispatch.
183
204
  *
184
205
  * Kind-agnostic for the same reason as {@link openDocument}. Set
185
206
  * `rejectOnTimeout: false` to ask whether a submission happened *at all*.
@@ -189,6 +210,11 @@ export interface GlspHarness<TState extends AbstractHydraniumGlspState<AstNode,
189
210
  * LIGHT fidelity: seed the state's source root directly via
190
211
  * `setSourceRoot`, bypassing source-model storage. Valid only after
191
212
  * {@link start}.
213
+ *
214
+ * A test of a provider that reads `builtNodeOf` or `referenceTo` outside
215
+ * an operation seeds the real state here rather than stub them. They answer as in production
216
+ * for a root with a `$document` in a project; without one, `referenceTo`
217
+ * names a target as in no project, unchecked for visibility.
192
218
  */
193
219
  seedSourceRoot(uri: string, root: Parameters<TState['setSourceRoot']>[1]): void;
194
220
 
@@ -235,11 +261,13 @@ export function makeGlspHarness<TState extends AbstractHydraniumGlspState<AstNod
235
261
  ): GlspHarness<TState> {
236
262
  const applicationId = options.applicationId ?? 'test-app';
237
263
  const clientSessionId = options.clientSessionId ?? 'test-session';
238
- const clientActionKinds = [...DEFAULT_CLIENT_ACTION_KINDS, ...(options.additionalClientActionKinds ?? [])];
264
+ const clientActionKinds = [...MINIMAL_CLIENT_ACTION_KINDS, ...(options.additionalClientActionKinds ?? [])];
239
265
  const createLogger = options.createLogger ?? (() => makeNoopGlspLogger());
240
266
 
241
267
  const actions: Action[] = [];
242
268
  const consumed = new WeakSet<Action>();
269
+ // Where the last dispatch fell in `actions`; a wait matches only from here.
270
+ let dispatchedAt = 0;
243
271
  const waiters: PendingWaiter[] = [];
244
272
 
245
273
  const captureProxy: GLSPClientProxy = {
@@ -257,8 +285,9 @@ export function makeGlspHarness<TState extends AbstractHydraniumGlspState<AstNod
257
285
  };
258
286
 
259
287
  /**
260
- * Resolve with the first unconsumed action matching any of `kinds` — one
261
- * already captured resolves immediately, otherwise the next arrival wins.
288
+ * Resolve with the first unconsumed action matching any of `kinds` since the
289
+ * last dispatch — one already captured resolves immediately, otherwise the
290
+ * next arrival wins.
262
291
  *
263
292
  * `rejectOnTimeout` is what lets a caller distinguish the two questions a
264
293
  * test asks. `true` (the default) is "this must happen", and a timeout is a
@@ -268,7 +297,7 @@ export function makeGlspHarness<TState extends AbstractHydraniumGlspState<AstNod
268
297
  * `ActionDispatcher.requestUntil(action, timeoutMs, rejectOnTimeout)`.
269
298
  */
270
299
  function waitFor(kinds: ReadonlyArray<string>, timeoutMs: number, rejectOnTimeout: boolean): Promise<Action | undefined> {
271
- const existing = actions.find(action => kinds.includes(action.kind) && !consumed.has(action));
300
+ const existing = actions.find((action, index) => index >= dispatchedAt && kinds.includes(action.kind) && !consumed.has(action));
272
301
  if (existing) {
273
302
  consumed.add(existing);
274
303
  return Promise.resolve(existing);
@@ -290,8 +319,19 @@ export function makeGlspHarness<TState extends AbstractHydraniumGlspState<AstNod
290
319
  // Distinguishing "nothing happened" from "something else
291
320
  // happened" is the difference between a five-minute hunt and a
292
321
  // one-line diagnosis.
293
- const seen = actions.map(captured => captured.kind);
294
- const context = seen.length === 0 ? 'no actions were captured at all' : `captured since start: ${seen.join(', ')}`;
322
+ // Split at the last dispatch, since a wait matches only what
323
+ // came after it: an earlier match is no answer to this one.
324
+ const kindsIn = (from: number, to?: number): string =>
325
+ actions
326
+ .slice(from, to)
327
+ .map(captured => captured.kind)
328
+ .join(', ') || 'nothing';
329
+ const context =
330
+ actions.length === 0
331
+ ? 'no actions were captured at all'
332
+ : dispatchedAt === 0
333
+ ? `captured since start: ${kindsIn(0)}`
334
+ : `captured before the last dispatch, which a wait does not match: ${kindsIn(0, dispatchedAt)}; since: ${kindsIn(dispatchedAt)}`;
295
335
  reject(
296
336
  new Error(
297
337
  `makeGlspHarness: no ${kinds.map(kind => `'${kind}'`).join(' / ')} action within ${timeoutMs}ms — ${context}`
@@ -328,10 +368,10 @@ export function makeGlspHarness<TState extends AbstractHydraniumGlspState<AstNod
328
368
  const container = new Container();
329
369
  container.load(defaultAppModule, ...(options.appModules ?? []), captureProxyModule);
330
370
  container.load(options.serverModule);
331
- // After the serverModule, which binds `GLSPServer`: the override rebinds that
332
- // symbol, so it needs the binding to exist. The launchers reach the same tier
333
- // by passing this to `configure` — a harness that skipped it would answer
334
- // request failures differently from every real bringup.
371
+ // Into the container the serverModule binds `GLSPServer` in, as the
372
+ // launchers' `configure` loads it: a harness that skipped it would keep
373
+ // upstream's server and answer request failures differently from every
374
+ // real bringup.
335
375
  container.load(createGlspServerOverrides());
336
376
  const server = container.get<GLSPServer>(GLSPServer);
337
377
 
@@ -378,6 +418,7 @@ export function makeGlspHarness<TState extends AbstractHydraniumGlspState<AstNod
378
418
  },
379
419
 
380
420
  dispatch(action: Action): void {
421
+ dispatchedAt = actions.length;
381
422
  server.process({ clientId: clientSessionId, action });
382
423
  },
383
424
 
@@ -389,6 +430,7 @@ export function makeGlspHarness<TState extends AbstractHydraniumGlspState<AstNod
389
430
  if (!state) {
390
431
  throw new Error('makeGlspHarness: openDocument() is only valid after start()');
391
432
  }
433
+ dispatchedAt = actions.length;
392
434
  server.process({
393
435
  clientId: clientSessionId,
394
436
  action: RequestModelAction.create({ options: { [SOURCE_URI_ARG]: sourceUri } })
@@ -7,10 +7,10 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
- import { type Marker, MarkerKind } from '@eclipse-glsp/protocol';
10
+ import { type Marker, MarkerKind } from '@eclipse-glsp/server';
11
11
  import { type AstNode } from '@hydranium/langium';
12
12
  import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-types';
13
- import { type TransferLspDiagnostic } from '@hydranium/core';
13
+ import { type AstDiagnostic } from '@hydranium/core';
14
14
 
15
15
  /**
16
16
  * The model-shaped lookups the diagnostic→marker translation needs, injected so
@@ -48,7 +48,7 @@ export interface DiagnosticMarkerLookups {
48
48
  * `MarkersReason` so a fresh `SetMarkersAction` replaces the previous markers
49
49
  * (an empty result clears them when all errors are fixed).
50
50
  */
51
- export function diagnosticsToMarkers(diagnostics: readonly TransferLspDiagnostic[], lookups: DiagnosticMarkerLookups): Marker[] {
51
+ export function diagnosticsToMarkers(diagnostics: readonly AstDiagnostic[], lookups: DiagnosticMarkerLookups): Marker[] {
52
52
  const markers: Marker[] = [];
53
53
  for (const diagnostic of diagnostics) {
54
54
  const kind = markerKind(diagnostic.severity);
@@ -7,12 +7,11 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
- import { type Marker } from '@eclipse-glsp/protocol';
11
- import { type GModelElement, ModelState, type ModelValidator } from '@eclipse-glsp/server';
10
+ import { type GModelElement, type Marker, ModelState, type ModelValidator } from '@eclipse-glsp/server';
12
11
  import { inject, injectable } from 'inversify';
13
12
  import { type AstNode } from '@hydranium/langium';
14
13
  import { URI } from '@hydranium/langium';
15
- import { type ServerSharedServices, type TransferLspDiagnostic } from '@hydranium/core';
14
+ import { type ServerSharedServices, type AstDiagnostic } from '@hydranium/core';
16
15
  import { type AbstractHydraniumGlspState } from '../state/abstract-hydranium-glsp-state.js';
17
16
  import { HydraniumTypes } from '../state/hydranium-shared-core-services.js';
18
17
  import { type DiagnosticMarkerLookups, diagnosticsToMarkers } from './diagnostic-markers.js';
@@ -75,9 +74,9 @@ export class HydraniumGlspModelValidator<TRoot extends AstNode = AstNode> implem
75
74
  continue;
76
75
  }
77
76
  // Diagnostics are produced by the framework's `HydraniumDocumentValidator`,
78
- // which emits `TransferLspDiagnostic` (LSP `Diagnostic` + `element` path).
77
+ // which emits `AstDiagnostic` (LSP `Diagnostic` + `element` path).
79
78
  // Parser/lexer diagnostics carry no `element`; the lookup drops them.
80
- const diagnostics = document.diagnostics as TransferLspDiagnostic[] | undefined;
79
+ const diagnostics = document.diagnostics as AstDiagnostic[] | undefined;
81
80
  if (!diagnostics || diagnostics.length === 0) {
82
81
  continue;
83
82
  }
@@ -1,56 +0,0 @@
1
- /********************************************************************************
2
- * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the MIT License which is available in the project root.
6
- *
7
- * SPDX-License-Identifier: MIT
8
- ********************************************************************************/
9
- /**
10
- * How `HydraniumGlspStorage.saveSourceModel` reacts when a concurrent edit has
11
- * advanced the document past the version this diagram was last captured at.
12
- *
13
- * A save spans otherwise-independent choices — whether to guard on the captured
14
- * based-on version, whether the GLSP save action awaits the persist, and what
15
- * happens on failure. Each arm bundles those into one coherent, nameable policy
16
- * so an adopter selects a whole behaviour rather than assembling an incoherent
17
- * mix: fire-and-forget plus rethrow would leave an unhandled rejection. It is a
18
- * *selection of configuration*, delivered as a bound option (see the
19
- * {@link SaveConflictPolicy} symbol) rather than a behaviour hook.
20
- */
21
- export type SaveConflictPolicy =
22
- /**
23
- * Last-write-wins: no based-on guard, await the persist, propagate any
24
- * failure to the GLSP save action. The default; correct for a single-editor
25
- * head where nothing races the save.
26
- */
27
- {
28
- kind: 'overwrite';
29
- }
30
- /**
31
- * Guard on the captured version, await the persist, and surface a
32
- * `ConflictError` (and any other failure) to the GLSP save action.
33
- */
34
- | {
35
- kind: 'reject';
36
- }
37
- /**
38
- * Guard on the captured version, fire-and-forget, and log + swallow every
39
- * failure. Correct when another editor of the same document may already have
40
- * written the truth, so a stale diagram save is benign and must neither
41
- * block the action nor surface as an error.
42
- */
43
- | {
44
- kind: 'drop-and-log';
45
- };
46
- /** The policy applied when no {@link SaveConflictPolicy} option is bound. */
47
- export declare const DEFAULT_SAVE_CONFLICT_POLICY: SaveConflictPolicy;
48
- /**
49
- * DI token for the {@link SaveConflictPolicy} option. Bind a constant value in a
50
- * `DiagramModule` to select a non-default policy; left unbound,
51
- * `HydraniumGlspStorage` falls back to
52
- * {@link DEFAULT_SAVE_CONFLICT_POLICY}. Shares its name with the type, the way a
53
- * `class` is both a value and a type.
54
- */
55
- export declare const SaveConflictPolicy: unique symbol;
56
- //# sourceMappingURL=save-conflict-policy.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"save-conflict-policy.d.ts","sourceRoot":"","sources":["../../src/storage/save-conflict-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,kBAAkB;AAC3B;;;;GAIG;AACD;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE;AACvB;;;GAGG;GACD;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE;AACpB;;;;;GAKG;GACD;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,CAAC;AAE9B,6EAA6E;AAC7E,eAAO,MAAM,4BAA4B,EAAE,kBAA0C,CAAC;AAEtF;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,eAA+B,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"save-conflict-policy.js","sourceRoot":"","sources":["../../src/storage/save-conflict-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAkClF,6EAA6E;AAC7E,MAAM,CAAC,MAAM,4BAA4B,GAAuB,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AAEtF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC"}
@@ -1,52 +0,0 @@
1
- /********************************************************************************
2
- * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the MIT License which is available in the project root.
6
- *
7
- * SPDX-License-Identifier: MIT
8
- ********************************************************************************/
9
-
10
- /**
11
- * How `HydraniumGlspStorage.saveSourceModel` reacts when a concurrent edit has
12
- * advanced the document past the version this diagram was last captured at.
13
- *
14
- * A save spans otherwise-independent choices — whether to guard on the captured
15
- * based-on version, whether the GLSP save action awaits the persist, and what
16
- * happens on failure. Each arm bundles those into one coherent, nameable policy
17
- * so an adopter selects a whole behaviour rather than assembling an incoherent
18
- * mix: fire-and-forget plus rethrow would leave an unhandled rejection. It is a
19
- * *selection of configuration*, delivered as a bound option (see the
20
- * {@link SaveConflictPolicy} symbol) rather than a behaviour hook.
21
- */
22
- export type SaveConflictPolicy =
23
- /**
24
- * Last-write-wins: no based-on guard, await the persist, propagate any
25
- * failure to the GLSP save action. The default; correct for a single-editor
26
- * head where nothing races the save.
27
- */
28
- | { kind: 'overwrite' }
29
- /**
30
- * Guard on the captured version, await the persist, and surface a
31
- * `ConflictError` (and any other failure) to the GLSP save action.
32
- */
33
- | { kind: 'reject' }
34
- /**
35
- * Guard on the captured version, fire-and-forget, and log + swallow every
36
- * failure. Correct when another editor of the same document may already have
37
- * written the truth, so a stale diagram save is benign and must neither
38
- * block the action nor surface as an error.
39
- */
40
- | { kind: 'drop-and-log' };
41
-
42
- /** The policy applied when no {@link SaveConflictPolicy} option is bound. */
43
- export const DEFAULT_SAVE_CONFLICT_POLICY: SaveConflictPolicy = { kind: 'overwrite' };
44
-
45
- /**
46
- * DI token for the {@link SaveConflictPolicy} option. Bind a constant value in a
47
- * `DiagramModule` to select a non-default policy; left unbound,
48
- * `HydraniumGlspStorage` falls back to
49
- * {@link DEFAULT_SAVE_CONFLICT_POLICY}. Shares its name with the type, the way a
50
- * `class` is both a value and a type.
51
- */
52
- export const SaveConflictPolicy = Symbol('SaveConflictPolicy');