@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
|
@@ -75,7 +75,7 @@ let HydraniumGlspModelValidator = class HydraniumGlspModelValidator {
|
|
|
75
75
|
continue;
|
|
76
76
|
}
|
|
77
77
|
// Diagnostics are produced by the framework's `HydraniumDocumentValidator`,
|
|
78
|
-
// which emits `
|
|
78
|
+
// which emits `AstDiagnostic` (LSP `Diagnostic` + `element` path).
|
|
79
79
|
// Parser/lexer diagnostics carry no `element`; the lookup drops them.
|
|
80
80
|
const diagnostics = document.diagnostics;
|
|
81
81
|
if (!diagnostics || diagnostics.length === 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydranium-glsp-model-validator.js","sourceRoot":"","sources":["../../src/validation/hydranium-glsp-model-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;;;;;;;;;;AAGlF,OAAO,EAAsB,UAAU,EAAuB,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE/C,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAGzC,OAAO,EAAE,cAAc,EAAE,MAAM,4CAA4C,CAAC;AAC5E,OAAO,EAAgC,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAE7F;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEI,IAAM,2BAA2B,GAAjC,MAAM,2BAA2B;IACyB,cAAc,CAAwB;IAC7D,UAAU,CAAqC;IAEtF,QAAQ,CAAC,SAA0B,EAAE,OAAgB;QAClD,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC;IAED;;;;;OAKG;IACH,OAAO;QACJ,mEAAmE;QACnE,0EAA0E;QAC1E,wEAAwE;QACxE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;YAC9B,OAAO,EAAE,CAAC;QACb,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QACpC,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,uEAAuE;QACvE,qEAAqE;QACrE,cAAc;QACd,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,oBAAoB,EAAE,EAAE,CAAC;YAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,gBAAgB,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;YAC5F,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACb,SAAS;YACZ,CAAC;YACD,4EAA4E;YAC5E,
|
|
1
|
+
{"version":3,"file":"hydranium-glsp-model-validator.js","sourceRoot":"","sources":["../../src/validation/hydranium-glsp-model-validator.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hydranium/glsp-server",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.85",
|
|
4
4
|
"description": "GLSP protocol head for the hydranium framework. Peer of @hydranium/core/lsp and @hydranium/data-server; built on @hydranium/core.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hydranium",
|
|
@@ -48,6 +48,14 @@
|
|
|
48
48
|
"types": "./lib/node/index.d.ts",
|
|
49
49
|
"default": "./lib/node/index.js"
|
|
50
50
|
},
|
|
51
|
+
"./messages": {
|
|
52
|
+
"types": "./lib/messages/index.d.ts",
|
|
53
|
+
"default": "./lib/messages/index.js"
|
|
54
|
+
},
|
|
55
|
+
"./lib/messages": {
|
|
56
|
+
"types": "./lib/messages/index.d.ts",
|
|
57
|
+
"default": "./lib/messages/index.js"
|
|
58
|
+
},
|
|
51
59
|
"./testing": {
|
|
52
60
|
"types": "./lib/testing/index.d.ts",
|
|
53
61
|
"default": "./lib/testing/index.js"
|
|
@@ -79,9 +87,9 @@
|
|
|
79
87
|
"devDependencies": {
|
|
80
88
|
"@eclipse-glsp/protocol": "2.7.0",
|
|
81
89
|
"@eclipse-glsp/server": "2.7.0",
|
|
82
|
-
"@hydranium/core": "1.0.0-next.
|
|
83
|
-
"@hydranium/langium": "1.0.0-next.
|
|
84
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
90
|
+
"@hydranium/core": "1.0.0-next.85",
|
|
91
|
+
"@hydranium/langium": "1.0.0-next.85",
|
|
92
|
+
"@hydranium/protocol": "1.0.0-next.85",
|
|
85
93
|
"inversify": "6.2.2",
|
|
86
94
|
"reflect-metadata": "0.2.2",
|
|
87
95
|
"rimraf": "^5.0.0",
|
|
@@ -92,12 +100,12 @@
|
|
|
92
100
|
"peerDependencies": {
|
|
93
101
|
"@eclipse-glsp/protocol": "^2.6.0",
|
|
94
102
|
"@eclipse-glsp/server": "^2.6.0",
|
|
95
|
-
"@hydranium/core": "1.0.0-next.
|
|
96
|
-
"@hydranium/langium": "1.0.0-next.
|
|
97
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
103
|
+
"@hydranium/core": "1.0.0-next.85",
|
|
104
|
+
"@hydranium/langium": "1.0.0-next.85",
|
|
105
|
+
"@hydranium/protocol": "1.0.0-next.85",
|
|
98
106
|
"inversify": "^6.0.0",
|
|
99
107
|
"reflect-metadata": "^0.2.2",
|
|
100
|
-
"vscode-jsonrpc": "
|
|
108
|
+
"vscode-jsonrpc": "9.0.1",
|
|
101
109
|
"vscode-languageserver-types": "^3.17.5"
|
|
102
110
|
},
|
|
103
111
|
"engines": {
|
|
@@ -106,6 +114,7 @@
|
|
|
106
114
|
"publishConfig": {
|
|
107
115
|
"access": "public"
|
|
108
116
|
},
|
|
117
|
+
"//peerDependencies": "`vscode-jsonrpc` is EXACT rather than a range, because it is one link of an atomic chain with no independently movable link: `vscode-languageserver-protocol` depends on it at exactly 9.0.1, so any other value an adopter supplies is a SECOND physical copy rather than an upgrade. This wire stack breaks on copy identity rather than on structure — `ParameterStructures.auto` is a singleton compared by `===` in connection.js, so a request type built by one copy and sent over a connection owned by the other throws `Unknown parameter structure auto`. A caret or union range was rejected because npm then resolves a `vscode-jsonrpc@8.2.0` tree SILENTLY, with no diagnostic at all, and the failure surfaces only later at server init. Exact does not fail the install either — npm downgrades an unsatisfiable peer to a warning — but it is a NAMED ERESOLVE warning printing the required version beside the found one, and a hard error for anyone installing with `--strict-peer-deps`. **This is the head where it is not enough**: `@eclipse-glsp/*` carries its own exact `vscode-jsonrpc@8.2.0` dependency and root `overrides` do not ship, so a first install of this package ALWAYS lands two copies and the adopter must pin the chain in their own manifest. docs/adopting/requirements.md carries the block to paste.",
|
|
109
118
|
"//prepack": "The publish guard, and it deliberately is NOT a `prepare`: npm runs a workspace `prepare` BEFORE the root `postinstall` that applies patches/vscode-jsonrpc+9.0.1.patch, so building there fails on a cold clone and npm rolls the entire install back. `prepack` runs only when a tarball is made (`npm pack`, `npm publish`) and never on install, so it cannot break the install it has no business touching. It FAILS rather than rebuilds, because the rebuild is exactly the part that ordering defeats. What it defends against: `files` lists `lib`, `lib` is gitignored, and a `files` entry matching nothing is skipped SILENTLY — so `npm publish` from an unbuilt tree emits a tarball of `src` and nothing else, with no error.",
|
|
110
119
|
"//sideEffects": "The harness bare-imports `reflect-metadata`, which installs a global `Reflect` polyfill the inversify decorators in this package need before any container is built. That effect is invisible from the module's exports, so a bundler that treats the file as pure may reorder or drop the import and every `@injectable` resolution then fails at runtime. The rest of the package constructs no module-level state beyond frozen constants and DI symbols."
|
|
111
120
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { type AnyObject, type JsonModelState, JsonRecordingCommand, type MaybePromise } from '@eclipse-glsp/server';
|
|
11
11
|
import { type AstNode } from '@hydranium/langium';
|
|
12
12
|
import { type AbstractHydraniumGlspState } from '../state/abstract-hydranium-glsp-state.js';
|
|
13
|
+
import { type BasedOn } from '@hydranium/protocol';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Source-model state shape consumed by {@link HydraniumGlspRecordingCommand}.
|
|
@@ -46,25 +47,25 @@ export type HydraniumGlspRecordingState<TSourceModel extends AnyObject> = Abstra
|
|
|
46
47
|
*
|
|
47
48
|
* **postChange semantics.** GLSP's `JsonRecordingCommand.postChange` calls
|
|
48
49
|
* `modelState.updateSourceModel(newModel)` with the model only. The
|
|
49
|
-
* framework lift overrides `postChange` so it also threads the
|
|
50
|
-
* version
|
|
50
|
+
* framework lift overrides `postChange` so it also threads the snapshot
|
|
51
|
+
* version taken at command start — letting the downstream
|
|
51
52
|
* `ModelService.update` opt into the `ConflictError` gate. Undo / redo
|
|
52
|
-
* postChange calls
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
53
|
+
* postChange calls pass `'anything'`: the user authored against the recorded
|
|
54
|
+
* patch, not against a specific server version, so re-applying it should
|
|
55
|
+
* succeed regardless of intervening edits. The recorded patch itself encodes
|
|
56
|
+
* the semantic intent.
|
|
56
57
|
*/
|
|
57
58
|
export class HydraniumGlspRecordingCommand<TSourceModel extends AnyObject> extends JsonRecordingCommand<TSourceModel> {
|
|
58
59
|
declare protected modelState: HydraniumGlspRecordingState<TSourceModel>;
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
|
-
*
|
|
62
|
-
* {@link postChange} so
|
|
63
|
-
*
|
|
64
|
-
* returns so undo / redo paths fall through
|
|
65
|
-
*
|
|
62
|
+
* What the command was authored against, taken at {@link execute} start and
|
|
63
|
+
* threaded into {@link postChange} so
|
|
64
|
+
* {@link AbstractHydraniumGlspState.updateSourceModel} gates on it. Reset to
|
|
65
|
+
* `'anything'` after `execute` returns so undo / redo paths fall through
|
|
66
|
+
* ungated.
|
|
66
67
|
*/
|
|
67
|
-
protected
|
|
68
|
+
protected activeBasedOn: BasedOn = 'anything';
|
|
68
69
|
|
|
69
70
|
/**
|
|
70
71
|
* Source-model snapshots captured at {@link execute} — the state the
|
|
@@ -98,20 +99,19 @@ export class HydraniumGlspRecordingCommand<TSourceModel extends AnyObject> exten
|
|
|
98
99
|
* via {@link AbstractHydraniumGlspState.updateSourceModel}). The label combines
|
|
99
100
|
* the operation name with the source-uri-stamped logger.
|
|
100
101
|
*
|
|
101
|
-
*
|
|
102
|
-
* can thread
|
|
103
|
-
*
|
|
104
|
-
* see a stale captured version.
|
|
102
|
+
* Takes {@link AbstractHydraniumGlspState.basedOn} at start so {@link postChange}
|
|
103
|
+
* can thread it into `updateSourceModel`. Scoped with `try`/`finally` so undo
|
|
104
|
+
* / redo paths invoked later do not see a stale value.
|
|
105
105
|
*/
|
|
106
106
|
override async execute(): Promise<void> {
|
|
107
107
|
const logger = this.modelState.logger.for('HydraniumGlspRecordingCommand');
|
|
108
|
-
this.
|
|
109
|
-
logger.debug(`Executing '${this.label}' (based-on doc.version=v${this.
|
|
108
|
+
this.activeBasedOn = this.modelState.basedOn;
|
|
109
|
+
logger.debug(`Executing '${this.label}' (based-on doc.version=v${this.modelState.version})`);
|
|
110
110
|
this.beforeSnapshot = this.deepClone(await this.getJsonObject());
|
|
111
111
|
try {
|
|
112
112
|
await this.modelState.tracer.for('HydraniumGlspRecordingCommand').time(`Execute command '${this.label}'`, () => super.execute());
|
|
113
113
|
} finally {
|
|
114
|
-
this.
|
|
114
|
+
this.activeBasedOn = 'anything';
|
|
115
115
|
}
|
|
116
116
|
this.afterSnapshot = this.deepClone(await this.getJsonObject());
|
|
117
117
|
}
|
|
@@ -119,15 +119,15 @@ export class HydraniumGlspRecordingCommand<TSourceModel extends AnyObject> exten
|
|
|
119
119
|
/**
|
|
120
120
|
* Override of GLSP's {@link JsonRecordingCommand.postChange} so the
|
|
121
121
|
* call to {@link AbstractHydraniumGlspState.updateSourceModel} threads the
|
|
122
|
-
*
|
|
122
|
+
* based-on version alongside the new model.
|
|
123
123
|
*
|
|
124
|
-
* During {@link execute}
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
124
|
+
* During {@link execute} it is the snapshot version taken at command start;
|
|
125
|
+
* during {@link undo} / {@link redo} it is `'anything'` — replaying a
|
|
126
|
+
* recorded patch does not author against a specific server version, so no
|
|
127
|
+
* gate applies.
|
|
128
128
|
*/
|
|
129
129
|
protected override postChange(newModel: TSourceModel): MaybePromise<void> {
|
|
130
|
-
return this.modelState.updateSourceModel(newModel, this.
|
|
130
|
+
return this.modelState.updateSourceModel(newModel, this.activeBasedOn);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
/**
|
|
@@ -26,9 +26,10 @@ import { injectable } from 'inversify';
|
|
|
26
26
|
* `error.cause?.toString()` whenever the error is a {@link GLSPServerError},
|
|
27
27
|
* and that one value feeds both the server log and the client's
|
|
28
28
|
* {@link RejectAction}. A {@link GLSPServerError} carrying no `cause` therefore
|
|
29
|
-
* reaches
|
|
30
|
-
* while a plain `Error` survives,
|
|
31
|
-
* itself. The typed error is the trap,
|
|
29
|
+
* reaches its readers as `undefined` — the log line and the client's
|
|
30
|
+
* action-dispatcher warning both print it — while a plain `Error` survives,
|
|
31
|
+
* because the other branch reads the error itself. The typed error is the trap,
|
|
32
|
+
* and it is the one upstream's own
|
|
32
33
|
* `getOrThrow` helper raises: that helper's signature cannot pass a `cause`, so
|
|
33
34
|
* an adopter using the sanctioned helper has no fix available on their side.
|
|
34
35
|
* Restating the cause at each throw site is the alternative, and it costs
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
* Every user-facing message `@hydranium/glsp-server` raises.
|
|
12
|
+
*
|
|
13
|
+
* **Each one is rendered AT ITS RAISE SITE**, through the shared
|
|
14
|
+
* `MessageRenderer`, because GLSP's action protocol carries no slot for an
|
|
15
|
+
* identity on any of its actions — so the identity is gone the moment a raise
|
|
16
|
+
* site formats its text, and there is nothing at the error boundary to render
|
|
17
|
+
* from. That makes this head the one place where a new message can ship
|
|
18
|
+
* unrendered without any single chokepoint noticing, which is why the package's
|
|
19
|
+
* own test suite scans for a declaration reached by `.format()` instead.
|
|
20
|
+
*
|
|
21
|
+
* The barrel is also how an adopter rendering their own diagram chrome
|
|
22
|
+
* discovers the codes exist.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export { DIAGRAM_READONLY_PARSE_ERROR, SAVE_TARGET_UNKNOWN, SOURCE_URI_MISSING } from '../storage/hydranium-glsp-storage.js';
|
|
@@ -19,7 +19,15 @@ import {
|
|
|
19
19
|
type ServerLanguageServices,
|
|
20
20
|
type ServerSharedServices
|
|
21
21
|
} from '@hydranium/core';
|
|
22
|
-
import
|
|
22
|
+
import {
|
|
23
|
+
type BasedOn,
|
|
24
|
+
type SnapshotVersion,
|
|
25
|
+
type ConflictResolver,
|
|
26
|
+
type Disposable,
|
|
27
|
+
type Logger,
|
|
28
|
+
type Tracer,
|
|
29
|
+
asSnapshotVersion
|
|
30
|
+
} from '@hydranium/protocol';
|
|
23
31
|
import { type HydraniumGlspIndex } from './hydranium-glsp-index.js';
|
|
24
32
|
import { HydraniumTypes } from './hydranium-shared-core-services.js';
|
|
25
33
|
import { ModelReadyTimeoutError } from './model-ready-timeout-error.js';
|
|
@@ -90,9 +98,9 @@ export abstract class AbstractHydraniumGlspState<TRoot extends AstNode, TSourceM
|
|
|
90
98
|
protected _sourceUri!: string;
|
|
91
99
|
protected _sourceRoot!: TRoot;
|
|
92
100
|
protected _tracer?: Tracer;
|
|
93
|
-
protected
|
|
94
|
-
/**
|
|
95
|
-
protected readonly _secondaryVersions = new Map<string,
|
|
101
|
+
protected _basedOn: SnapshotVersion = asSnapshotVersion(0);
|
|
102
|
+
/** Snapshot versions of the secondary write set, keyed by URI. See {@link trackSecondaryDocument}. */
|
|
103
|
+
protected readonly _secondaryVersions = new Map<string, SnapshotVersion>();
|
|
96
104
|
|
|
97
105
|
/** Backing emitter for {@link onSecondaryUrisChanged}; fired only on a membership change. */
|
|
98
106
|
protected readonly secondaryUrisChangedEmitter = new Emitter<void>();
|
|
@@ -115,7 +123,7 @@ export abstract class AbstractHydraniumGlspState<TRoot extends AstNode, TSourceM
|
|
|
115
123
|
setSourceRoot(uri: string, root: TRoot): void {
|
|
116
124
|
this._sourceUri = uri;
|
|
117
125
|
this._sourceRoot = root;
|
|
118
|
-
this.
|
|
126
|
+
this._basedOn = this.readSnapshotVersion(uri);
|
|
119
127
|
// Re-read the secondary write set for the same reason the primary is
|
|
120
128
|
// re-read: the write that brought us here advanced their versions too, and
|
|
121
129
|
// a stale based-on version would gate the NEXT command against a
|
|
@@ -124,7 +132,7 @@ export abstract class AbstractHydraniumGlspState<TRoot extends AstNode, TSourceM
|
|
|
124
132
|
this.refreshSecondaryVersions();
|
|
125
133
|
this.set(SOURCE_URI_ARG, uri);
|
|
126
134
|
this._tracer = this.baseTracer.withUri(uri);
|
|
127
|
-
this.tracer.debug(`Captured source root at doc.version=v${this.
|
|
135
|
+
this.tracer.debug(`Captured source root at doc.version=v${this._basedOn}`);
|
|
128
136
|
this.checkDeclaredLanguage(uri);
|
|
129
137
|
this.index.indexSourceRoot(root, uri);
|
|
130
138
|
}
|
|
@@ -241,25 +249,34 @@ export abstract class AbstractHydraniumGlspState<TRoot extends AstNode, TSourceM
|
|
|
241
249
|
}
|
|
242
250
|
|
|
243
251
|
/**
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
* at that point in time — frozen until the next `setSourceRoot`, then
|
|
247
|
-
* re-read from `LangiumDocuments`.
|
|
252
|
+
* Snapshot version taken at the last {@link setSourceRoot} call — frozen
|
|
253
|
+
* until the next one, then re-read from the document store.
|
|
248
254
|
*
|
|
249
255
|
* Threaded by `HydraniumGlspRecordingCommand` into
|
|
250
|
-
* {@link updateSourceModel}
|
|
251
|
-
*
|
|
252
|
-
* conflict gate against the captured based-on version. See
|
|
256
|
+
* {@link updateSourceModel} so the downstream `ModelService.update` /
|
|
257
|
+
* `.save` call gates against what the command was authored on. See
|
|
253
258
|
* `@hydranium/protocol#ConflictError` for the detection contract.
|
|
254
259
|
*
|
|
260
|
+
* **This, not a number read at write time, is what a write must be gated
|
|
261
|
+
* on.** A version read when the write is issued is the store's current one,
|
|
262
|
+
* so it matches by construction and the gate can never fire.
|
|
263
|
+
*/
|
|
264
|
+
get basedOn(): SnapshotVersion {
|
|
265
|
+
return this._basedOn;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Text-document version of {@link basedOn}. Mirrors the server-side
|
|
270
|
+
* `TextDocuments.version(uri)` counter as of the snapshot.
|
|
271
|
+
*
|
|
255
272
|
* Falls back to `0` when the document is absent from `LangiumDocuments`
|
|
256
|
-
*
|
|
257
|
-
* that was never opened. Callers that need to distinguish
|
|
273
|
+
* when the snapshot is taken — the same observable state callers see for a
|
|
274
|
+
* doc that was never opened. Callers that need to distinguish
|
|
258
275
|
* "never-versioned" from "v0" must consult the document registry
|
|
259
276
|
* directly.
|
|
260
277
|
*/
|
|
261
278
|
get version(): number {
|
|
262
|
-
return this.
|
|
279
|
+
return this._basedOn;
|
|
263
280
|
}
|
|
264
281
|
|
|
265
282
|
// ============================================================
|
|
@@ -267,8 +284,8 @@ export abstract class AbstractHydraniumGlspState<TRoot extends AstNode, TSourceM
|
|
|
267
284
|
// ============================================================
|
|
268
285
|
|
|
269
286
|
/**
|
|
270
|
-
* Register `uri` as a document this state also writes,
|
|
271
|
-
* text-document version.
|
|
287
|
+
* Register `uri` as a document this state also writes, snapshotting its
|
|
288
|
+
* current text-document version.
|
|
272
289
|
*
|
|
273
290
|
* A diagram whose layout lives in its own file, or whose canvas creates
|
|
274
291
|
* elements in a referenced semantic document, writes more than the document
|
|
@@ -281,7 +298,7 @@ export abstract class AbstractHydraniumGlspState<TRoot extends AstNode, TSourceM
|
|
|
281
298
|
* identified once the operation names the node. What the framework supplies
|
|
282
299
|
* is the part an adopter cannot reconstruct after the fact — the version each
|
|
283
300
|
* document was at BEFORE the command mutated anything (see
|
|
284
|
-
* {@link
|
|
301
|
+
* {@link snapshotVersionOf}). Idempotent; registering the primary is ignored,
|
|
285
302
|
* since {@link version} already tracks it. Registering a URI not already in
|
|
286
303
|
* the set fires {@link onSecondaryUrisChanged}.
|
|
287
304
|
*/
|
|
@@ -290,7 +307,7 @@ export abstract class AbstractHydraniumGlspState<TRoot extends AstNode, TSourceM
|
|
|
290
307
|
return;
|
|
291
308
|
}
|
|
292
309
|
const added = !this._secondaryVersions.has(uri);
|
|
293
|
-
this._secondaryVersions.set(uri, this.
|
|
310
|
+
this._secondaryVersions.set(uri, this.readSnapshotVersion(uri));
|
|
294
311
|
if (added) {
|
|
295
312
|
this.secondaryUrisChangedEmitter.fire();
|
|
296
313
|
}
|
|
@@ -310,14 +327,15 @@ export abstract class AbstractHydraniumGlspState<TRoot extends AstNode, TSourceM
|
|
|
310
327
|
|
|
311
328
|
/**
|
|
312
329
|
* Fires whenever the secondary write set gains or loses a URI — never when a
|
|
313
|
-
* tracked document's
|
|
330
|
+
* tracked document's snapshot version is merely refreshed, which happens on
|
|
314
331
|
* every {@link setSourceRoot} and changes nothing a subscriber cares about.
|
|
315
332
|
*
|
|
316
333
|
* **This exists so no caller has to know when the set can change.** The set is
|
|
317
334
|
* usually discovered rather than known, and the discovery point is an
|
|
318
|
-
* operation handler naming a node — not a
|
|
319
|
-
* re-derived the set after each
|
|
320
|
-
* it silently: the document would simply never be
|
|
335
|
+
* operation handler naming a node — not a read of the source root. A
|
|
336
|
+
* subscriber that instead re-derived the set after each such read would miss
|
|
337
|
+
* exactly that case, and miss it silently: the document would simply never be
|
|
338
|
+
* watched, with nothing logged.
|
|
321
339
|
* Announcing the change here moves the obligation to the one place that can
|
|
322
340
|
* always discharge it.
|
|
323
341
|
*
|
|
@@ -336,37 +354,44 @@ export abstract class AbstractHydraniumGlspState<TRoot extends AstNode, TSourceM
|
|
|
336
354
|
}
|
|
337
355
|
|
|
338
356
|
/**
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
357
|
+
* Snapshot version for `uri` — {@link basedOn} for the primary, the version
|
|
358
|
+
* taken at {@link trackSecondaryDocument} (refreshed by {@link setSourceRoot})
|
|
359
|
+
* for a secondary, `undefined` for anything untracked.
|
|
342
360
|
*
|
|
343
|
-
* `undefined` rather than
|
|
361
|
+
* `undefined` rather than v0 for an untracked URI deliberately: `0` is a real
|
|
344
362
|
* version meaning "present but never edited", so collapsing the two would let
|
|
345
|
-
* a caller gate a write against a document
|
|
363
|
+
* a caller gate a write against a document this state never read.
|
|
346
364
|
*/
|
|
347
|
-
|
|
365
|
+
snapshotVersionOf(uri: string): SnapshotVersion | undefined {
|
|
348
366
|
if (uri === this._sourceUri) {
|
|
349
|
-
return this.
|
|
367
|
+
return this._basedOn;
|
|
350
368
|
}
|
|
351
369
|
return this._secondaryVersions.get(uri);
|
|
352
370
|
}
|
|
353
371
|
|
|
354
372
|
/**
|
|
355
|
-
* Read a document's
|
|
356
|
-
* canonicalizing gateway
|
|
357
|
-
* the document keyed by its real path rather than
|
|
358
|
-
* would silently weaken the conflict gate).
|
|
359
|
-
*
|
|
360
|
-
*
|
|
373
|
+
* Read a document's version out of the live store and mark it as a snapshot,
|
|
374
|
+
* through the model service's canonicalizing gateway so a URI spelled through
|
|
375
|
+
* a symlink still resolves to the document keyed by its real path rather than
|
|
376
|
+
* stranding at `0` (which would silently weaken the conflict gate).
|
|
377
|
+
*
|
|
378
|
+
* **Call this only at the point the source root is read.** It is the one
|
|
379
|
+
* place the framework turns a live version into a snapshot one, and the value
|
|
380
|
+
* is true only because that is where it is called. Called at write time it
|
|
381
|
+
* answers with the version the write is about to be compared against, so the
|
|
382
|
+
* gate passes unconditionally.
|
|
383
|
+
*
|
|
384
|
+
* Falls back to v0 for a URI the store does not know, matching what
|
|
385
|
+
* {@link version} reports for a document that was never opened.
|
|
361
386
|
*/
|
|
362
|
-
protected
|
|
363
|
-
return this.sharedServices.model.ModelService.
|
|
387
|
+
protected readSnapshotVersion(uri: string): SnapshotVersion {
|
|
388
|
+
return this.sharedServices.model.ModelService.snapshot(uri)?.version ?? asSnapshotVersion(0);
|
|
364
389
|
}
|
|
365
390
|
|
|
366
|
-
/** Re-
|
|
391
|
+
/** Re-take every registered secondary's snapshot version from the document store. */
|
|
367
392
|
protected refreshSecondaryVersions(): void {
|
|
368
393
|
for (const uri of [...this._secondaryVersions.keys()]) {
|
|
369
|
-
this._secondaryVersions.set(uri, this.
|
|
394
|
+
this._secondaryVersions.set(uri, this.readSnapshotVersion(uri));
|
|
370
395
|
}
|
|
371
396
|
}
|
|
372
397
|
|
|
@@ -417,8 +442,8 @@ export abstract class AbstractHydraniumGlspState<TRoot extends AstNode, TSourceM
|
|
|
417
442
|
const timeout = new Promise<void>((resolve, reject) => {
|
|
418
443
|
timer = this.sharedServices.Clock.setTimer(() => {
|
|
419
444
|
const elapsed = Math.round(stopwatch.elapsedMs);
|
|
420
|
-
//
|
|
421
|
-
//
|
|
445
|
+
// Through the model service's canonicalizing gateway, so a symlinked
|
|
446
|
+
// `_sourceUri` is not reported as a hard timeout.
|
|
422
447
|
const doc = this.sharedServices.model.ModelService.getDocument(uri);
|
|
423
448
|
if (doc && doc.state >= state) {
|
|
424
449
|
this.logger.warn(
|
|
@@ -491,13 +516,22 @@ export abstract class AbstractHydraniumGlspState<TRoot extends AstNode, TSourceM
|
|
|
491
516
|
* `JsonModelState` shape meet the framework state via structural
|
|
492
517
|
* intersection at the recording-command call site.
|
|
493
518
|
*
|
|
494
|
-
* `
|
|
495
|
-
*
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
*
|
|
519
|
+
* `basedOn` is the snapshot version taken at command start by
|
|
520
|
+
* `HydraniumGlspRecordingCommand.execute`. Adopters that route through
|
|
521
|
+
* `ModelService.update` / `.save` forward it as the args' `basedOn` field;
|
|
522
|
+
* adopters whose write path doesn't go through `ModelService` ignore it.
|
|
523
|
+
* `'anything'` when the writer is not the recording command (an external
|
|
524
|
+
* storage refresh, an undo replaying a recorded patch), which is a write
|
|
525
|
+
* authored against no particular server version.
|
|
526
|
+
*
|
|
527
|
+
* **Optional, and every implementation must default it to {@link basedOn}
|
|
528
|
+
* rather than to `'anything'`.** Optional is forced: GLSP's `JsonModelState`
|
|
529
|
+
* declares a one-parameter `updateSourceModel` and calls it with one
|
|
530
|
+
* argument, so a second required parameter makes the state unassignable to
|
|
531
|
+
* the slot it has to fill. Defaulting to the state's own snapshot version is
|
|
532
|
+
* what keeps that from costing anything — an operation handler calling
|
|
533
|
+
* `updateSourceModel(model)` gets the gate, and skipping it has to be typed
|
|
534
|
+
* out as `'anything'`.
|
|
501
535
|
*/
|
|
502
|
-
abstract updateSourceModel(model: TSourceModel,
|
|
536
|
+
abstract updateSourceModel(model: TSourceModel, basedOn?: BasedOn): MaybePromise<void>;
|
|
503
537
|
}
|
|
@@ -11,7 +11,7 @@ import { type JsonModelState } from '@eclipse-glsp/server';
|
|
|
11
11
|
import { injectable } from 'inversify';
|
|
12
12
|
import { type AstNode } from '@hydranium/langium';
|
|
13
13
|
import { URI } from '@hydranium/langium';
|
|
14
|
-
import { isPromiseLike, type MaybePromise } from '@hydranium/protocol';
|
|
14
|
+
import { type BasedOn, isPromiseLike, type MaybePromise } from '@hydranium/protocol';
|
|
15
15
|
import { AbstractHydraniumGlspState } from './abstract-hydranium-glsp-state.js';
|
|
16
16
|
|
|
17
17
|
/** Whole-document-text source model: the serialised text of the source root. */
|
|
@@ -34,7 +34,7 @@ export interface FullTextSourceModel {
|
|
|
34
34
|
* because serialization may be async; the sync fast path is preserved.
|
|
35
35
|
* - {@link updateSourceModel} pushes the text back through
|
|
36
36
|
* `ModelService.update` (which accepts a raw text payload) and captures the
|
|
37
|
-
* re-parsed root
|
|
37
|
+
* re-parsed root, gated on the caller's `basedOn`.
|
|
38
38
|
*
|
|
39
39
|
* No `baseline` / conflict reconcile: a whole-document model has exactly one
|
|
40
40
|
* field, so every concurrent edit is a same-field collision and undo / redo
|
|
@@ -52,11 +52,12 @@ export class FullTextHydraniumGlspState<TRoot extends AstNode>
|
|
|
52
52
|
return isPromiseLike(text) ? text.then(value => ({ text: value })) : { text };
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
async updateSourceModel(model: FullTextSourceModel): Promise<void> {
|
|
55
|
+
async updateSourceModel(model: FullTextSourceModel, basedOn: BasedOn = this.basedOn): Promise<void> {
|
|
56
56
|
const document = await this.sharedServices.model.ModelService.update({
|
|
57
57
|
uri: this._sourceUri,
|
|
58
58
|
model: model.text,
|
|
59
|
-
clientId: this.clientId
|
|
59
|
+
clientId: this.clientId,
|
|
60
|
+
basedOn
|
|
60
61
|
});
|
|
61
62
|
this.setSourceRoot(this._sourceUri, document.root as TRoot);
|
|
62
63
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
|
-
import { type ConflictResolver, type Logger, isConflictError } from '@hydranium/protocol';
|
|
10
|
+
import { type BasedOn, type ConflictResolver, type Logger, isConflictError } from '@hydranium/protocol';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* The I/O and policy a {@link reconcileSourceModelWrite} call needs. Deliberately
|
|
@@ -18,11 +18,12 @@ import { type ConflictResolver, type Logger, isConflictError } from '@hydranium/
|
|
|
18
18
|
*/
|
|
19
19
|
export interface SourceModelWriteHooks<TModel> {
|
|
20
20
|
/**
|
|
21
|
-
* Write `model` and
|
|
22
|
-
* on the first attempt (opting into the `ConflictError` gate) and
|
|
23
|
-
* on the merged / forced retries, where the
|
|
21
|
+
* Write `model` and record the resulting state. Called with the caller's own
|
|
22
|
+
* `basedOn` on the first attempt (opting into the `ConflictError` gate) and
|
|
23
|
+
* with `'anything'` on the merged / forced retries, where the reconcile has
|
|
24
|
+
* already decided to win and the point is to land the write.
|
|
24
25
|
*/
|
|
25
|
-
persist(model: TModel,
|
|
26
|
+
persist(model: TModel, basedOn: BasedOn): Promise<void>;
|
|
26
27
|
/** Current server-side projection, or `undefined` when unavailable. */
|
|
27
28
|
refetch(): Promise<TModel | undefined>;
|
|
28
29
|
/** Last in-sync projection the user's intent is measured against. */
|
|
@@ -44,11 +45,11 @@ export interface SourceModelWriteHooks<TModel> {
|
|
|
44
45
|
*/
|
|
45
46
|
export async function reconcileSourceModelWrite<TModel extends object>(
|
|
46
47
|
model: TModel,
|
|
47
|
-
|
|
48
|
+
basedOn: BasedOn,
|
|
48
49
|
hooks: SourceModelWriteHooks<TModel>
|
|
49
50
|
): Promise<void> {
|
|
50
51
|
try {
|
|
51
|
-
await hooks.persist(model,
|
|
52
|
+
await hooks.persist(model, basedOn);
|
|
52
53
|
} catch (err) {
|
|
53
54
|
if (!isConflictError(err)) {
|
|
54
55
|
throw err;
|
|
@@ -56,21 +57,23 @@ export async function reconcileSourceModelWrite<TModel extends object>(
|
|
|
56
57
|
const outcome = await hooks.conflictResolver.resolve(hooks.baseline ?? model, model, () => hooks.refetch());
|
|
57
58
|
switch (outcome.status) {
|
|
58
59
|
case 'merged':
|
|
59
|
-
await hooks.persist(outcome.merged);
|
|
60
|
+
await hooks.persist(outcome.merged, 'anything');
|
|
60
61
|
return;
|
|
61
62
|
case 'no-op':
|
|
62
|
-
hooks.logger.debug(`updateSourceModel no-op (v${err.
|
|
63
|
+
hooks.logger.debug(`updateSourceModel no-op (v${err.expectedVersion} → v${err.actualVersion}); already in sync`);
|
|
63
64
|
return;
|
|
64
65
|
case 'conflict':
|
|
65
66
|
hooks.logger.warn(
|
|
66
|
-
`updateSourceModel conflict (v${err.
|
|
67
|
+
`updateSourceModel conflict (v${err.expectedVersion} → v${err.actualVersion}); dropping the diagram edit — ` +
|
|
67
68
|
'a foreign writer changed the same field'
|
|
68
69
|
);
|
|
69
70
|
hooks.onConflictDropped();
|
|
70
71
|
return;
|
|
71
72
|
case 'unavailable':
|
|
72
|
-
hooks.logger.warn(
|
|
73
|
-
|
|
73
|
+
hooks.logger.warn(
|
|
74
|
+
`updateSourceModel refetch unavailable (v${err.expectedVersion} → v${err.actualVersion}); forcing without version`
|
|
75
|
+
);
|
|
76
|
+
await hooks.persist(model, 'anything');
|
|
74
77
|
return;
|
|
75
78
|
}
|
|
76
79
|
}
|