@lionweb/class-core 0.7.0-beta.1 → 0.7.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/base-types.d.ts.map +1 -1
  3. package/dist/base-types.js.map +1 -1
  4. package/dist/convenience.d.ts.map +1 -1
  5. package/dist/deltas/appliers.d.ts +3 -2
  6. package/dist/deltas/appliers.d.ts.map +1 -1
  7. package/dist/deltas/appliers.js +362 -160
  8. package/dist/deltas/appliers.js.map +1 -1
  9. package/dist/deltas/handlers.d.ts.map +1 -1
  10. package/dist/deltas/inverters.d.ts.map +1 -1
  11. package/dist/deltas/inverters.js +65 -26
  12. package/dist/deltas/inverters.js.map +1 -1
  13. package/dist/deltas/serialization/deserializer.g.d.ts.map +1 -1
  14. package/dist/deltas/serialization/deserializer.g.js +168 -57
  15. package/dist/deltas/serialization/deserializer.g.js.map +1 -1
  16. package/dist/deltas/serialization/serializer-helpers.d.ts.map +1 -1
  17. package/dist/deltas/serialization/serializer.g.d.ts +2 -2
  18. package/dist/deltas/serialization/serializer.g.d.ts.map +1 -1
  19. package/dist/deltas/serialization/serializer.g.js +186 -47
  20. package/dist/deltas/serialization/serializer.g.js.map +1 -1
  21. package/dist/deltas/serialization/types.g.d.ts +156 -43
  22. package/dist/deltas/serialization/types.g.d.ts.map +1 -1
  23. package/dist/deltas/types.g.d.ts +155 -55
  24. package/dist/deltas/types.g.d.ts.map +1 -1
  25. package/dist/deltas/types.g.js +170 -57
  26. package/dist/deltas/types.g.js.map +1 -1
  27. package/dist/deserializer.d.ts.map +1 -1
  28. package/dist/deserializer.js +4 -4
  29. package/dist/deserializer.js.map +1 -1
  30. package/dist/duplicator.d.ts.map +1 -1
  31. package/dist/duplicator.js.map +1 -1
  32. package/dist/factory.d.ts +7 -0
  33. package/dist/factory.d.ts.map +1 -0
  34. package/dist/factory.js +44 -0
  35. package/dist/factory.js.map +1 -0
  36. package/dist/id-mapping.d.ts +3 -0
  37. package/dist/id-mapping.d.ts.map +1 -1
  38. package/dist/id-mapping.js +3 -0
  39. package/dist/id-mapping.js.map +1 -1
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +1 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/serializer.d.ts.map +1 -1
  45. package/dist/textualizer.d.ts +1 -1
  46. package/dist/textualizer.d.ts.map +1 -1
  47. package/dist/value-managers/annotations.d.ts +5 -0
  48. package/dist/value-managers/annotations.d.ts.map +1 -1
  49. package/dist/value-managers/annotations.js +30 -1
  50. package/dist/value-managers/annotations.js.map +1 -1
  51. package/dist/value-managers/base.d.ts.map +1 -1
  52. package/dist/value-managers/containments.d.ts +3 -0
  53. package/dist/value-managers/containments.d.ts.map +1 -1
  54. package/dist/value-managers/containments.js +78 -8
  55. package/dist/value-managers/containments.js.map +1 -1
  56. package/dist/value-managers/properties.js.map +1 -1
  57. package/dist/value-managers/references.d.ts +3 -0
  58. package/dist/value-managers/references.d.ts.map +1 -1
  59. package/dist/value-managers/references.js +26 -8
  60. package/dist/value-managers/references.js.map +1 -1
  61. package/package.json +7 -10
  62. package/src/base-types.ts +0 -1
  63. package/src/deltas/appliers.ts +380 -184
  64. package/src/deltas/inverters.ts +83 -35
  65. package/src/deltas/serialization/deserializer.g.ts +195 -66
  66. package/src/deltas/serialization/serializer.g.ts +246 -67
  67. package/src/deltas/serialization/types.g.ts +192 -53
  68. package/src/deltas/types.g.ts +192 -53
  69. package/src/deserializer.ts +7 -5
  70. package/src/duplicator.ts +1 -1
  71. package/src/factory.ts +49 -0
  72. package/src/id-mapping.ts +3 -0
  73. package/src/index.ts +1 -0
  74. package/src/value-managers/annotations.ts +27 -1
  75. package/src/value-managers/containments.ts +73 -8
  76. package/src/value-managers/references.ts +28 -8
package/src/factory.ts ADDED
@@ -0,0 +1,49 @@
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 { lazyMapGet } from "@lionweb/ts-utils"
19
+ import { ILanguageBase, NodeBaseFactory } from "./base-types.js"
20
+ import { DeltaHandler } from "./deltas/index.js"
21
+
22
+ /**
23
+ * @return a {@link NodeBaseFactory factory function} that works for all given {@link ILanguageBase language bases}.
24
+ */
25
+ export const combinedFactoryFor = (languageBases: ILanguageBase[], handleDelta?: DeltaHandler): NodeBaseFactory => {
26
+ // create lookup map:
27
+ const languageKey2version2factory: { [key: string]: { [version: string]: NodeBaseFactory } } = {}
28
+ languageBases.forEach((languageBase) => {
29
+ const {key, version} = languageBase.language
30
+ const version2factory = lazyMapGet(languageKey2version2factory, key, () => ({}))
31
+ lazyMapGet(version2factory, version, () => languageBase.factory(handleDelta))
32
+ // (Note: don't destructure factory from languageBase, as that will unbind it as "this"!)
33
+ })
34
+
35
+ return (classifier, id) => {
36
+ const {key, version, name} = classifier.language
37
+ const version2factory = languageKey2version2factory[key]
38
+ if (version2factory === undefined) {
39
+ throw new Error(`language ${name} with key=${key} not known`)
40
+ }
41
+ const factory = version2factory[version]
42
+ if (factory === undefined) {
43
+ const candidateVersions = Object.keys(version2factory)
44
+ throw new Error(`language ${name} with key=${key} and version=${version} not known${candidateVersions.length > 0 ? `- candidate version${candidateVersions.length > 1 ? `s` : ``}: ${candidateVersions.join(", ")}` : ``}`)
45
+ }
46
+ return factory(classifier, id)
47
+ }
48
+ }
49
+
package/src/id-mapping.ts CHANGED
@@ -51,6 +51,9 @@ export class IdMapping {
51
51
  ? null
52
52
  : (this.nodesById[idOrUnresolved] ?? unresolved);
53
53
 
54
+ /**
55
+ * Updates this {@link IdMapping} with the given `node` *and all its descendants* (recursively).
56
+ */
54
57
  updateWith(node: INodeBase) {
55
58
  this.nodesById[node.id] = node;
56
59
  node.children // recurse into all children
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";
@@ -18,14 +18,15 @@
18
18
  import { action, observable } from "mobx"
19
19
 
20
20
  import { INodeBase, removeFromParent } 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
  /**
@@ -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) {
@@ -19,7 +19,14 @@ import { Containment } from "@lionweb/core"
19
19
  import { action, observable } from "mobx"
20
20
 
21
21
  import { INodeBase, removeFromParent } from "../base-types.js"
22
- import { ChildAddedDelta, ChildDeletedDelta, ChildMovedDelta, ChildMovedInSameContainmentDelta, ChildReplacedDelta } from "../deltas/index.js"
22
+ import {
23
+ ChildAddedDelta,
24
+ ChildDeletedDelta,
25
+ ChildMovedAndReplacedFromOtherContainmentInSameParentDelta, ChildMovedAndReplacedInSameContainmentDelta,
26
+ ChildMovedFromOtherContainmentDelta,
27
+ ChildMovedInSameContainmentDelta,
28
+ ChildReplacedDelta
29
+ } from "../deltas/index.js"
23
30
  import { checkIndex, FeatureValueManager } from "./base.js"
24
31
 
25
32
 
@@ -68,11 +75,33 @@ export abstract class SingleContainmentValueManager<T extends INodeBase> extends
68
75
  @action addDirectly(newChild: T) {
69
76
  const oldChild = this.getDirectly();
70
77
  if (oldChild !== undefined) {
71
- throw new Error(`replacing a child using addDirectly on a value manager for a single-valued containment isn't allowed`); // FIXME unit test this
78
+ 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
79
  }
73
80
  this.child.set(newChild);
74
81
  }
75
82
 
83
+ @action replaceWith(newChild: T) {
84
+ const oldChild = this.getDirectly();
85
+ if (oldChild === undefined) {
86
+ // not a proper replace, but an add-set => delegate to regular setter (— unfortunately, through necessarily “unfolding the hierarchy”):
87
+ if (this instanceof OptionalSingleContainmentValueManager) {
88
+ this.set(newChild);
89
+ }
90
+ if (this instanceof RequiredSingleContainmentValueManager) {
91
+ this.set(newChild);
92
+ }
93
+ } else {
94
+ if (oldChild === newChild) {
95
+ // do nothing: nothing's changed
96
+ } else {
97
+ oldChild.detach();
98
+ this.setDirectly(newChild);
99
+ newChild.attachTo(this.container, this.feature);
100
+ this.emitDelta(() => new ChildReplacedDelta(this.container, this.feature, 0, oldChild, newChild));
101
+ }
102
+ }
103
+ }
104
+
76
105
  }
77
106
 
78
107
 
@@ -96,7 +125,7 @@ export class OptionalSingleContainmentValueManager<T extends INodeBase> extends
96
125
  if (newChild.parent && newChild.containment) {
97
126
  const oldParent = newChild.parent;
98
127
  removeFromParent(oldParent, newChild);
99
- this.emitDelta(() => new ChildMovedDelta(oldParent, newChild.containment!, 0, this.container, this.feature, 0, newChild));
128
+ this.emitDelta(() => new ChildMovedFromOtherContainmentDelta(oldParent, newChild.containment!, 0, this.container, this.feature, 0, newChild));
100
129
  } else {
101
130
  this.emitDelta(() => new ChildAddedDelta(this.container, this.feature, 0, newChild));
102
131
  }
@@ -155,7 +184,7 @@ export class RequiredSingleContainmentValueManager<T extends INodeBase> extends
155
184
  if (newChild.parent && newChild.containment) {
156
185
  const oldParent = newChild.parent;
157
186
  removeFromParent(oldParent, newChild);
158
- this.emitDelta(() => new ChildMovedDelta(oldParent, newChild.containment!, 0, this.container, this.feature, 0, newChild));
187
+ this.emitDelta(() => new ChildMovedFromOtherContainmentDelta(oldParent, newChild.containment!, 0, this.container, this.feature, 0, newChild));
159
188
  } else {
160
189
  this.emitDelta(() => new ChildAddedDelta(this.container, this.feature, 0, newChild));
161
190
  }
@@ -228,7 +257,7 @@ export abstract class MultiContainmentValueManager<T extends INodeBase> extends
228
257
  this.emitDelta(() => new ChildAddedDelta(this.container, this.containment, index, newChild));
229
258
  } else {
230
259
  const oldIndex = removeFromParent(newChild.parent, newChild);
231
- this.emitDelta(() => new ChildMovedDelta(newChild.parent!, newChild.containment!, oldIndex, this.container, this.containment, index, newChild));
260
+ this.emitDelta(() => new ChildMovedFromOtherContainmentDelta(newChild.parent!, newChild.containment!, oldIndex, this.container, this.containment, index, newChild));
232
261
  newChild.detach();
233
262
  }
234
263
  newChild.attachTo(this.container, this.containment);
@@ -236,7 +265,7 @@ export abstract class MultiContainmentValueManager<T extends INodeBase> extends
236
265
 
237
266
  @action removeDirectly(childToRemove: T): number {
238
267
  const children = this.getDirectly();
239
- const index = children.findIndex((child) => child === childToRemove);
268
+ const index = children.indexOf(childToRemove);
240
269
  if (index > -1) {
241
270
  children.splice(index, 1);
242
271
  return index;
@@ -244,6 +273,11 @@ export abstract class MultiContainmentValueManager<T extends INodeBase> extends
244
273
  return -1;
245
274
  }
246
275
 
276
+ @action removeAtIndexDirectly(index: number) {
277
+ checkIndex(index, this.children.length, false);
278
+ this.getDirectly().splice(index, 1);
279
+ }
280
+
247
281
  @action moveDirectly(oldIndex: number, newIndex: number): T | undefined {
248
282
  checkIndex(oldIndex, this.children.length, false);
249
283
  checkIndex(newIndex, this.children.length, false);
@@ -262,6 +296,37 @@ export abstract class MultiContainmentValueManager<T extends INodeBase> extends
262
296
  }
263
297
  }
264
298
 
299
+ @action replaceAtIndex(movedChild: T, newIndex: number) {
300
+ checkIndex(newIndex, this.children.length, false);
301
+ const replacedChild = this.children[newIndex];
302
+ if (replacedChild === movedChild) {
303
+ // do nothing: nothing's changed
304
+ } else {
305
+ this.children.splice(newIndex, 1, movedChild);
306
+ if (replacedChild.parent) {
307
+ const oldValueManager = replacedChild.parent.getContainmentValueManager(replacedChild.containment!);
308
+ const oldIndex = oldValueManager instanceof SingleContainmentValueManager
309
+ ? 0
310
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
311
+ : (oldValueManager as MultiContainmentValueManager<any>).children.indexOf(replacedChild);
312
+ replacedChild.detach();
313
+ if (replacedChild.parent === movedChild.parent) {
314
+ if (replacedChild.containment === movedChild.containment) {
315
+ this.emitDelta(() => new ChildMovedAndReplacedInSameContainmentDelta(this.container, this.containment, oldIndex, newIndex, movedChild, replacedChild));
316
+ } else {
317
+ this.emitDelta(() => new ChildMovedAndReplacedFromOtherContainmentInSameParentDelta(this.container, replacedChild.containment!, oldIndex, this.containment, newIndex, movedChild, replacedChild));
318
+ }
319
+ } else {
320
+ this.emitDelta(() => new ChildMovedFromOtherContainmentDelta(replacedChild.parent!, replacedChild.containment!, oldIndex, this.container, this.containment, newIndex, movedChild));
321
+ }
322
+ } else {
323
+ // not a move+replace, but a regular replace instead:
324
+ this.emitDelta(() => new ChildReplacedDelta(this.container, this.containment, newIndex, replacedChild, movedChild));
325
+ }
326
+ movedChild.attachTo(this.container, this.containment);
327
+ }
328
+ }
329
+
265
330
  }
266
331
 
267
332
 
@@ -274,7 +339,7 @@ export class OptionalMultiContainmentValueManager<T extends INodeBase> extends M
274
339
 
275
340
  @action remove(childToRemove: T) {
276
341
  const children = this.getDirectly();
277
- const index = children.findIndex((child) => child === childToRemove);
342
+ const index = children.indexOf(childToRemove);
278
343
  if (index > -1) {
279
344
  children.splice(index, 1);
280
345
  childToRemove.detach();
@@ -302,7 +367,7 @@ export class RequiredMultiContainmentValueManager<T extends INodeBase> extends M
302
367
 
303
368
  @action remove(childToRemove: T) {
304
369
  const children = this.getDirectly();
305
- const index = children.findIndex((child) => child === childToRemove);
370
+ const index = children.indexOf(childToRemove);
306
371
  if (index > -1) {
307
372
  if (children.length === 1) {
308
373
  this.throwOnUnset();
@@ -19,7 +19,12 @@ import { Reference, SingleRef } from "@lionweb/core"
19
19
  import { action, observable } from "mobx"
20
20
 
21
21
  import { INodeBase } from "../base-types.js"
22
- import { ReferenceAddedDelta, ReferenceDeletedDelta, ReferenceMovedInSameReferenceDelta, ReferenceReplacedDelta } from "../deltas/index.js"
22
+ import {
23
+ EntryMovedInSameReferenceDelta,
24
+ ReferenceAddedDelta,
25
+ ReferenceChangedDelta,
26
+ ReferenceDeletedDelta
27
+ } from "../deltas/index.js"
23
28
  import { checkIndex, FeatureValueManager } from "./base.js"
24
29
 
25
30
 
@@ -37,6 +42,7 @@ export abstract class ReferenceValueManager<T extends INodeBase> extends Feature
37
42
  /**
38
43
  * Adds the given target to the reference.
39
44
  * For a single-valued reference, this replaces an already-present target.
45
+ * **Note**: this method predominantly exists for the benefit of the deserializer and duplicator!
40
46
  */
41
47
  abstract addDirectly(newTarget: SingleRef<T> | undefined): void;
42
48
 
@@ -89,18 +95,18 @@ export class OptionalSingleReferenceValueManager<T extends INodeBase> extends Si
89
95
  return
90
96
  }
91
97
  if (newTarget === undefined) {
92
- this.addDirectly(undefined);
98
+ this.setDirectly(undefined);
93
99
  if (oldTarget === undefined) {
94
100
  // (nothing)
95
101
  } else {
96
102
  this.emitDelta(() => new ReferenceDeletedDelta(this.container, this.reference, 0, oldTarget));
97
103
  }
98
104
  } else {
99
- this.addDirectly(newTarget);
105
+ this.setDirectly(newTarget);
100
106
  if (oldTarget === undefined) {
101
107
  this.emitDelta(() => new ReferenceAddedDelta(this.container, this.reference, 0, newTarget));
102
108
  } else {
103
- this.emitDelta(() => new ReferenceReplacedDelta(this.container, this.reference, 0, oldTarget, newTarget));
109
+ this.emitDelta(() => new ReferenceChangedDelta(this.container, this.reference, 0, oldTarget, newTarget));
104
110
  }
105
111
  }
106
112
  }
@@ -132,11 +138,11 @@ export class RequiredSingleReferenceValueManager<T extends INodeBase> extends Si
132
138
  if (newTarget === undefined) {
133
139
  this.throwOnUnset();
134
140
  } else {
135
- this.addDirectly(newTarget);
141
+ this.setDirectly(newTarget);
136
142
  if (oldTarget === undefined) {
137
143
  this.emitDelta(() => new ReferenceAddedDelta(this.container, this.reference, 0, newTarget));
138
144
  } else {
139
- this.emitDelta(() => new ReferenceReplacedDelta(this.container, this.reference, 0, oldTarget, newTarget));
145
+ this.emitDelta(() => new ReferenceChangedDelta(this.container, this.reference, 0, oldTarget, newTarget));
140
146
  }
141
147
  }
142
148
  }
@@ -158,7 +164,7 @@ export abstract class MultiReferenceValueManager<T extends INodeBase> extends Re
158
164
  }
159
165
 
160
166
  get(): SingleRef<T>[] {
161
- return this.getDirectly().slice();
167
+ return this.getDirectly().slice(); // make defensive copy
162
168
  }
163
169
 
164
170
  isSet(): boolean {
@@ -194,6 +200,11 @@ export abstract class MultiReferenceValueManager<T extends INodeBase> extends Re
194
200
 
195
201
  abstract remove(targetToRemove: SingleRef<T>): void;
196
202
 
203
+ @action removeAtIndexDirectly(index: number): SingleRef<T> {
204
+ checkIndex(index, this.targets.length, false);
205
+ return this.targets.splice(index, 1)[0];
206
+ }
207
+
197
208
  @action moveDirectly(oldIndex: number, newIndex: number): SingleRef<T> | undefined {
198
209
  checkIndex(oldIndex, this.targets.length, false);
199
210
  checkIndex(newIndex, this.targets.length, false);
@@ -208,7 +219,16 @@ export abstract class MultiReferenceValueManager<T extends INodeBase> extends Re
208
219
  @action move(oldIndex: number, newIndex: number) {
209
220
  const target = this.moveDirectly(oldIndex, newIndex);
210
221
  if (target !== undefined) {
211
- this.emitDelta(() => new ReferenceMovedInSameReferenceDelta(this.container, this.reference, oldIndex, newIndex, target));
222
+ this.emitDelta(() => new EntryMovedInSameReferenceDelta(this.container, this.reference, oldIndex, newIndex, target));
223
+ }
224
+ }
225
+
226
+ @action moveAndReplaceDirectly(oldIndex: number, newIndex: number) {
227
+ checkIndex(oldIndex, this.targets.length, false);
228
+ checkIndex(newIndex, this.targets.length, false);
229
+ if (oldIndex !== newIndex) {
230
+ this.targets.splice(newIndex, 1, this.targets[oldIndex]);
231
+ this.targets.splice(oldIndex, 1);
212
232
  }
213
233
  }
214
234