@hydranium/glsp-server 1.0.0-next.12 → 1.0.0-next.123
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 +13 -2
- package/lib/command/hydranium-glsp-recording-command.d.ts +22 -22
- package/lib/command/hydranium-glsp-recording-command.d.ts.map +1 -1
- package/lib/command/hydranium-glsp-recording-command.js +25 -26
- 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/node/start-glsp-server.js +1 -1
- package/lib/node/start-glsp-server.js.map +1 -1
- package/lib/state/abstract-hydranium-glsp-state.d.ts +110 -43
- package/lib/state/abstract-hydranium-glsp-state.d.ts.map +1 -1
- package/lib/state/abstract-hydranium-glsp-state.js +130 -41
- package/lib/state/abstract-hydranium-glsp-state.js.map +1 -1
- package/lib/state/full-text-hydranium-glsp-state.d.ts +6 -6
- package/lib/state/full-text-hydranium-glsp-state.d.ts.map +1 -1
- package/lib/state/full-text-hydranium-glsp-state.js +6 -10
- package/lib/state/full-text-hydranium-glsp-state.js.map +1 -1
- package/lib/state/reconcile-source-model-write.d.ts +9 -8
- package/lib/state/reconcile-source-model-write.d.ts.map +1 -1
- package/lib/state/reconcile-source-model-write.js +8 -8
- package/lib/state/reconcile-source-model-write.js.map +1 -1
- package/lib/state/reconciling-multi-document-glsp-state.d.ts +59 -59
- package/lib/state/reconciling-multi-document-glsp-state.d.ts.map +1 -1
- package/lib/state/reconciling-multi-document-glsp-state.js +84 -82
- package/lib/state/reconciling-multi-document-glsp-state.js.map +1 -1
- package/lib/state/reconciling-transfer-hydranium-glsp-state.d.ts +19 -18
- package/lib/state/reconciling-transfer-hydranium-glsp-state.d.ts.map +1 -1
- package/lib/state/reconciling-transfer-hydranium-glsp-state.js +26 -30
- package/lib/state/reconciling-transfer-hydranium-glsp-state.js.map +1 -1
- package/lib/storage/hydranium-glsp-storage.d.ts +271 -76
- package/lib/storage/hydranium-glsp-storage.d.ts.map +1 -1
- package/lib/storage/hydranium-glsp-storage.js +383 -87
- 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 +26 -26
- package/src/launcher/hydranium-glsp-server.ts +4 -3
- package/src/messages/index.ts +30 -0
- package/src/node/start-glsp-server.ts +1 -1
- package/src/state/abstract-hydranium-glsp-state.ts +162 -51
- package/src/state/full-text-hydranium-glsp-state.ts +7 -11
- package/src/state/reconcile-source-model-write.ts +18 -15
- package/src/state/reconciling-multi-document-glsp-state.ts +86 -83
- package/src/state/reconciling-transfer-hydranium-glsp-state.ts +27 -31
- package/src/storage/hydranium-glsp-storage.ts +445 -97
- 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,8 @@
|
|
|
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
|
|
13
|
+
import { type ClientSession, type ClientSessionWriteArgs } from '@hydranium/core';
|
|
14
|
+
import { type BasedOn, type TransferElement } from '@hydranium/protocol';
|
|
14
15
|
import { AbstractHydraniumGlspState } from './abstract-hydranium-glsp-state.js';
|
|
15
16
|
import { reconcileSourceModelWrite } from './reconcile-source-model-write.js';
|
|
16
17
|
|
|
@@ -44,35 +45,22 @@ export interface MultiDocumentSourceModel<TPrimary extends TransferElement = Tra
|
|
|
44
45
|
* defines them over the primary plus the secondary write set registered through
|
|
45
46
|
* {@link AbstractHydraniumGlspState.trackSecondaryDocument}.
|
|
46
47
|
*
|
|
47
|
-
* **
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* back, and the document store commits per URI. An adopter that cannot accept the
|
|
52
|
-
* window should not span documents in one operation.
|
|
48
|
+
* **The write set is written all or none.** {@link persist} hands every
|
|
49
|
+
* changed document to one `ClientSession.updateAll` on the diagram's session,
|
|
50
|
+
* so a conflict on any document leaves every document of the set as it was,
|
|
51
|
+
* and write order carries no meaning.
|
|
53
52
|
*
|
|
54
|
-
* **
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* secondaries-then-primary, which is correct for the common shape where the
|
|
61
|
-
* primary is the DIAGRAM and the semantics it references are secondaries: the
|
|
62
|
-
* user opened the diagram, so it is also the document the conflict gate should
|
|
63
|
-
* guard. **An adopter whose primary is the referenced document — a semantic file
|
|
64
|
-
* as the diagram source, with layout in a secondary — has the ordering backwards
|
|
65
|
-
* and must override {@link persist} to write the primary first.** Overriding it
|
|
66
|
-
* is the supported route; the base order is a default for the common case, not an
|
|
67
|
-
* invariant of the class.
|
|
53
|
+
* **Every document of the set is gated.** The primary on the caller's
|
|
54
|
+
* `basedOn`, each secondary on {@link secondaryBasedOn}, by default the version
|
|
55
|
+
* it had when the source root was last read. A conflict on any of them is
|
|
56
|
+
* reconciled against the whole write set. A write based on `'anything'` — a
|
|
57
|
+
* merged retry, an undo or redo replaying a patch — forces every document of
|
|
58
|
+
* the set.
|
|
68
59
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
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.
|
|
60
|
+
* A secondary is written only while the diagram's session has it open. The
|
|
61
|
+
* storage opens it as it joins the write set, and {@link openForWrite} opens it
|
|
62
|
+
* again before each write; an adopter whose write set can name a document that
|
|
63
|
+
* does not exist yet overrides {@link openForWrite} to create it.
|
|
76
64
|
*/
|
|
77
65
|
@injectable()
|
|
78
66
|
export class ReconcilingMultiDocumentGlspState<TRoot extends AstNode, TPrimary extends TransferElement = TransferElement>
|
|
@@ -81,10 +69,10 @@ export class ReconcilingMultiDocumentGlspState<TRoot extends AstNode, TPrimary e
|
|
|
81
69
|
{
|
|
82
70
|
/**
|
|
83
71
|
* Last in-sync projection across the whole write set, captured on every
|
|
84
|
-
* {@link setSourceRoot}. Same role as the single-document
|
|
72
|
+
* {@link setSourceRoot}. Same role as the single-document base: the state a
|
|
85
73
|
* forward-write conflict reconciles the user's intent against.
|
|
86
74
|
*/
|
|
87
|
-
protected
|
|
75
|
+
protected base?: MultiDocumentSourceModel<TPrimary>;
|
|
88
76
|
|
|
89
77
|
/**
|
|
90
78
|
* Projection of the primary plus every registered secondary, in the framework
|
|
@@ -111,21 +99,21 @@ export class ReconcilingMultiDocumentGlspState<TRoot extends AstNode, TPrimary e
|
|
|
111
99
|
override setSourceRoot(uri: string, root: TRoot): void {
|
|
112
100
|
super.setSourceRoot(uri, root);
|
|
113
101
|
this.trackWriteSet(uri);
|
|
114
|
-
this.
|
|
102
|
+
this.base = this.sourceModel;
|
|
115
103
|
}
|
|
116
104
|
|
|
117
105
|
/**
|
|
118
106
|
* Register the secondary documents that belong to the primary at `uri`, via
|
|
119
107
|
* {@link AbstractHydraniumGlspState.trackSecondaryDocument}. Called on every
|
|
120
108
|
* {@link setSourceRoot}, after the primary is captured (so `sourceUri` is
|
|
121
|
-
* current) and BEFORE the
|
|
109
|
+
* current) and BEFORE the base is taken (so the base includes them).
|
|
122
110
|
* Default: no secondaries.
|
|
123
111
|
*
|
|
124
112
|
* This hook exists because that ordering is a trap an adopter would otherwise
|
|
125
113
|
* hit silently. Registering from an overridden `setSourceRoot` *after*
|
|
126
|
-
* `super.setSourceRoot(...)` runs too late — the
|
|
114
|
+
* `super.setSourceRoot(...)` runs too late — the base has already been
|
|
127
115
|
* captured without the secondaries, so the first conflict reconcile measures
|
|
128
|
-
* the user's intent against a
|
|
116
|
+
* the user's intent against a base missing half the write set and the
|
|
129
117
|
* secondary edits look like foreign changes. Registering *before* the super
|
|
130
118
|
* call is too early for a URI derived from the new primary. Overriding this
|
|
131
119
|
* instead removes the choice.
|
|
@@ -136,17 +124,18 @@ export class ReconcilingMultiDocumentGlspState<TRoot extends AstNode, TPrimary e
|
|
|
136
124
|
|
|
137
125
|
/**
|
|
138
126
|
* Persist the whole write set, then capture the resulting primary root. On a
|
|
139
|
-
* `ConflictError` from the
|
|
140
|
-
* as the single-document state does — the orchestration is
|
|
127
|
+
* `ConflictError` from any document of the set, reconcile via the injected
|
|
128
|
+
* policy exactly as the single-document state does — the orchestration is
|
|
129
|
+
* shared.
|
|
141
130
|
*/
|
|
142
|
-
async updateSourceModel(model: MultiDocumentSourceModel<TPrimary>,
|
|
143
|
-
return reconcileSourceModelWrite<MultiDocumentSourceModel<TPrimary>>(model,
|
|
144
|
-
persist: async (candidate,
|
|
145
|
-
const { root } = await this.persist(candidate,
|
|
131
|
+
async updateSourceModel(model: MultiDocumentSourceModel<TPrimary>, basedOn: BasedOn = this.basedOn): Promise<void> {
|
|
132
|
+
return reconcileSourceModelWrite<MultiDocumentSourceModel<TPrimary>>(model, basedOn, {
|
|
133
|
+
persist: async (candidate, candidateBasedOn) => {
|
|
134
|
+
const { root } = await this.persist(candidate, candidateBasedOn);
|
|
146
135
|
this.setSourceRoot(this._sourceUri, root);
|
|
147
136
|
},
|
|
148
137
|
refetch: () => this.refetch(),
|
|
149
|
-
|
|
138
|
+
base: this.base,
|
|
150
139
|
conflictResolver: this.conflictResolver,
|
|
151
140
|
logger: this.logger,
|
|
152
141
|
onConflictDropped: () => this.refreshSourceRoot()
|
|
@@ -154,85 +143,99 @@ export class ReconcilingMultiDocumentGlspState<TRoot extends AstNode, TPrimary e
|
|
|
154
143
|
}
|
|
155
144
|
|
|
156
145
|
/**
|
|
157
|
-
* Write hook —
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
146
|
+
* Write hook — every document of `model` that changed, opened first through
|
|
147
|
+
* {@link openForWrite}, in one `updateAll` on the diagram's session: the
|
|
148
|
+
* primary gated on `basedOn`, each secondary on {@link secondaryBasedOn}, or
|
|
149
|
+
* every document on `'anything'` when `basedOn` is `'anything'`. Resolves to
|
|
150
|
+
* the primary's root, the one already captured when the primary did not
|
|
151
|
+
* change. A write that bypasses `updateAll` gives up the all-or-none
|
|
152
|
+
* guarantee the class describes. Throws without a session
|
|
153
|
+
* ({@link requireModelSession}).
|
|
161
154
|
*/
|
|
162
|
-
protected async persist(model: MultiDocumentSourceModel<TPrimary>,
|
|
155
|
+
protected async persist(model: MultiDocumentSourceModel<TPrimary>, basedOn: BasedOn): Promise<{ root: TRoot }> {
|
|
156
|
+
const primaryChanged = this.hasChanged(this.base?.primary, model.primary);
|
|
157
|
+
const updates: ClientSessionWriteArgs<TransferElement>[] = primaryChanged
|
|
158
|
+
? [{ uri: this._sourceUri, model: model.primary, basedOn }]
|
|
159
|
+
: [];
|
|
163
160
|
for (const [uri, secondary] of Object.entries(model.secondaries)) {
|
|
164
|
-
if (this.hasChanged(this.
|
|
165
|
-
|
|
161
|
+
if (this.hasChanged(this.base?.secondaries[uri], secondary)) {
|
|
162
|
+
updates.push({ uri, model: secondary, basedOn: basedOn === 'anything' ? 'anything' : this.secondaryBasedOn(uri) });
|
|
166
163
|
}
|
|
167
164
|
}
|
|
168
|
-
if (
|
|
169
|
-
// Nothing to write, so nothing to gate either — return the root already
|
|
170
|
-
// captured rather than round-tripping the document for no reason.
|
|
165
|
+
if (updates.length === 0) {
|
|
171
166
|
return { root: this._sourceRoot };
|
|
172
167
|
}
|
|
173
|
-
|
|
168
|
+
const session = this.requireModelSession();
|
|
169
|
+
for (const update of updates) {
|
|
170
|
+
await this.openForWrite(session, update.uri);
|
|
171
|
+
}
|
|
172
|
+
const documents = await session.updateAll({ updates });
|
|
173
|
+
return { root: primaryChanged ? (documents[0].root as unknown as TRoot) : this._sourceRoot };
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
/**
|
|
177
|
-
* Whether `candidate` differs from the
|
|
177
|
+
* Whether `candidate` differs from the base projection of the same
|
|
178
178
|
* document, and therefore needs writing.
|
|
179
179
|
*
|
|
180
180
|
* **Skipping unchanged documents is correctness, not an optimisation.** A
|
|
181
|
-
* write
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
181
|
+
* write re-serializes from the AST, so writing a document that did not
|
|
182
|
+
* change still rewrites its text in the serializer's own layout. Persisting
|
|
183
|
+
* the whole write set unconditionally therefore means a pure layout drag
|
|
184
|
+
* reflows the semantic file, a change the user never asked for and would
|
|
185
|
+
* struggle to attribute. Comments survive that rewrite, but the
|
|
186
|
+
* hand-formatting around them does not.
|
|
187
187
|
*
|
|
188
188
|
* Compared by serialised form. Both sides come from the same encoder walking
|
|
189
189
|
* the same shape, so key order is stable and a string compare is sound here;
|
|
190
190
|
* it is also cheap enough to run per document per write.
|
|
191
191
|
*/
|
|
192
|
-
protected hasChanged(
|
|
193
|
-
return
|
|
192
|
+
protected hasChanged(base: object | undefined, candidate: object): boolean {
|
|
193
|
+
return base === undefined || JSON.stringify(base) !== JSON.stringify(candidate);
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
/**
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
return document as unknown as { root: TRoot };
|
|
196
|
+
/**
|
|
197
|
+
* Make sure the diagram's session has `uri` open before the write set is
|
|
198
|
+
* written. Default: open it, a no-op for a document the session already
|
|
199
|
+
* has open. Override to create a document the write set names before it
|
|
200
|
+
* exists, through `session.create`: `updateAll` opens nothing.
|
|
201
|
+
*/
|
|
202
|
+
protected openForWrite(session: ClientSession<AstNode>, uri: string): Promise<void> {
|
|
203
|
+
return session.open(uri);
|
|
205
204
|
}
|
|
206
205
|
|
|
207
206
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
207
|
+
* What a secondary write declares it was based on. Default: the version the
|
|
208
|
+
* secondary had when the source root was last read
|
|
209
|
+
* ({@link AbstractHydraniumGlspState.snapshotVersionOf}), so a foreign edit
|
|
210
|
+
* to it since is reported as a conflict and reconciled rather than
|
|
211
|
+
* overwritten; `'anything'` for a secondary with no recorded version.
|
|
212
|
+
* Return `'anything'` to force secondary writes.
|
|
211
213
|
*/
|
|
212
|
-
protected
|
|
213
|
-
|
|
214
|
+
protected secondaryBasedOn(uri: string): BasedOn {
|
|
215
|
+
return this.snapshotVersionOf(uri) ?? 'anything';
|
|
214
216
|
}
|
|
215
217
|
|
|
216
218
|
/**
|
|
217
|
-
* Refetch hook — the current
|
|
218
|
-
*
|
|
219
|
+
* Refetch hook — the current projection across the write set, each document
|
|
220
|
+
* read through {@link AbstractHydraniumGlspState.readCurrentRoot}: theirs,
|
|
221
|
+
* which the conflict resolver replays the user's intent onto.
|
|
219
222
|
* Returns `undefined` when the PRIMARY cannot be read, since a reconcile
|
|
220
223
|
* without it has nothing to merge into; an unreadable secondary is omitted the
|
|
221
224
|
* same way {@link sourceModel} omits one.
|
|
222
225
|
*/
|
|
223
226
|
protected async refetch(): Promise<MultiDocumentSourceModel<TPrimary> | undefined> {
|
|
224
|
-
const
|
|
225
|
-
if (!
|
|
227
|
+
const theirs = await this.readCurrentRoot(this._sourceUri);
|
|
228
|
+
if (!theirs) {
|
|
226
229
|
return undefined;
|
|
227
230
|
}
|
|
228
231
|
const secondaries: Record<string, TransferElement> = {};
|
|
229
232
|
for (const uri of this.secondaryUris) {
|
|
230
|
-
const
|
|
231
|
-
if (
|
|
232
|
-
secondaries[uri] = this.projectRoot(
|
|
233
|
+
const root = await this.readCurrentRoot(uri);
|
|
234
|
+
if (root) {
|
|
235
|
+
secondaries[uri] = this.projectRoot(root);
|
|
233
236
|
}
|
|
234
237
|
}
|
|
235
|
-
return { primary: this.projectRoot(
|
|
238
|
+
return { primary: this.projectRoot<TPrimary>(theirs), secondaries };
|
|
236
239
|
}
|
|
237
240
|
|
|
238
241
|
/** Project a currently-loaded document's root, or `undefined` when it is not loaded. */
|
|
@@ -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
|
|
|
@@ -27,16 +27,16 @@ import { reconcileSourceModelWrite } from './reconcile-source-model-write.js';
|
|
|
27
27
|
* field-level shape is what lets `fast-json-patch` diff per field, so undo /
|
|
28
28
|
* redo and forward-write reconcile act per field instead of clobbering the
|
|
29
29
|
* whole document.
|
|
30
|
-
* - {@link
|
|
30
|
+
* - {@link base} — the last in-sync projection, captured on every
|
|
31
31
|
* {@link setSourceRoot}. A forward-write conflict reconciles the user's
|
|
32
|
-
* intent (
|
|
32
|
+
* intent (base → ours) against the server's current root (theirs).
|
|
33
33
|
* - {@link updateSourceModel} — the concrete reconcile template: persist,
|
|
34
34
|
* and on a `ConflictError` consult the injected `conflictResolver` and act
|
|
35
35
|
* on the merged / no-op / conflict / unavailable outcome.
|
|
36
36
|
* - {@link persist} / {@link refetch} — the only I/O seams. Defaults route
|
|
37
|
-
* through `
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
* through the diagram session's `update` and `ModelService.validated`;
|
|
38
|
+
* adopters whose document round-trip differs override these without
|
|
39
|
+
* touching the orchestration.
|
|
40
40
|
*
|
|
41
41
|
* Adopters whose source model is whole-document text (a bare `{ text }`
|
|
42
42
|
* source model) or that are read-only do NOT extend this class — they extend
|
|
@@ -52,12 +52,12 @@ export class ReconcilingTransferHydraniumGlspState<TRoot extends AstNode, TSourc
|
|
|
52
52
|
{
|
|
53
53
|
/**
|
|
54
54
|
* Last in-sync source-model projection, captured on every
|
|
55
|
-
* {@link setSourceRoot} (initial load + post-update). The
|
|
55
|
+
* {@link setSourceRoot} (initial load + post-update). The base a
|
|
56
56
|
* forward-write conflict reconciles against: it stays the pre-command
|
|
57
57
|
* state because operation handlers mutate `_sourceRoot` in place during
|
|
58
58
|
* `execute` while `setSourceRoot` only re-runs once the write commits.
|
|
59
59
|
*/
|
|
60
|
-
protected
|
|
60
|
+
protected base?: TSourceModel;
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* Persisted-shape projection of the current source root, consumed by GLSP's
|
|
@@ -73,28 +73,28 @@ export class ReconcilingTransferHydraniumGlspState<TRoot extends AstNode, TSourc
|
|
|
73
73
|
|
|
74
74
|
override setSourceRoot(uri: string, root: TRoot): void {
|
|
75
75
|
super.setSourceRoot(uri, root);
|
|
76
|
-
this.
|
|
76
|
+
this.base = this.sourceModel;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
80
|
* Persist `model` back to the document store, then capture the resulting
|
|
81
81
|
* AST root. On a `ConflictError` (the based-on version was superseded),
|
|
82
|
-
* reconcile the user's intent against the
|
|
82
|
+
* reconcile the user's intent against the server's current root via the bound
|
|
83
83
|
* `conflictResolver` and act on the outcome — one declarative policy
|
|
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(),
|
|
97
|
-
|
|
97
|
+
base: this.base,
|
|
98
98
|
conflictResolver: this.conflictResolver,
|
|
99
99
|
logger: this.logger,
|
|
100
100
|
onConflictDropped: () => this.refreshSourceRoot()
|
|
@@ -103,30 +103,26 @@ export class ReconcilingTransferHydraniumGlspState<TRoot extends AstNode, TSourc
|
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* Persist hook — the only write-side I/O. Default routes the structured
|
|
106
|
-
* model through `
|
|
107
|
-
* the `ConflictError` gate
|
|
106
|
+
* model through the diagram session's `update` (serialize → reparse),
|
|
107
|
+
* opting into the `ConflictError` gate unless `basedOn` is `'anything'`,
|
|
108
|
+
* and throws without a session. Adopters whose document
|
|
108
109
|
* round-trip differs override this; the orchestration in
|
|
109
110
|
* {@link updateSourceModel} is unchanged.
|
|
110
111
|
*/
|
|
111
|
-
protected async persist(model: TSourceModel,
|
|
112
|
-
const document = await this.
|
|
113
|
-
uri: this._sourceUri,
|
|
114
|
-
model,
|
|
115
|
-
clientId: this.clientId,
|
|
116
|
-
baseVersion
|
|
117
|
-
});
|
|
112
|
+
protected async persist(model: TSourceModel, basedOn: BasedOn): Promise<{ root: TRoot }> {
|
|
113
|
+
const document = await this.requireModelSession().update({ uri: this._sourceUri, model, basedOn });
|
|
118
114
|
return document as unknown as { root: TRoot };
|
|
119
115
|
}
|
|
120
116
|
|
|
121
117
|
/**
|
|
122
|
-
* Refetch hook —
|
|
123
|
-
* projection (or `undefined` when unavailable)
|
|
124
|
-
* replays the user's intent
|
|
125
|
-
*
|
|
126
|
-
* adopters override alongside {@link persist}.
|
|
118
|
+
* Refetch hook — the stored text parsed afresh, as a persisted-shape
|
|
119
|
+
* projection (or `undefined` when unavailable): theirs, which the
|
|
120
|
+
* `conflictResolver` replays the user's intent onto. Default uses
|
|
121
|
+
* {@link AbstractHydraniumGlspState.readCurrentRoot} + the framework
|
|
122
|
+
* encoder's `'grammar'` mode; adopters override alongside {@link persist}.
|
|
127
123
|
*/
|
|
128
124
|
protected async refetch(): Promise<TSourceModel | undefined> {
|
|
129
|
-
const
|
|
130
|
-
return
|
|
125
|
+
const theirs = await this.readCurrentRoot(this._sourceUri);
|
|
126
|
+
return theirs ? (this.sharedServices.model.TransferEncoder.toTransfer(theirs, 'grammar') as unknown as TSourceModel) : undefined;
|
|
131
127
|
}
|
|
132
128
|
}
|