@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.
- package/README.md +11 -2
- package/lib/command/hydranium-glsp-recording-command.d.ts +21 -21
- package/lib/command/hydranium-glsp-recording-command.d.ts.map +1 -1
- package/lib/command/hydranium-glsp-recording-command.js +24 -25
- package/lib/command/hydranium-glsp-recording-command.js.map +1 -1
- package/lib/launcher/hydranium-glsp-server.d.ts +4 -3
- package/lib/launcher/hydranium-glsp-server.d.ts.map +1 -1
- package/lib/launcher/hydranium-glsp-server.js +4 -3
- package/lib/launcher/hydranium-glsp-server.js.map +1 -1
- package/lib/messages/index.d.ts +24 -0
- package/lib/messages/index.d.ts.map +1 -0
- package/lib/messages/index.js +24 -0
- package/lib/messages/index.js.map +1 -0
- package/lib/state/abstract-hydranium-glsp-state.d.ts +65 -42
- package/lib/state/abstract-hydranium-glsp-state.d.ts.map +1 -1
- package/lib/state/abstract-hydranium-glsp-state.js +58 -41
- package/lib/state/abstract-hydranium-glsp-state.js.map +1 -1
- package/lib/state/full-text-hydranium-glsp-state.d.ts +3 -3
- package/lib/state/full-text-hydranium-glsp-state.d.ts.map +1 -1
- package/lib/state/full-text-hydranium-glsp-state.js +4 -3
- package/lib/state/full-text-hydranium-glsp-state.js.map +1 -1
- package/lib/state/reconcile-source-model-write.d.ts +7 -6
- package/lib/state/reconcile-source-model-write.d.ts.map +1 -1
- package/lib/state/reconcile-source-model-write.js +7 -7
- package/lib/state/reconcile-source-model-write.js.map +1 -1
- package/lib/state/reconciling-multi-document-glsp-state.d.ts +32 -12
- package/lib/state/reconciling-multi-document-glsp-state.d.ts.map +1 -1
- package/lib/state/reconciling-multi-document-glsp-state.js +44 -17
- package/lib/state/reconciling-multi-document-glsp-state.js.map +1 -1
- package/lib/state/reconciling-transfer-hydranium-glsp-state.d.ts +3 -3
- package/lib/state/reconciling-transfer-hydranium-glsp-state.d.ts.map +1 -1
- package/lib/state/reconciling-transfer-hydranium-glsp-state.js +6 -6
- package/lib/state/reconciling-transfer-hydranium-glsp-state.js.map +1 -1
- package/lib/storage/hydranium-glsp-storage.d.ts +187 -69
- package/lib/storage/hydranium-glsp-storage.d.ts.map +1 -1
- package/lib/storage/hydranium-glsp-storage.js +209 -80
- package/lib/storage/hydranium-glsp-storage.js.map +1 -1
- package/lib/storage/index.d.ts +1 -1
- package/lib/storage/index.js +1 -1
- package/lib/storage/save-delivery-policy.d.ts +46 -0
- package/lib/storage/save-delivery-policy.d.ts.map +1 -0
- package/lib/storage/{save-conflict-policy.js → save-delivery-policy.js} +7 -8
- package/lib/storage/save-delivery-policy.js.map +1 -0
- package/lib/validation/diagnostic-markers.d.ts +2 -2
- package/lib/validation/diagnostic-markers.d.ts.map +1 -1
- package/lib/validation/diagnostic-markers.js +7 -0
- package/lib/validation/diagnostic-markers.js.map +1 -1
- package/lib/validation/hydranium-glsp-model-validator.d.ts.map +1 -1
- package/lib/validation/hydranium-glsp-model-validator.js +1 -1
- package/lib/validation/hydranium-glsp-model-validator.js.map +1 -1
- package/package.json +17 -8
- package/src/command/hydranium-glsp-recording-command.ts +25 -25
- package/src/launcher/hydranium-glsp-server.ts +4 -3
- package/src/messages/index.ts +25 -0
- package/src/state/abstract-hydranium-glsp-state.ts +85 -51
- package/src/state/full-text-hydranium-glsp-state.ts +5 -4
- package/src/state/reconcile-source-model-write.ts +15 -12
- package/src/state/reconciling-multi-document-glsp-state.ts +46 -18
- package/src/state/reconciling-transfer-hydranium-glsp-state.ts +7 -7
- package/src/storage/hydranium-glsp-storage.ts +234 -89
- package/src/storage/index.ts +1 -1
- package/src/storage/save-delivery-policy.ts +44 -0
- package/src/validation/diagnostic-markers.ts +10 -3
- package/src/validation/hydranium-glsp-model-validator.ts +3 -3
- package/lib/storage/save-conflict-policy.d.ts +0 -56
- package/lib/storage/save-conflict-policy.d.ts.map +0 -1
- package/lib/storage/save-conflict-policy.js.map +0 -1
- 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.
|
|
74
|
-
*
|
|
75
|
-
*
|
|
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>,
|
|
143
|
-
return reconcileSourceModelWrite<MultiDocumentSourceModel<TPrimary>>(model,
|
|
144
|
-
persist: async (candidate,
|
|
145
|
-
const { root } = await this.persist(candidate,
|
|
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
|
-
* `
|
|
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>,
|
|
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,
|
|
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
|
|
197
|
-
protected async persistPrimary(model: TPrimary,
|
|
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
|
-
|
|
203
|
+
basedOn
|
|
203
204
|
});
|
|
204
205
|
return document as unknown as { root: TRoot };
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
/**
|
|
208
|
-
* Write one secondary document
|
|
209
|
-
* alongside {@link persistPrimary} when the round-trip
|
|
210
|
-
* role — a layout file and a semantic file need not
|
|
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({
|
|
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,
|
|
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,
|
|
92
|
-
persist: async (candidate,
|
|
93
|
-
const { root } = await this.persist(candidate,
|
|
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,
|
|
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
|
-
|
|
116
|
+
basedOn
|
|
117
117
|
});
|
|
118
118
|
return document as unknown as { root: TRoot };
|
|
119
119
|
}
|