@hydranium/glsp-server 1.0.0-next.8 → 1.0.0-next.85

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 (68) hide show
  1. package/README.md +11 -2
  2. package/lib/command/hydranium-glsp-recording-command.d.ts +21 -21
  3. package/lib/command/hydranium-glsp-recording-command.d.ts.map +1 -1
  4. package/lib/command/hydranium-glsp-recording-command.js +24 -25
  5. package/lib/command/hydranium-glsp-recording-command.js.map +1 -1
  6. package/lib/launcher/hydranium-glsp-server.d.ts +4 -3
  7. package/lib/launcher/hydranium-glsp-server.d.ts.map +1 -1
  8. package/lib/launcher/hydranium-glsp-server.js +4 -3
  9. package/lib/launcher/hydranium-glsp-server.js.map +1 -1
  10. package/lib/messages/index.d.ts +24 -0
  11. package/lib/messages/index.d.ts.map +1 -0
  12. package/lib/messages/index.js +24 -0
  13. package/lib/messages/index.js.map +1 -0
  14. package/lib/state/abstract-hydranium-glsp-state.d.ts +65 -42
  15. package/lib/state/abstract-hydranium-glsp-state.d.ts.map +1 -1
  16. package/lib/state/abstract-hydranium-glsp-state.js +58 -41
  17. package/lib/state/abstract-hydranium-glsp-state.js.map +1 -1
  18. package/lib/state/full-text-hydranium-glsp-state.d.ts +3 -3
  19. package/lib/state/full-text-hydranium-glsp-state.d.ts.map +1 -1
  20. package/lib/state/full-text-hydranium-glsp-state.js +4 -3
  21. package/lib/state/full-text-hydranium-glsp-state.js.map +1 -1
  22. package/lib/state/reconcile-source-model-write.d.ts +7 -6
  23. package/lib/state/reconcile-source-model-write.d.ts.map +1 -1
  24. package/lib/state/reconcile-source-model-write.js +7 -7
  25. package/lib/state/reconcile-source-model-write.js.map +1 -1
  26. package/lib/state/reconciling-multi-document-glsp-state.d.ts +32 -12
  27. package/lib/state/reconciling-multi-document-glsp-state.d.ts.map +1 -1
  28. package/lib/state/reconciling-multi-document-glsp-state.js +44 -17
  29. package/lib/state/reconciling-multi-document-glsp-state.js.map +1 -1
  30. package/lib/state/reconciling-transfer-hydranium-glsp-state.d.ts +3 -3
  31. package/lib/state/reconciling-transfer-hydranium-glsp-state.d.ts.map +1 -1
  32. package/lib/state/reconciling-transfer-hydranium-glsp-state.js +6 -6
  33. package/lib/state/reconciling-transfer-hydranium-glsp-state.js.map +1 -1
  34. package/lib/storage/hydranium-glsp-storage.d.ts +187 -69
  35. package/lib/storage/hydranium-glsp-storage.d.ts.map +1 -1
  36. package/lib/storage/hydranium-glsp-storage.js +209 -80
  37. package/lib/storage/hydranium-glsp-storage.js.map +1 -1
  38. package/lib/storage/index.d.ts +1 -1
  39. package/lib/storage/index.js +1 -1
  40. package/lib/storage/save-delivery-policy.d.ts +46 -0
  41. package/lib/storage/save-delivery-policy.d.ts.map +1 -0
  42. package/lib/storage/{save-conflict-policy.js → save-delivery-policy.js} +7 -8
  43. package/lib/storage/save-delivery-policy.js.map +1 -0
  44. package/lib/validation/diagnostic-markers.d.ts +2 -2
  45. package/lib/validation/diagnostic-markers.d.ts.map +1 -1
  46. package/lib/validation/diagnostic-markers.js +7 -0
  47. package/lib/validation/diagnostic-markers.js.map +1 -1
  48. package/lib/validation/hydranium-glsp-model-validator.d.ts.map +1 -1
  49. package/lib/validation/hydranium-glsp-model-validator.js +1 -1
  50. package/lib/validation/hydranium-glsp-model-validator.js.map +1 -1
  51. package/package.json +17 -8
  52. package/src/command/hydranium-glsp-recording-command.ts +25 -25
  53. package/src/launcher/hydranium-glsp-server.ts +4 -3
  54. package/src/messages/index.ts +25 -0
  55. package/src/state/abstract-hydranium-glsp-state.ts +85 -51
  56. package/src/state/full-text-hydranium-glsp-state.ts +5 -4
  57. package/src/state/reconcile-source-model-write.ts +15 -12
  58. package/src/state/reconciling-multi-document-glsp-state.ts +46 -18
  59. package/src/state/reconciling-transfer-hydranium-glsp-state.ts +7 -7
  60. package/src/storage/hydranium-glsp-storage.ts +234 -89
  61. package/src/storage/index.ts +1 -1
  62. package/src/storage/save-delivery-policy.ts +44 -0
  63. package/src/validation/diagnostic-markers.ts +10 -3
  64. package/src/validation/hydranium-glsp-model-validator.ts +3 -3
  65. package/lib/storage/save-conflict-policy.d.ts +0 -56
  66. package/lib/storage/save-conflict-policy.d.ts.map +0 -1
  67. package/lib/storage/save-conflict-policy.js.map +0 -1
  68. package/src/storage/save-conflict-policy.ts +0 -52
@@ -10,7 +10,7 @@
10
10
  import { type JsonModelState } from '@eclipse-glsp/server';
11
11
  import { injectable } from 'inversify';
12
12
  import { type AstNode } from '@hydranium/langium';
13
- import { type TransferElement } from '@hydranium/protocol';
13
+ import { type BasedOn, type TransferElement } from '@hydranium/protocol';
14
14
  import { AbstractHydraniumGlspState } from './abstract-hydranium-glsp-state.js';
15
15
  import { reconcileSourceModelWrite } from './reconcile-source-model-write.js';
16
16
 
@@ -70,9 +70,10 @@ export interface MultiDocumentSourceModel<TPrimary extends TransferElement = Tra
70
70
  * a based-on version, so a concurrent foreign edit to one is overwritten rather
71
71
  * than reconciled. Gating them too would need a per-document reconcile whose
72
72
  * outcomes can disagree (merge one, conflict another) with no way to un-write the
73
- * merged one — the atomicity problem again, one layer up. The captured versions
74
- * ARE available via {@link AbstractHydraniumGlspState.capturedVersionOf}, so an
75
- * adopter that wants a coarser check can compare before writing.
73
+ * merged one — the atomicity problem again, one layer up. An adopter that wants
74
+ * a coarser check overrides {@link secondaryBasedOn} to return the version
75
+ * {@link AbstractHydraniumGlspState.snapshotVersionOf} already holds, and takes
76
+ * on the partial-write window named there.
76
77
  */
77
78
  @injectable()
78
79
  export class ReconcilingMultiDocumentGlspState<TRoot extends AstNode, TPrimary extends TransferElement = TransferElement>
@@ -139,10 +140,10 @@ export class ReconcilingMultiDocumentGlspState<TRoot extends AstNode, TPrimary e
139
140
  * `ConflictError` from the primary, reconcile via the injected policy exactly
140
141
  * as the single-document state does — the orchestration is shared.
141
142
  */
142
- async updateSourceModel(model: MultiDocumentSourceModel<TPrimary>, version?: number): Promise<void> {
143
- return reconcileSourceModelWrite<MultiDocumentSourceModel<TPrimary>>(model, version, {
144
- persist: async (candidate, baseVersion) => {
145
- const { root } = await this.persist(candidate, baseVersion);
143
+ async updateSourceModel(model: MultiDocumentSourceModel<TPrimary>, basedOn: BasedOn = this.basedOn): Promise<void> {
144
+ return reconcileSourceModelWrite<MultiDocumentSourceModel<TPrimary>>(model, basedOn, {
145
+ persist: async (candidate, candidateBasedOn) => {
146
+ const { root } = await this.persist(candidate, candidateBasedOn);
146
147
  this.setSourceRoot(this._sourceUri, root);
147
148
  },
148
149
  refetch: () => this.refetch(),
@@ -155,11 +156,11 @@ export class ReconcilingMultiDocumentGlspState<TRoot extends AstNode, TPrimary e
155
156
 
156
157
  /**
157
158
  * Write hook — secondaries first (ungated), primary last (gated on
158
- * `baseVersion`). The order is the failure-mode choice documented on the
159
+ * `basedOn`). The order is the failure-mode choice documented on the
159
160
  * class, not incidental: override this when the primary is the document the
160
161
  * others REFERENCE, since then this order writes the references first.
161
162
  */
162
- protected async persist(model: MultiDocumentSourceModel<TPrimary>, baseVersion?: number): Promise<{ root: TRoot }> {
163
+ protected async persist(model: MultiDocumentSourceModel<TPrimary>, basedOn: BasedOn): Promise<{ root: TRoot }> {
163
164
  for (const [uri, secondary] of Object.entries(model.secondaries)) {
164
165
  if (this.hasChanged(this.baseline?.secondaries[uri], secondary)) {
165
166
  await this.persistSecondary(uri, secondary);
@@ -170,7 +171,7 @@ export class ReconcilingMultiDocumentGlspState<TRoot extends AstNode, TPrimary e
170
171
  // captured rather than round-tripping the document for no reason.
171
172
  return { root: this._sourceRoot };
172
173
  }
173
- return this.persistPrimary(model.primary, baseVersion);
174
+ return this.persistPrimary(model.primary, basedOn);
174
175
  }
175
176
 
176
177
  /**
@@ -193,24 +194,51 @@ export class ReconcilingMultiDocumentGlspState<TRoot extends AstNode, TPrimary e
193
194
  return baseline === undefined || JSON.stringify(baseline) !== JSON.stringify(candidate);
194
195
  }
195
196
 
196
- /** Write the primary document, opting into the conflict gate when `baseVersion` is given. */
197
- protected async persistPrimary(model: TPrimary, baseVersion?: number): Promise<{ root: TRoot }> {
197
+ /** Write the primary document, opting into the conflict gate unless `basedOn` is `'anything'`. */
198
+ protected async persistPrimary(model: TPrimary, basedOn: BasedOn): Promise<{ root: TRoot }> {
198
199
  const document = await this.sharedServices.model.ModelService.update({
199
200
  uri: this._sourceUri,
200
201
  model,
201
202
  clientId: this.clientId,
202
- baseVersion
203
+ basedOn
203
204
  });
204
205
  return document as unknown as { root: TRoot };
205
206
  }
206
207
 
207
208
  /**
208
- * Write one secondary document. Ungated by design (see the class doc); override
209
- * alongside {@link persistPrimary} when the round-trip differs per document
210
- * role — a layout file and a semantic file need not share a serializer.
209
+ * Write one secondary document, gated on whatever {@link secondaryBasedOn}
210
+ * returns. Override alongside {@link persistPrimary} when the round-trip
211
+ * differs per document role — a layout file and a semantic file need not
212
+ * share a serializer.
211
213
  */
212
214
  protected async persistSecondary(uri: string, model: TransferElement): Promise<void> {
213
- await this.sharedServices.model.ModelService.update({ uri, model, clientId: this.clientId });
215
+ await this.sharedServices.model.ModelService.update({
216
+ uri,
217
+ model,
218
+ clientId: this.clientId,
219
+ basedOn: this.secondaryBasedOn(uri)
220
+ });
221
+ }
222
+
223
+ /**
224
+ * What a secondary write declares it was based on. Default `'anything'` —
225
+ * ungated, because gating the whole set needs a per-document reconcile whose
226
+ * outcomes can disagree, with no way to un-write an already-merged sibling.
227
+ *
228
+ * **The hook exists so that opting in is an override rather than a
229
+ * reimplementation.** The state already holds what such a check needs
230
+ * ({@link AbstractHydraniumGlspState.snapshotVersionOf}), and returning that
231
+ * value here is the whole opt-in; without it the only place to put the
232
+ * comparison is a copy of {@link persistSecondary}, which then has to be kept
233
+ * in step with the framework's write call.
234
+ *
235
+ * **Opting in buys a conflict report and costs atomicity.** A gated secondary
236
+ * throws mid-set, after earlier secondaries have already been written, and if
237
+ * the reconcile then drops the edit those writes stay. Weigh that against the
238
+ * default, which lets a foreign edit to a secondary be overwritten silently.
239
+ */
240
+ protected secondaryBasedOn(_uri: string): BasedOn {
241
+ return 'anything';
214
242
  }
215
243
 
216
244
  /**
@@ -10,7 +10,7 @@
10
10
  import { type JsonModelState } from '@eclipse-glsp/server';
11
11
  import { injectable } from 'inversify';
12
12
  import { type AstNode } from '@hydranium/langium';
13
- import { type TransferElement } from '@hydranium/protocol';
13
+ import { type BasedOn, type TransferElement } from '@hydranium/protocol';
14
14
  import { AbstractHydraniumGlspState } from './abstract-hydranium-glsp-state.js';
15
15
  import { reconcileSourceModelWrite } from './reconcile-source-model-write.js';
16
16
 
@@ -84,13 +84,13 @@ export class ReconcilingTransferHydraniumGlspState<TRoot extends AstNode, TSourc
84
84
  * (force = last-writer-wins, reconciling = field-level merge) shared with
85
85
  * undo / redo.
86
86
  */
87
- async updateSourceModel(model: TSourceModel, version?: number): Promise<void> {
87
+ async updateSourceModel(model: TSourceModel, basedOn: BasedOn = this.basedOn): Promise<void> {
88
88
  // Orchestration lives in `reconcileSourceModelWrite` so the multi-document
89
89
  // state gets the identical conflict handling; this method supplies only
90
90
  // the single-document meaning of persist / project.
91
- return reconcileSourceModelWrite<TSourceModel>(model, version, {
92
- persist: async (candidate, baseVersion) => {
93
- const { root } = await this.persist(candidate, baseVersion);
91
+ return reconcileSourceModelWrite<TSourceModel>(model, basedOn, {
92
+ persist: async (candidate, candidateBasedOn) => {
93
+ const { root } = await this.persist(candidate, candidateBasedOn);
94
94
  this.setSourceRoot(this._sourceUri, root);
95
95
  },
96
96
  refetch: () => this.refetch(),
@@ -108,12 +108,12 @@ export class ReconcilingTransferHydraniumGlspState<TRoot extends AstNode, TSourc
108
108
  * round-trip differs override this; the orchestration in
109
109
  * {@link updateSourceModel} is unchanged.
110
110
  */
111
- protected async persist(model: TSourceModel, baseVersion?: number): Promise<{ root: TRoot }> {
111
+ protected async persist(model: TSourceModel, basedOn: BasedOn): Promise<{ root: TRoot }> {
112
112
  const document = await this.sharedServices.model.ModelService.update({
113
113
  uri: this._sourceUri,
114
114
  model,
115
115
  clientId: this.clientId,
116
- baseVersion
116
+ basedOn
117
117
  });
118
118
  return document as unknown as { root: TRoot };
119
119
  }