@lionweb/class-core 0.7.0-beta.2 → 0.7.0-beta.20
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/CHANGELOG.md +17 -0
- package/README.md +6 -6
- package/dist/LionCore_builtins.g.d.ts +2 -2
- package/dist/LionCore_builtins.g.d.ts.map +1 -1
- package/dist/LionCore_builtins.g.js +1 -1
- package/dist/LionCore_builtins.g.js.map +1 -1
- package/dist/base-types.d.ts +7 -11
- package/dist/base-types.d.ts.map +1 -1
- package/dist/base-types.js +3 -28
- package/dist/base-types.js.map +1 -1
- package/dist/convenience.d.ts +7 -7
- package/dist/convenience.d.ts.map +1 -1
- package/dist/convenience.js +7 -7
- package/dist/convenience.js.map +1 -1
- package/dist/deltas/appliers.d.ts +5 -4
- package/dist/deltas/appliers.d.ts.map +1 -1
- package/dist/deltas/appliers.js +369 -163
- package/dist/deltas/appliers.js.map +1 -1
- package/dist/deltas/compositor.d.ts +51 -0
- package/dist/deltas/compositor.d.ts.map +1 -0
- package/dist/deltas/compositor.js +95 -0
- package/dist/deltas/compositor.js.map +1 -0
- package/dist/deltas/index.d.ts +2 -1
- package/dist/deltas/index.d.ts.map +1 -1
- package/dist/deltas/index.js +2 -1
- package/dist/deltas/index.js.map +1 -1
- package/dist/deltas/inverters.d.ts.map +1 -1
- package/dist/deltas/inverters.js +91 -28
- package/dist/deltas/inverters.js.map +1 -1
- package/dist/deltas/receivers.d.ts +39 -0
- package/dist/deltas/receivers.d.ts.map +1 -0
- package/dist/deltas/receivers.js +60 -0
- package/dist/deltas/receivers.js.map +1 -0
- package/dist/deltas/serialization/deserializer.g.d.ts.map +1 -1
- package/dist/deltas/serialization/deserializer.g.js +167 -59
- package/dist/deltas/serialization/deserializer.g.js.map +1 -1
- package/dist/deltas/serialization/serializer-helpers.d.ts.map +1 -1
- package/dist/deltas/serialization/serializer.g.d.ts +2 -2
- package/dist/deltas/serialization/serializer.g.d.ts.map +1 -1
- package/dist/deltas/serialization/serializer.g.js +186 -47
- package/dist/deltas/serialization/serializer.g.js.map +1 -1
- package/dist/deltas/serialization/types.g.d.ts +156 -43
- package/dist/deltas/serialization/types.g.d.ts.map +1 -1
- package/dist/deltas/types.g.d.ts +155 -55
- package/dist/deltas/types.g.d.ts.map +1 -1
- package/dist/deltas/types.g.js +170 -57
- package/dist/deltas/types.g.js.map +1 -1
- package/dist/deserializer.d.ts +6 -6
- package/dist/deserializer.d.ts.map +1 -1
- package/dist/deserializer.js +15 -26
- package/dist/deserializer.js.map +1 -1
- package/dist/duplicator.d.ts.map +1 -1
- package/dist/duplicator.js.map +1 -1
- package/dist/factory.d.ts +15 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +53 -0
- package/dist/factory.js.map +1 -0
- package/dist/id-mapping.d.ts +27 -3
- package/dist/id-mapping.d.ts.map +1 -1
- package/dist/id-mapping.js +38 -14
- package/dist/id-mapping.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/serializer.d.ts.map +1 -1
- package/dist/textualizer.d.ts +1 -1
- package/dist/textualizer.d.ts.map +1 -1
- package/dist/textualizer.js +1 -1
- package/dist/textualizer.js.map +1 -1
- package/dist/value-managers/annotations.d.ts +5 -0
- package/dist/value-managers/annotations.d.ts.map +1 -1
- package/dist/value-managers/annotations.js +31 -3
- package/dist/value-managers/annotations.js.map +1 -1
- package/dist/value-managers/base.d.ts +2 -2
- package/dist/value-managers/base.d.ts.map +1 -1
- package/dist/value-managers/base.js +4 -4
- package/dist/value-managers/base.js.map +1 -1
- package/dist/value-managers/containments.d.ts +4 -1
- package/dist/value-managers/containments.d.ts.map +1 -1
- package/dist/value-managers/containments.js +152 -20
- package/dist/value-managers/containments.js.map +1 -1
- package/dist/value-managers/properties.js.map +1 -1
- package/dist/value-managers/references.d.ts +3 -0
- package/dist/value-managers/references.d.ts.map +1 -1
- package/dist/value-managers/references.js +26 -8
- package/dist/value-managers/references.js.map +1 -1
- package/package.json +34 -37
- package/src/LionCore_builtins.g.ts +2 -2
- package/src/base-types.ts +6 -32
- package/src/convenience.ts +10 -10
- package/src/deltas/appliers.ts +387 -187
- package/src/deltas/compositor.ts +107 -0
- package/src/deltas/index.ts +3 -1
- package/src/deltas/inverters.ts +109 -37
- package/src/deltas/receivers.ts +92 -0
- package/src/deltas/serialization/deserializer.g.ts +189 -68
- package/src/deltas/serialization/serializer.g.ts +246 -67
- package/src/deltas/serialization/types.g.ts +192 -53
- package/src/deltas/types.g.ts +192 -53
- package/src/deserializer.ts +31 -42
- package/src/duplicator.ts +1 -1
- package/src/factory.ts +63 -0
- package/src/id-mapping.ts +34 -5
- package/src/index.ts +1 -0
- package/src/textualizer.ts +1 -1
- package/src/value-managers/annotations.ts +29 -3
- package/src/value-managers/base.ts +4 -4
- package/src/value-managers/containments.ts +145 -20
- package/src/value-managers/references.ts +28 -8
- package/dist/deltas/handlers.d.ts +0 -17
- package/dist/deltas/handlers.d.ts.map +0 -1
- package/dist/deltas/handlers.js +0 -43
- package/dist/deltas/handlers.js.map +0 -1
- package/src/deltas/handlers.ts +0 -64
package/src/deserializer.ts
CHANGED
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
Containment,
|
|
22
22
|
defaultSimplisticHandler,
|
|
23
23
|
Enumeration,
|
|
24
|
-
Language,
|
|
25
24
|
MemoisingSymbolTable,
|
|
26
25
|
PrimitiveType,
|
|
27
26
|
Property,
|
|
@@ -31,9 +30,10 @@ import {
|
|
|
31
30
|
unresolved
|
|
32
31
|
} from "@lionweb/core"
|
|
33
32
|
import { LionWebId, LionWebJsonChunk, LionWebJsonNode } from "@lionweb/json"
|
|
34
|
-
import { byIdMap } from "@lionweb/ts-utils"
|
|
33
|
+
import { byIdMap, keepDefineds } from "@lionweb/ts-utils"
|
|
35
34
|
|
|
36
|
-
import {
|
|
35
|
+
import { DeltaReceiver, IdMapping, ILanguageBase, INodeBase } from "./index.js"
|
|
36
|
+
import { combinedLanguageBaseLookupFor } from "./factory.js"
|
|
37
37
|
import { NodesToInstall } from "./linking.js"
|
|
38
38
|
|
|
39
39
|
|
|
@@ -43,26 +43,12 @@ import { NodesToInstall } from "./linking.js"
|
|
|
43
43
|
export type Deserializer<T> = (
|
|
44
44
|
serializationChunk: LionWebJsonChunk,
|
|
45
45
|
dependentNodes?: INodeBase[],
|
|
46
|
+
idMapping?: IdMapping,
|
|
46
47
|
propertyValueDeserializer?: PropertyValueDeserializer,
|
|
47
48
|
problemHandler?: SimplisticHandler
|
|
48
49
|
) => T;
|
|
49
50
|
|
|
50
51
|
|
|
51
|
-
const languageBaseLookupFor = (languageBases: ILanguageBase[]) =>
|
|
52
|
-
(language: Language) => {
|
|
53
|
-
const languageBase = languageBases.find((languageBase) => languageBase.language === language);
|
|
54
|
-
if (languageBase === undefined) {
|
|
55
|
-
throw new Error(`language ${language.name} (with key=${language.key} and version=${language.version}) not known`);
|
|
56
|
-
}
|
|
57
|
-
return languageBase;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const factoryLookupFor = (languageBases: ILanguageBase[], handleDelta?: DeltaHandler) => {
|
|
61
|
-
const lookup = languageBaseLookupFor(languageBases);
|
|
62
|
-
return (language: Language) => lookup(language).factory(handleDelta);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
52
|
/**
|
|
67
53
|
* A quasi-tuple of the roots (of type {@link INodeBase}) of a model,
|
|
68
54
|
* and its {@link IdMapping} instance.
|
|
@@ -73,24 +59,24 @@ export type RootsWithIdMapping = { roots: INodeBase[], idMapping: IdMapping };
|
|
|
73
59
|
/**
|
|
74
60
|
* @return a {@link Deserializer} function for the given languages (given as {@link ILanguageBase}s) that returns a {@link RootsWithIdMapping}.
|
|
75
61
|
* @param languageBases the {@link ILanguageBase}s for (at least) all the languages used in the {@link LionWebJsonChunk} to deserialize, minus LionCore M3 and built-ins.
|
|
76
|
-
* @param
|
|
62
|
+
* @param receiveDelta an optional {@link DeltaReceiver} that will be injected in all {@link INodeBase nodes} created.
|
|
77
63
|
*/
|
|
78
|
-
export const nodeBaseDeserializerWithIdMapping = (languageBases: ILanguageBase[],
|
|
64
|
+
export const nodeBaseDeserializerWithIdMapping = (languageBases: ILanguageBase[], receiveDelta?: DeltaReceiver): Deserializer<RootsWithIdMapping> => {
|
|
79
65
|
|
|
80
66
|
const symbolTable = new MemoisingSymbolTable(languageBases.map(({language}) => language));
|
|
81
|
-
const languageBaseFor =
|
|
82
|
-
const factoryFor = factoryLookupFor(languageBases, handleDelta);
|
|
67
|
+
const languageBaseFor = combinedLanguageBaseLookupFor(languageBases);
|
|
83
68
|
|
|
84
69
|
return (
|
|
85
|
-
serializationChunk
|
|
86
|
-
dependentNodes
|
|
87
|
-
|
|
88
|
-
|
|
70
|
+
serializationChunk,
|
|
71
|
+
dependentNodes = [],
|
|
72
|
+
idMapping,
|
|
73
|
+
propertyValueDeserializer = new BuiltinPropertyValueDeserializer(),
|
|
74
|
+
problemsHandler = defaultSimplisticHandler
|
|
89
75
|
): RootsWithIdMapping => {
|
|
90
76
|
|
|
91
77
|
const nodesToInstall: NodesToInstall[] = [];
|
|
92
78
|
|
|
93
|
-
const createNode = ({id, classifier: classifierMetaPointer, properties, containments, references, annotations}: LionWebJsonNode) => {
|
|
79
|
+
const createNode = ({id, classifier: classifierMetaPointer, properties, containments, references, annotations}: LionWebJsonNode): (INodeBase | undefined) => {
|
|
94
80
|
const languageMessage = `language ${classifierMetaPointer.language} (${classifierMetaPointer.version})`;
|
|
95
81
|
const classifier = symbolTable.entityMatching(classifierMetaPointer);
|
|
96
82
|
if (classifier === undefined || !(classifier instanceof Classifier)) {
|
|
@@ -98,7 +84,7 @@ export const nodeBaseDeserializerWithIdMapping = (languageBases: ILanguageBase[]
|
|
|
98
84
|
return undefined;
|
|
99
85
|
}
|
|
100
86
|
|
|
101
|
-
const node =
|
|
87
|
+
const node = languageBaseFor(classifier.language).factory(receiveDelta)(classifier, id);
|
|
102
88
|
|
|
103
89
|
properties.forEach(({property: propertyMetaPointer, value}) => {
|
|
104
90
|
const feature = symbolTable.featureMatching(classifierMetaPointer, propertyMetaPointer);
|
|
@@ -152,15 +138,17 @@ export const nodeBaseDeserializerWithIdMapping = (languageBases: ILanguageBase[]
|
|
|
152
138
|
};
|
|
153
139
|
|
|
154
140
|
const nodesById = byIdMap(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
141
|
+
keepDefineds(
|
|
142
|
+
serializationChunk
|
|
143
|
+
.nodes
|
|
144
|
+
.map(createNode)
|
|
145
|
+
)
|
|
158
146
|
);
|
|
159
147
|
|
|
160
|
-
const dependentNodesById = byIdMap(dependentNodes)
|
|
148
|
+
const dependentNodesById = byIdMap(dependentNodes);
|
|
161
149
|
|
|
162
|
-
const lookupNodeById = (id: LionWebId) =>
|
|
163
|
-
nodesById[id] ?? dependentNodesById[id];
|
|
150
|
+
const lookupNodeById = (id: LionWebId): (INodeBase | undefined) =>
|
|
151
|
+
nodesById[id] ?? dependentNodesById[id] ?? idMapping?.tryFromId(id);
|
|
164
152
|
|
|
165
153
|
nodesToInstall.forEach(([node, feature, ids]) => {
|
|
166
154
|
if (feature instanceof Containment) {
|
|
@@ -217,16 +205,17 @@ export const nodeBaseDeserializerWithIdMapping = (languageBases: ILanguageBase[]
|
|
|
217
205
|
/**
|
|
218
206
|
* @return a {@link Deserializer} function for the languages (given as {@link ILanguageBase}s) that returns the roots (of type {@link INodeBase}) of the deserialized model.
|
|
219
207
|
* @param languageBases the {@link ILanguageBase}s for (at least) all the languages used in the {@link LionWebJsonChunk} to deserialize, minus LionCore M3 and built-ins.
|
|
220
|
-
* @param
|
|
208
|
+
* @param receiveDelta an optional {@link DeltaReceiver} that will be injected in all {@link INodeBase nodes} created.
|
|
221
209
|
*/
|
|
222
|
-
export const nodeBaseDeserializer = (languageBases: ILanguageBase[],
|
|
223
|
-
const deserializerWithIdMapping = nodeBaseDeserializerWithIdMapping(languageBases,
|
|
210
|
+
export const nodeBaseDeserializer = (languageBases: ILanguageBase[], receiveDelta?: DeltaReceiver): Deserializer<INodeBase[]> => {
|
|
211
|
+
const deserializerWithIdMapping = nodeBaseDeserializerWithIdMapping(languageBases, receiveDelta);
|
|
224
212
|
return (
|
|
225
|
-
serializationChunk
|
|
226
|
-
dependentNodes
|
|
227
|
-
|
|
228
|
-
|
|
213
|
+
serializationChunk,
|
|
214
|
+
dependentNodes,
|
|
215
|
+
idMapping,
|
|
216
|
+
propertyValueDeserializer = new BuiltinPropertyValueDeserializer(),
|
|
217
|
+
problemsHandler = defaultSimplisticHandler
|
|
229
218
|
): INodeBase[] =>
|
|
230
|
-
deserializerWithIdMapping(serializationChunk, dependentNodes, propertyValueDeserializer, problemsHandler).roots
|
|
219
|
+
deserializerWithIdMapping(serializationChunk, dependentNodes, idMapping, propertyValueDeserializer, problemsHandler).roots
|
|
231
220
|
}
|
|
232
221
|
|
package/src/duplicator.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// SPDX-FileCopyrightText: 2025 TRUMPF Laser SE and other contributors
|
|
16
16
|
// SPDX-License-Identifier: Apache-2.0
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { Containment, Feature, idOf, Link, Property, Reference, SingleRef, unresolved } from "@lionweb/core"
|
|
19
19
|
import { LionWebId } from "@lionweb/json"
|
|
20
20
|
import { asArray } from "@lionweb/ts-utils"
|
|
21
21
|
|
package/src/factory.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Copyright 2025 TRUMPF Laser SE and other contributors
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License")
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
//
|
|
15
|
+
// SPDX-FileCopyrightText: 2025 TRUMPF Laser SE and other contributors
|
|
16
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
17
|
+
|
|
18
|
+
import { Language } from "@lionweb/core"
|
|
19
|
+
import { lazyMapGet } from "@lionweb/ts-utils"
|
|
20
|
+
import { ILanguageBase, NodeBaseFactory } from "./base-types.js"
|
|
21
|
+
import { DeltaReceiver } from "./deltas/index.js"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @return a function that looks up the {@link ILanguageBase language base} for the {@link Language language} passed to it,
|
|
26
|
+
* from among the given language bases.
|
|
27
|
+
* The returned function throws when the language wasn't among the languages the given bases were for.
|
|
28
|
+
* The lookup is hashmap-backed, so efficient.
|
|
29
|
+
*/
|
|
30
|
+
export const combinedLanguageBaseLookupFor = (languageBases: ILanguageBase[]): ((language: Language) => ILanguageBase) => {
|
|
31
|
+
// create lookup map:
|
|
32
|
+
const languageKey2version2base: { [key: string]: { [version: string]: ILanguageBase } } = {}
|
|
33
|
+
languageBases.forEach((languageBase) => {
|
|
34
|
+
const {key, version} = languageBase.language
|
|
35
|
+
const version2base = lazyMapGet(languageKey2version2base, key, () => ({}))
|
|
36
|
+
lazyMapGet(version2base, version, () => languageBase)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
return (language) => {
|
|
40
|
+
const {key, version, name} = language
|
|
41
|
+
const version2base = languageKey2version2base[key]
|
|
42
|
+
if (version2base === undefined) {
|
|
43
|
+
throw new Error(`language ${name} with key=${key} not registered`)
|
|
44
|
+
}
|
|
45
|
+
const base = version2base[version]
|
|
46
|
+
if (base === undefined) {
|
|
47
|
+
const candidateVersions = Object.keys(version2base)
|
|
48
|
+
throw new Error(`language ${name} with key=${key} and version=${version} not registered${candidateVersions.length > 0 ? `- candidate version${candidateVersions.length > 1 ? `s` : ``}: ${candidateVersions.join(", ")}` : ``}`)
|
|
49
|
+
}
|
|
50
|
+
return base
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @return a {@link NodeBaseFactory factory function} that works for all given {@link ILanguageBase language bases}.
|
|
57
|
+
*/
|
|
58
|
+
export const combinedFactoryFor = (languageBases: ILanguageBase[], receiveDelta?: DeltaReceiver): NodeBaseFactory => {
|
|
59
|
+
const baseOf = combinedLanguageBaseLookupFor(languageBases)
|
|
60
|
+
return (classifier, id) =>
|
|
61
|
+
baseOf(classifier.language).factory(receiveDelta)(classifier, id)
|
|
62
|
+
}
|
|
63
|
+
|
package/src/id-mapping.ts
CHANGED
|
@@ -20,6 +20,10 @@ import { LionWebId } from "@lionweb/json"
|
|
|
20
20
|
|
|
21
21
|
import { INodeBase } from "./index.js"
|
|
22
22
|
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Type def. for a (hash-)map {@link LionWebId ID} → {@link INodeBase}.
|
|
26
|
+
*/
|
|
23
27
|
type NodesById = { [id: LionWebId]: INodeBase }
|
|
24
28
|
|
|
25
29
|
|
|
@@ -30,31 +34,56 @@ type NodesById = { [id: LionWebId]: INodeBase }
|
|
|
30
34
|
*/
|
|
31
35
|
export class IdMapping {
|
|
32
36
|
|
|
33
|
-
nodesById: NodesById;
|
|
37
|
+
private nodesById: NodesById;
|
|
34
38
|
constructor(nodesById: NodesById) {
|
|
35
39
|
this.nodesById = {...nodesById};
|
|
36
40
|
}
|
|
37
41
|
// TODO consider using an instance of Map<Id, INodeBase> instead
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
/**
|
|
44
|
+
* @return the {@link INodeBase node} with the given {@link LionWebId `id`}, or
|
|
45
|
+
* @throws an Error if no node with the given ID was registered.
|
|
46
|
+
*/
|
|
47
|
+
fromId = (id: LionWebId): INodeBase => {
|
|
40
48
|
if (!(id in this.nodesById)) {
|
|
41
49
|
throw new Error(`node with id=${id} not in ID mapping`);
|
|
42
50
|
}
|
|
43
51
|
return this.nodesById[id];
|
|
44
52
|
}
|
|
45
53
|
|
|
54
|
+
/**
|
|
55
|
+
* @return the {@link INodeBase node} with the given {@link LionWebId `id`},
|
|
56
|
+
* or `undefined` if no node with the given ID was registered.
|
|
57
|
+
*/
|
|
46
58
|
tryFromId = (id: LionWebId): (INodeBase | undefined) =>
|
|
47
59
|
this.nodesById[id];
|
|
48
60
|
|
|
61
|
+
/**
|
|
62
|
+
* @return the {@link INodeBase node} referenced from the given {@link LionWebId ID},
|
|
63
|
+
* or `unresolved` if `unresolved` was passed in or no node with the given ID was registered.
|
|
64
|
+
*/
|
|
49
65
|
fromRefId = (idOrUnresolved: IdOrUnresolved): SingleRef<INodeBase> =>
|
|
50
|
-
idOrUnresolved ===
|
|
51
|
-
?
|
|
66
|
+
idOrUnresolved === unresolved
|
|
67
|
+
? unresolved
|
|
52
68
|
: (this.nodesById[idOrUnresolved] ?? unresolved);
|
|
53
69
|
|
|
54
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Updates this {@link IdMapping} with the given `node` *and all its descendants* (recursively).
|
|
72
|
+
*/
|
|
73
|
+
updateWith= (node: INodeBase) => {
|
|
55
74
|
this.nodesById[node.id] = node;
|
|
56
75
|
node.children // recurse into all children
|
|
57
76
|
.forEach((child) => this.updateWith(child));
|
|
77
|
+
// TODO figure out when it's really necessary to call this, as it's potentially *very* expensive
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Re-initializes this {@link IdMapping ID mapping} with the given nodes-by-ID.
|
|
82
|
+
* This completely removes all registrations of nodes,
|
|
83
|
+
* and should only be used by components which are in complete control of the nodes being passed to this method.
|
|
84
|
+
*/
|
|
85
|
+
reinitializeWith = (nodesById: NodesById) => {
|
|
86
|
+
this.nodesById = nodesById;
|
|
58
87
|
}
|
|
59
88
|
|
|
60
89
|
}
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export * from "./deltas/index.js";
|
|
|
21
21
|
export * from "./deserializer.js";
|
|
22
22
|
export { deepDuplicateWith } from "./duplicator.js";
|
|
23
23
|
export * from "./id-mapping.js";
|
|
24
|
+
export { combinedFactoryFor } from "./factory.js";
|
|
24
25
|
// skip linking.js: see comment there
|
|
25
26
|
export * from "./LionCore_builtins.g.js";
|
|
26
27
|
export {serializeNodeBases} from "./serializer.js";
|
package/src/textualizer.ts
CHANGED
|
@@ -54,7 +54,7 @@ export const asTreeTextWith = (identificationFor: (node: INodeBase) => string):
|
|
|
54
54
|
const valueManager = node.getPropertyValueManager(feature)
|
|
55
55
|
const displayValue = (() => {
|
|
56
56
|
if (!valueManager.isSet()) {
|
|
57
|
-
return
|
|
57
|
+
return `<not set>`
|
|
58
58
|
}
|
|
59
59
|
const value = valueManager.getDirectly()
|
|
60
60
|
if (feature.type === LionCore_builtinsBase.INSTANCE.String) {
|
|
@@ -17,15 +17,16 @@
|
|
|
17
17
|
|
|
18
18
|
import { action, observable } from "mobx"
|
|
19
19
|
|
|
20
|
-
import { INodeBase
|
|
20
|
+
import { INodeBase } from "../base-types.js"
|
|
21
|
+
import { checkIndex, ValueManager } from "./base.js"
|
|
21
22
|
import {
|
|
22
23
|
AnnotationAddedDelta,
|
|
23
24
|
AnnotationDeletedDelta,
|
|
25
|
+
AnnotationMovedAndReplacedInSameParentDelta,
|
|
24
26
|
AnnotationMovedFromOtherParentDelta,
|
|
25
27
|
AnnotationMovedInSameParentDelta,
|
|
26
28
|
AnnotationReplacedDelta
|
|
27
29
|
} from "../deltas/index.js"
|
|
28
|
-
import { checkIndex, ValueManager } from "./base.js"
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -66,7 +67,7 @@ export class AnnotationsValueManager extends ValueManager {
|
|
|
66
67
|
newAnnotation.attachTo(this.container, null);
|
|
67
68
|
return false;
|
|
68
69
|
} else {
|
|
69
|
-
const oldIndex =
|
|
70
|
+
const oldIndex = oldParent.annotationsValueManager.removeDirectly(newAnnotation);
|
|
70
71
|
newAnnotation.attachTo(this.container, null);
|
|
71
72
|
return [oldParent, oldIndex];
|
|
72
73
|
}
|
|
@@ -119,6 +120,31 @@ export class AnnotationsValueManager extends ValueManager {
|
|
|
119
120
|
this.emitDelta(() => new AnnotationReplacedDelta(this.container, index, replacedAnnotation, newAnnotation));
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
/**
|
|
124
|
+
* @return the moved and replaced annotations, as an array tuple.
|
|
125
|
+
*/
|
|
126
|
+
@action moveAndReplaceAtIndexDirectly(oldIndex: number, newIndex: number): [INodeBase, INodeBase] | undefined {
|
|
127
|
+
checkIndex(oldIndex, this.annotations.length, false);
|
|
128
|
+
checkIndex(newIndex, this.annotations.length, false);
|
|
129
|
+
if (oldIndex !== newIndex) {
|
|
130
|
+
const movedAnnotation = this.annotations[oldIndex];
|
|
131
|
+
const replacedAnnotation = this.annotations[newIndex];
|
|
132
|
+
this.annotations[newIndex] = movedAnnotation;
|
|
133
|
+
this.annotations.splice(oldIndex, 1);
|
|
134
|
+
replacedAnnotation.detach();
|
|
135
|
+
return [movedAnnotation, replacedAnnotation];
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@action moveAndReplaceAtIndex(oldIndex: number, newIndex: number) {
|
|
141
|
+
const participants = this.moveAndReplaceAtIndexDirectly(oldIndex, newIndex);
|
|
142
|
+
if (participants !== undefined) {
|
|
143
|
+
const [movedAnnotation, replacedAnnotation] = participants;
|
|
144
|
+
this.emitDelta(() => new AnnotationMovedAndReplacedInSameParentDelta(this.container, oldIndex, newIndex, replacedAnnotation, movedAnnotation));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
122
148
|
@action removeDirectly(annotationToRemove: INodeBase): number {
|
|
123
149
|
const index = this.annotations.indexOf(annotationToRemove);
|
|
124
150
|
if (index > -1) {
|
|
@@ -29,12 +29,12 @@ export abstract class ValueManager {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Emits a delta if a {@link
|
|
33
|
-
* @param deltaThunk a thunk that generates the delta, and is only called when a delta
|
|
32
|
+
* Emits a delta if a {@link DeltaReceiver} is registered with the container.
|
|
33
|
+
* @param deltaThunk a thunk that generates the delta, and is only called when a delta receiver is registered with the container.
|
|
34
34
|
*/
|
|
35
35
|
emitDelta(deltaThunk: () => IDelta) {
|
|
36
|
-
if (this.container.
|
|
37
|
-
this.container.
|
|
36
|
+
if (this.container.receiveDelta) {
|
|
37
|
+
this.container.receiveDelta(deltaThunk());
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -18,8 +18,18 @@
|
|
|
18
18
|
import { Containment } from "@lionweb/core"
|
|
19
19
|
import { action, observable } from "mobx"
|
|
20
20
|
|
|
21
|
-
import { INodeBase
|
|
22
|
-
import {
|
|
21
|
+
import { INodeBase } from "../base-types.js"
|
|
22
|
+
import {
|
|
23
|
+
ChildAddedDelta,
|
|
24
|
+
ChildDeletedDelta,
|
|
25
|
+
ChildMovedAndReplacedFromOtherContainmentDelta,
|
|
26
|
+
ChildMovedAndReplacedFromOtherContainmentInSameParentDelta,
|
|
27
|
+
ChildMovedAndReplacedInSameContainmentDelta,
|
|
28
|
+
ChildMovedFromOtherContainmentDelta,
|
|
29
|
+
ChildMovedFromOtherContainmentInSameParentDelta,
|
|
30
|
+
ChildMovedInSameContainmentDelta,
|
|
31
|
+
ChildReplacedDelta
|
|
32
|
+
} from "../deltas/index.js"
|
|
23
33
|
import { checkIndex, FeatureValueManager } from "./base.js"
|
|
24
34
|
|
|
25
35
|
|
|
@@ -68,11 +78,47 @@ export abstract class SingleContainmentValueManager<T extends INodeBase> extends
|
|
|
68
78
|
@action addDirectly(newChild: T) {
|
|
69
79
|
const oldChild = this.getDirectly();
|
|
70
80
|
if (oldChild !== undefined) {
|
|
71
|
-
throw new Error(`replacing a child using addDirectly on a value manager for a single-valued containment isn't allowed`); //
|
|
81
|
+
throw new Error(`replacing a child using addDirectly on a value manager for a single-valued containment isn't allowed`); // TODO unit test this
|
|
72
82
|
}
|
|
73
83
|
this.child.set(newChild);
|
|
74
84
|
}
|
|
75
85
|
|
|
86
|
+
@action replaceWith(movedChild: T) {
|
|
87
|
+
const replacedChild = this.getDirectly();
|
|
88
|
+
if (replacedChild === undefined) {
|
|
89
|
+
// not a proper replace, but an add-set => delegate to regular setter (— unfortunately, through necessarily “unfolding the hierarchy”):
|
|
90
|
+
if (this instanceof OptionalSingleContainmentValueManager) {
|
|
91
|
+
this.set(movedChild);
|
|
92
|
+
}
|
|
93
|
+
if (this instanceof RequiredSingleContainmentValueManager) {
|
|
94
|
+
this.set(movedChild);
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
if (replacedChild === movedChild) {
|
|
98
|
+
// do nothing: nothing's changed
|
|
99
|
+
} else {
|
|
100
|
+
if (movedChild.parent === undefined) {
|
|
101
|
+
this.emitDelta(() => new ChildReplacedDelta(this.container, this.feature, 0, replacedChild, movedChild));
|
|
102
|
+
} else {
|
|
103
|
+
if (movedChild.parent === this.container) {
|
|
104
|
+
if (movedChild.containment === this.containment) {
|
|
105
|
+
this.emitDelta(() => new ChildMovedAndReplacedInSameContainmentDelta(this.container, this.containment, 0, 0, movedChild, replacedChild));
|
|
106
|
+
} else {
|
|
107
|
+
const oldIndex = removeFromContainment(replacedChild);
|
|
108
|
+
this.emitDelta(() => new ChildMovedAndReplacedFromOtherContainmentInSameParentDelta(this.container, movedChild.containment!, oldIndex, this.containment, 0, movedChild, replacedChild));
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
const oldIndex = removeFromContainment(replacedChild);
|
|
112
|
+
this.emitDelta(() => new ChildMovedAndReplacedFromOtherContainmentDelta(this.container, this.containment, 0, movedChild, movedChild.parent!, movedChild.containment!, oldIndex, replacedChild));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
this.setDirectly(movedChild);
|
|
116
|
+
movedChild.attachTo(this.container, this.containment);
|
|
117
|
+
replacedChild.detach();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
76
122
|
}
|
|
77
123
|
|
|
78
124
|
|
|
@@ -95,8 +141,8 @@ export class OptionalSingleContainmentValueManager<T extends INodeBase> extends
|
|
|
95
141
|
} else {
|
|
96
142
|
if (newChild.parent && newChild.containment) {
|
|
97
143
|
const oldParent = newChild.parent;
|
|
98
|
-
|
|
99
|
-
this.emitDelta(() => new
|
|
144
|
+
removeFromContainment(newChild);
|
|
145
|
+
this.emitDelta(() => new ChildMovedFromOtherContainmentDelta(oldParent, newChild.containment!, 0, this.container, this.feature, 0, newChild));
|
|
100
146
|
} else {
|
|
101
147
|
this.emitDelta(() => new ChildAddedDelta(this.container, this.feature, 0, newChild));
|
|
102
148
|
}
|
|
@@ -112,13 +158,14 @@ export class OptionalSingleContainmentValueManager<T extends INodeBase> extends
|
|
|
112
158
|
if (oldChild === newChild) {
|
|
113
159
|
// do nothing: nothing's changed
|
|
114
160
|
} else {
|
|
161
|
+
// FIXME this could emit 2 deltas where it should be a single ChildReplaced-delta
|
|
115
162
|
if (oldChild.parent && oldChild.containment && oldChild.parent === this.container && oldChild.containment === this.feature) {
|
|
116
163
|
// FIXME oldChild.parent COULD be this.container
|
|
117
164
|
this.emitDelta(() => new ChildDeletedDelta(this.container, this.feature, 0, oldChild));
|
|
118
165
|
}
|
|
119
166
|
oldChild.detach();
|
|
120
167
|
if (newChild.parent && newChild.containment) {
|
|
121
|
-
|
|
168
|
+
removeFromContainment(newChild);
|
|
122
169
|
}
|
|
123
170
|
this.setDirectly(newChild);
|
|
124
171
|
newChild.attachTo(this.container, this.feature);
|
|
@@ -154,8 +201,8 @@ export class RequiredSingleContainmentValueManager<T extends INodeBase> extends
|
|
|
154
201
|
} else {
|
|
155
202
|
if (newChild.parent && newChild.containment) {
|
|
156
203
|
const oldParent = newChild.parent;
|
|
157
|
-
|
|
158
|
-
this.emitDelta(() => new
|
|
204
|
+
removeFromContainment(newChild);
|
|
205
|
+
this.emitDelta(() => new ChildMovedFromOtherContainmentDelta(oldParent, newChild.containment!, 0, this.container, this.feature, 0, newChild));
|
|
159
206
|
} else {
|
|
160
207
|
this.emitDelta(() => new ChildAddedDelta(this.container, this.feature, 0, newChild));
|
|
161
208
|
}
|
|
@@ -175,7 +222,7 @@ export class RequiredSingleContainmentValueManager<T extends INodeBase> extends
|
|
|
175
222
|
}
|
|
176
223
|
oldChild.detach();
|
|
177
224
|
if (newChild.parent && newChild.containment) {
|
|
178
|
-
|
|
225
|
+
removeFromContainment(newChild);
|
|
179
226
|
}
|
|
180
227
|
this.setDirectly(newChild);
|
|
181
228
|
newChild.attachTo(this.container, this.feature);
|
|
@@ -222,21 +269,36 @@ export abstract class MultiContainmentValueManager<T extends INodeBase> extends
|
|
|
222
269
|
this.children.splice(index, 0, newChild);
|
|
223
270
|
}
|
|
224
271
|
|
|
225
|
-
@action insertAtIndex(newChild: T,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
272
|
+
@action insertAtIndex(newChild: T, newIndex: number) {
|
|
273
|
+
if (newChild.parent === undefined) {
|
|
274
|
+
this.insertAtIndexDirectly(newChild, newIndex);
|
|
275
|
+
newChild.attachTo(this.container, this.containment);
|
|
276
|
+
this.emitDelta(() => new ChildAddedDelta(this.container, this.containment, newIndex, newChild));
|
|
229
277
|
} else {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
278
|
+
if (newChild.parent === this.container) {
|
|
279
|
+
if (newChild.containment === this.containment) {
|
|
280
|
+
const oldIndex = this.children.indexOf(newChild);
|
|
281
|
+
this.moveDirectly(oldIndex, newIndex);
|
|
282
|
+
this.emitDelta(() => new ChildMovedInSameContainmentDelta(this.container, this.containment, oldIndex, newIndex, newChild));
|
|
283
|
+
} else {
|
|
284
|
+
const oldIndex = removeFromContainment(newChild);
|
|
285
|
+
checkIndex(newIndex, this.children.length, true);
|
|
286
|
+
this.insertAtIndexDirectly(newChild, newIndex);
|
|
287
|
+
this.emitDelta(() => new ChildMovedFromOtherContainmentInSameParentDelta(this.container, newChild.containment!, oldIndex, newChild, this.containment, newIndex));
|
|
288
|
+
newChild.attachTo(this.container, this.containment);
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
const oldIndex = removeFromContainment(newChild);
|
|
292
|
+
this.insertAtIndexDirectly(newChild, newIndex);
|
|
293
|
+
this.emitDelta(() => new ChildMovedFromOtherContainmentDelta(newChild.parent!, newChild.containment!, oldIndex, this.container, this.containment, newIndex, newChild));
|
|
294
|
+
newChild.attachTo(this.container, this.containment);
|
|
295
|
+
}
|
|
233
296
|
}
|
|
234
|
-
newChild.attachTo(this.container, this.containment);
|
|
235
297
|
}
|
|
236
298
|
|
|
237
299
|
@action removeDirectly(childToRemove: T): number {
|
|
238
300
|
const children = this.getDirectly();
|
|
239
|
-
const index = children.
|
|
301
|
+
const index = children.indexOf(childToRemove);
|
|
240
302
|
if (index > -1) {
|
|
241
303
|
children.splice(index, 1);
|
|
242
304
|
return index;
|
|
@@ -244,6 +306,11 @@ export abstract class MultiContainmentValueManager<T extends INodeBase> extends
|
|
|
244
306
|
return -1;
|
|
245
307
|
}
|
|
246
308
|
|
|
309
|
+
@action removeAtIndexDirectly(index: number) {
|
|
310
|
+
checkIndex(index, this.children.length, false);
|
|
311
|
+
this.getDirectly().splice(index, 1);
|
|
312
|
+
}
|
|
313
|
+
|
|
247
314
|
@action moveDirectly(oldIndex: number, newIndex: number): T | undefined {
|
|
248
315
|
checkIndex(oldIndex, this.children.length, false);
|
|
249
316
|
checkIndex(newIndex, this.children.length, false);
|
|
@@ -262,6 +329,40 @@ export abstract class MultiContainmentValueManager<T extends INodeBase> extends
|
|
|
262
329
|
}
|
|
263
330
|
}
|
|
264
331
|
|
|
332
|
+
@action replaceAtIndex(movedChild: T, newIndex: number) {
|
|
333
|
+
checkIndex(newIndex, this.children.length, false);
|
|
334
|
+
if (movedChild.parent === undefined) {
|
|
335
|
+
throw new Error(`a child-to-move (id=${movedChild.id}) must already be contained`);
|
|
336
|
+
}
|
|
337
|
+
const replacedChild = this.children[newIndex];
|
|
338
|
+
if (replacedChild === movedChild) {
|
|
339
|
+
// do nothing: nothing's changed
|
|
340
|
+
} else {
|
|
341
|
+
this.children.splice(newIndex, 1, movedChild);
|
|
342
|
+
if (replacedChild.parent !== undefined) {
|
|
343
|
+
const oldValueManager = replacedChild.parent.getContainmentValueManager(replacedChild.containment!);
|
|
344
|
+
const oldIndex = oldValueManager instanceof SingleContainmentValueManager
|
|
345
|
+
? 0
|
|
346
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
347
|
+
: (oldValueManager as MultiContainmentValueManager<any>).children.indexOf(replacedChild);
|
|
348
|
+
if (replacedChild.parent === movedChild.parent) {
|
|
349
|
+
if (replacedChild.containment === movedChild.containment) {
|
|
350
|
+
this.emitDelta(() => new ChildMovedAndReplacedInSameContainmentDelta(this.container, this.containment, oldIndex, newIndex, movedChild, replacedChild));
|
|
351
|
+
} else {
|
|
352
|
+
this.emitDelta(() => new ChildMovedAndReplacedFromOtherContainmentInSameParentDelta(this.container, replacedChild.containment!, oldIndex, this.containment, newIndex, movedChild, replacedChild));
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
this.emitDelta(() => new ChildMovedAndReplacedFromOtherContainmentDelta(this.container, this.containment, newIndex, movedChild, movedChild.parent!, movedChild.containment!, oldIndex, replacedChild));
|
|
356
|
+
}
|
|
357
|
+
replacedChild.detach();
|
|
358
|
+
} else {
|
|
359
|
+
// not a move+replace, but a regular replace instead:
|
|
360
|
+
this.emitDelta(() => new ChildReplacedDelta(this.container, this.containment, newIndex, replacedChild, movedChild));
|
|
361
|
+
}
|
|
362
|
+
movedChild.attachTo(this.container, this.containment);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
265
366
|
}
|
|
266
367
|
|
|
267
368
|
|
|
@@ -274,7 +375,7 @@ export class OptionalMultiContainmentValueManager<T extends INodeBase> extends M
|
|
|
274
375
|
|
|
275
376
|
@action remove(childToRemove: T) {
|
|
276
377
|
const children = this.getDirectly();
|
|
277
|
-
const index = children.
|
|
378
|
+
const index = children.indexOf(childToRemove);
|
|
278
379
|
if (index > -1) {
|
|
279
380
|
children.splice(index, 1);
|
|
280
381
|
childToRemove.detach();
|
|
@@ -302,7 +403,7 @@ export class RequiredMultiContainmentValueManager<T extends INodeBase> extends M
|
|
|
302
403
|
|
|
303
404
|
@action remove(childToRemove: T) {
|
|
304
405
|
const children = this.getDirectly();
|
|
305
|
-
const index = children.
|
|
406
|
+
const index = children.indexOf(childToRemove);
|
|
306
407
|
if (index > -1) {
|
|
307
408
|
if (children.length === 1) {
|
|
308
409
|
this.throwOnUnset();
|
|
@@ -315,3 +416,27 @@ export class RequiredMultiContainmentValueManager<T extends INodeBase> extends M
|
|
|
315
416
|
|
|
316
417
|
}
|
|
317
418
|
|
|
419
|
+
/**
|
|
420
|
+
* Removes the given child node from its containment, and returns the containment index it had before removal.
|
|
421
|
+
*/
|
|
422
|
+
const removeFromContainment = (child: INodeBase): number => {
|
|
423
|
+
if (child.parent === undefined) {
|
|
424
|
+
throw new Error(`can't remove an orphan from its parent`)
|
|
425
|
+
}
|
|
426
|
+
if (child.containment === undefined) {
|
|
427
|
+
throw new Error(`can't remove an orphan from its containing feature`)
|
|
428
|
+
}
|
|
429
|
+
if (child.containment === null) {
|
|
430
|
+
throw new Error(`can't remove an annotation`)
|
|
431
|
+
}
|
|
432
|
+
const valueManager = child.parent.getContainmentValueManager(child.containment)
|
|
433
|
+
if (valueManager instanceof SingleContainmentValueManager) {
|
|
434
|
+
valueManager.setDirectly(undefined)
|
|
435
|
+
return 0
|
|
436
|
+
} else if (valueManager instanceof MultiContainmentValueManager) {
|
|
437
|
+
return valueManager.removeDirectly(child)
|
|
438
|
+
} else {
|
|
439
|
+
throw new Error(`don't know how to remove a child that's contained through a value manager of type ${valueManager.constructor.name}`)
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|