@lionweb/class-core 0.7.0-beta.13 → 0.7.0-beta.14

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 (43) hide show
  1. package/CHANGELOG.md +8 -1
  2. package/dist/base-types.d.ts +0 -4
  3. package/dist/base-types.d.ts.map +1 -1
  4. package/dist/base-types.js +0 -25
  5. package/dist/base-types.js.map +1 -1
  6. package/dist/deltas/inverters.d.ts.map +1 -1
  7. package/dist/deltas/inverters.js +33 -9
  8. package/dist/deltas/inverters.js.map +1 -1
  9. package/dist/deltas/receivers.d.ts.map +1 -1
  10. package/dist/deltas/receivers.js +2 -1
  11. package/dist/deltas/receivers.js.map +1 -1
  12. package/dist/deserializer.d.ts.map +1 -1
  13. package/dist/deserializer.js +3 -14
  14. package/dist/deserializer.js.map +1 -1
  15. package/dist/factory.d.ts +8 -0
  16. package/dist/factory.d.ts.map +1 -1
  17. package/dist/factory.js +25 -16
  18. package/dist/factory.js.map +1 -1
  19. package/dist/id-mapping.d.ts +21 -3
  20. package/dist/id-mapping.d.ts.map +1 -1
  21. package/dist/id-mapping.js +37 -17
  22. package/dist/id-mapping.js.map +1 -1
  23. package/dist/textualizer.js +1 -1
  24. package/dist/textualizer.js.map +1 -1
  25. package/dist/value-managers/annotations.d.ts.map +1 -1
  26. package/dist/value-managers/annotations.js +1 -2
  27. package/dist/value-managers/annotations.js.map +1 -1
  28. package/dist/value-managers/containments.d.ts +2 -2
  29. package/dist/value-managers/containments.d.ts.map +1 -1
  30. package/dist/value-managers/containments.js +89 -27
  31. package/dist/value-managers/containments.js.map +1 -1
  32. package/dist/value-managers/references.js +2 -2
  33. package/package.json +34 -34
  34. package/src/base-types.ts +0 -25
  35. package/src/deltas/inverters.ts +33 -9
  36. package/src/deltas/receivers.ts +2 -1
  37. package/src/deserializer.ts +3 -19
  38. package/src/factory.ts +30 -16
  39. package/src/id-mapping.ts +26 -5
  40. package/src/textualizer.ts +1 -1
  41. package/src/value-managers/annotations.ts +2 -2
  42. package/src/value-managers/containments.ts +87 -27
  43. package/src/value-managers/references.ts +2 -2
package/src/factory.ts CHANGED
@@ -15,35 +15,49 @@
15
15
  // SPDX-FileCopyrightText: 2025 TRUMPF Laser SE and other contributors
16
16
  // SPDX-License-Identifier: Apache-2.0
17
17
 
18
+ import { Language } from "@lionweb/core"
18
19
  import { lazyMapGet } from "@lionweb/ts-utils"
19
20
  import { ILanguageBase, NodeBaseFactory } from "./base-types.js"
20
21
  import { DeltaReceiver } from "./deltas/index.js"
21
22
 
23
+
22
24
  /**
23
- * @return a {@link NodeBaseFactory factory function} that works for all given {@link ILanguageBase language bases}.
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.
24
29
  */
25
- export const combinedFactoryFor = (languageBases: ILanguageBase[], receiveDelta?: DeltaReceiver): NodeBaseFactory => {
30
+ export const combinedLanguageBaseLookupFor = (languageBases: ILanguageBase[]): ((language: Language) => ILanguageBase) => {
26
31
  // create lookup map:
27
- const languageKey2version2factory: { [key: string]: { [version: string]: NodeBaseFactory } } = {}
32
+ const languageKey2version2base: { [key: string]: { [version: string]: ILanguageBase } } = {}
28
33
  languageBases.forEach((languageBase) => {
29
34
  const {key, version} = languageBase.language
30
- const version2factory = lazyMapGet(languageKey2version2factory, key, () => ({}))
31
- lazyMapGet(version2factory, version, () => languageBase.factory(receiveDelta))
32
- // (Note: don't destructure factory from languageBase, as that will unbind it as "this"!)
35
+ const version2base = lazyMapGet(languageKey2version2base, key, () => ({}))
36
+ lazyMapGet(version2base, version, () => languageBase)
33
37
  })
34
38
 
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`)
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`)
40
44
  }
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
+ 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(", ")}` : ``}`)
45
49
  }
46
- return factory(classifier, id)
50
+ return base
47
51
  }
48
52
  }
49
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
@@ -30,35 +30,56 @@ type NodesById = { [id: LionWebId]: INodeBase }
30
30
  */
31
31
  export class IdMapping {
32
32
 
33
- nodesById: NodesById;
33
+ private nodesById: NodesById;
34
34
  constructor(nodesById: NodesById) {
35
35
  this.nodesById = {...nodesById};
36
36
  }
37
37
  // TODO consider using an instance of Map<Id, INodeBase> instead
38
38
 
39
- fromId(id: LionWebId): INodeBase {
39
+ /**
40
+ * @return the {@link INodeBase node} with the given {@link LionWebId `id`}, or
41
+ * @throws an Error if no node with the given ID was registered.
42
+ */
43
+ fromId = (id: LionWebId): INodeBase => {
40
44
  if (!(id in this.nodesById)) {
41
45
  throw new Error(`node with id=${id} not in ID mapping`);
42
46
  }
43
47
  return this.nodesById[id];
44
48
  }
45
49
 
50
+ /**
51
+ * @return the {@link INodeBase node} with the given {@link LionWebId `id`},
52
+ * or `undefined` if no node with the given ID was registered.
53
+ */
46
54
  tryFromId = (id: LionWebId): (INodeBase | undefined) =>
47
55
  this.nodesById[id];
48
56
 
57
+ /**
58
+ * @return the {@link INodeBase node} referenced from the given {@link LionWebId ID},
59
+ * or `unresolved` if `unresolved` was passed in or no node with the given ID was registered.
60
+ */
49
61
  fromRefId = (idOrUnresolved: IdOrUnresolved): SingleRef<INodeBase> =>
50
- idOrUnresolved === null
51
- ? null
62
+ idOrUnresolved === unresolved
63
+ ? unresolved
52
64
  : (this.nodesById[idOrUnresolved] ?? unresolved);
53
65
 
54
66
  /**
55
67
  * Updates this {@link IdMapping} with the given `node` *and all its descendants* (recursively).
56
68
  */
57
- updateWith(node: INodeBase) {
69
+ updateWith= (node: INodeBase) => {
58
70
  this.nodesById[node.id] = node;
59
71
  node.children // recurse into all children
60
72
  .forEach((child) => this.updateWith(child));
61
73
  }
62
74
 
75
+ /**
76
+ * Re-initializes this {@link IdMapping ID mapping} with the given nodes-by-ID.
77
+ * This completely removes all registrations of nodes,
78
+ * and should only be used by components which are in complete control of the nodes being passed to this method.
79
+ */
80
+ reinitializeWith = (nodesById: NodesById) => {
81
+ this.nodesById = nodesById
82
+ }
83
+
63
84
  }
64
85
 
@@ -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 `$<not set>`
57
+ return `<not set>`
58
58
  }
59
59
  const value = valueManager.getDirectly()
60
60
  if (feature.type === LionCore_builtinsBase.INSTANCE.String) {
@@ -17,7 +17,7 @@
17
17
 
18
18
  import { action, observable } from "mobx"
19
19
 
20
- import { INodeBase, removeFromParent } from "../base-types.js"
20
+ import { INodeBase } from "../base-types.js"
21
21
  import { checkIndex, ValueManager } from "./base.js"
22
22
  import {
23
23
  AnnotationAddedDelta,
@@ -67,7 +67,7 @@ export class AnnotationsValueManager extends ValueManager {
67
67
  newAnnotation.attachTo(this.container, null);
68
68
  return false;
69
69
  } else {
70
- const oldIndex = removeFromParent(oldParent, newAnnotation);
70
+ const oldIndex = oldParent.annotationsValueManager.removeDirectly(newAnnotation);
71
71
  newAnnotation.attachTo(this.container, null);
72
72
  return [oldParent, oldIndex];
73
73
  }
@@ -18,12 +18,15 @@
18
18
  import { Containment } from "@lionweb/core"
19
19
  import { action, observable } from "mobx"
20
20
 
21
- import { INodeBase, removeFromParent } from "../base-types.js"
21
+ import { INodeBase } from "../base-types.js"
22
22
  import {
23
23
  ChildAddedDelta,
24
24
  ChildDeletedDelta,
25
- ChildMovedAndReplacedFromOtherContainmentInSameParentDelta, ChildMovedAndReplacedInSameContainmentDelta,
25
+ ChildMovedAndReplacedFromOtherContainmentDelta,
26
+ ChildMovedAndReplacedFromOtherContainmentInSameParentDelta,
27
+ ChildMovedAndReplacedInSameContainmentDelta,
26
28
  ChildMovedFromOtherContainmentDelta,
29
+ ChildMovedFromOtherContainmentInSameParentDelta,
27
30
  ChildMovedInSameContainmentDelta,
28
31
  ChildReplacedDelta
29
32
  } from "../deltas/index.js"
@@ -80,24 +83,38 @@ export abstract class SingleContainmentValueManager<T extends INodeBase> extends
80
83
  this.child.set(newChild);
81
84
  }
82
85
 
83
- @action replaceWith(newChild: T) {
84
- const oldChild = this.getDirectly();
85
- if (oldChild === undefined) {
86
+ @action replaceWith(movedChild: T) {
87
+ const replacedChild = this.getDirectly();
88
+ if (replacedChild === undefined) {
86
89
  // not a proper replace, but an add-set => delegate to regular setter (— unfortunately, through necessarily “unfolding the hierarchy”):
87
90
  if (this instanceof OptionalSingleContainmentValueManager) {
88
- this.set(newChild);
91
+ this.set(movedChild);
89
92
  }
90
93
  if (this instanceof RequiredSingleContainmentValueManager) {
91
- this.set(newChild);
94
+ this.set(movedChild);
92
95
  }
93
96
  } else {
94
- if (oldChild === newChild) {
97
+ if (replacedChild === movedChild) {
95
98
  // do nothing: nothing's changed
96
99
  } 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));
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();
101
118
  }
102
119
  }
103
120
  }
@@ -124,7 +141,7 @@ export class OptionalSingleContainmentValueManager<T extends INodeBase> extends
124
141
  } else {
125
142
  if (newChild.parent && newChild.containment) {
126
143
  const oldParent = newChild.parent;
127
- removeFromParent(oldParent, newChild);
144
+ removeFromContainment(newChild);
128
145
  this.emitDelta(() => new ChildMovedFromOtherContainmentDelta(oldParent, newChild.containment!, 0, this.container, this.feature, 0, newChild));
129
146
  } else {
130
147
  this.emitDelta(() => new ChildAddedDelta(this.container, this.feature, 0, newChild));
@@ -141,13 +158,14 @@ export class OptionalSingleContainmentValueManager<T extends INodeBase> extends
141
158
  if (oldChild === newChild) {
142
159
  // do nothing: nothing's changed
143
160
  } else {
161
+ // FIXME this could emit 2 deltas where it should be a single ChildReplaced-delta
144
162
  if (oldChild.parent && oldChild.containment && oldChild.parent === this.container && oldChild.containment === this.feature) {
145
163
  // FIXME oldChild.parent COULD be this.container
146
164
  this.emitDelta(() => new ChildDeletedDelta(this.container, this.feature, 0, oldChild));
147
165
  }
148
166
  oldChild.detach();
149
167
  if (newChild.parent && newChild.containment) {
150
- removeFromParent(newChild.parent, newChild);
168
+ removeFromContainment(newChild);
151
169
  }
152
170
  this.setDirectly(newChild);
153
171
  newChild.attachTo(this.container, this.feature);
@@ -183,7 +201,7 @@ export class RequiredSingleContainmentValueManager<T extends INodeBase> extends
183
201
  } else {
184
202
  if (newChild.parent && newChild.containment) {
185
203
  const oldParent = newChild.parent;
186
- removeFromParent(oldParent, newChild);
204
+ removeFromContainment(newChild);
187
205
  this.emitDelta(() => new ChildMovedFromOtherContainmentDelta(oldParent, newChild.containment!, 0, this.container, this.feature, 0, newChild));
188
206
  } else {
189
207
  this.emitDelta(() => new ChildAddedDelta(this.container, this.feature, 0, newChild));
@@ -204,7 +222,7 @@ export class RequiredSingleContainmentValueManager<T extends INodeBase> extends
204
222
  }
205
223
  oldChild.detach();
206
224
  if (newChild.parent && newChild.containment) {
207
- removeFromParent(newChild.parent, newChild);
225
+ removeFromContainment(newChild);
208
226
  }
209
227
  this.setDirectly(newChild);
210
228
  newChild.attachTo(this.container, this.feature);
@@ -251,16 +269,31 @@ export abstract class MultiContainmentValueManager<T extends INodeBase> extends
251
269
  this.children.splice(index, 0, newChild);
252
270
  }
253
271
 
254
- @action insertAtIndex(newChild: T, index: number) {
255
- this.insertAtIndexDirectly(newChild, index);
256
- if (newChild.parent === undefined && newChild.containment === undefined) {
257
- this.emitDelta(() => new ChildAddedDelta(this.container, this.containment, index, newChild));
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));
258
277
  } else {
259
- const oldIndex = removeFromParent(newChild.parent, newChild);
260
- this.emitDelta(() => new ChildMovedFromOtherContainmentDelta(newChild.parent!, newChild.containment!, oldIndex, this.container, this.containment, index, newChild));
261
- newChild.detach();
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
+ }
262
296
  }
263
- newChild.attachTo(this.container, this.containment);
264
297
  }
265
298
 
266
299
  @action removeDirectly(childToRemove: T): number {
@@ -298,18 +331,20 @@ export abstract class MultiContainmentValueManager<T extends INodeBase> extends
298
331
 
299
332
  @action replaceAtIndex(movedChild: T, newIndex: number) {
300
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
+ }
301
337
  const replacedChild = this.children[newIndex];
302
338
  if (replacedChild === movedChild) {
303
339
  // do nothing: nothing's changed
304
340
  } else {
305
341
  this.children.splice(newIndex, 1, movedChild);
306
- if (replacedChild.parent) {
342
+ if (replacedChild.parent !== undefined) {
307
343
  const oldValueManager = replacedChild.parent.getContainmentValueManager(replacedChild.containment!);
308
344
  const oldIndex = oldValueManager instanceof SingleContainmentValueManager
309
345
  ? 0
310
346
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
311
347
  : (oldValueManager as MultiContainmentValueManager<any>).children.indexOf(replacedChild);
312
- replacedChild.detach();
313
348
  if (replacedChild.parent === movedChild.parent) {
314
349
  if (replacedChild.containment === movedChild.containment) {
315
350
  this.emitDelta(() => new ChildMovedAndReplacedInSameContainmentDelta(this.container, this.containment, oldIndex, newIndex, movedChild, replacedChild));
@@ -317,8 +352,9 @@ export abstract class MultiContainmentValueManager<T extends INodeBase> extends
317
352
  this.emitDelta(() => new ChildMovedAndReplacedFromOtherContainmentInSameParentDelta(this.container, replacedChild.containment!, oldIndex, this.containment, newIndex, movedChild, replacedChild));
318
353
  }
319
354
  } else {
320
- this.emitDelta(() => new ChildMovedFromOtherContainmentDelta(replacedChild.parent!, replacedChild.containment!, oldIndex, this.container, this.containment, newIndex, movedChild));
355
+ this.emitDelta(() => new ChildMovedAndReplacedFromOtherContainmentDelta(this.container, this.containment, newIndex, movedChild, movedChild.parent!, movedChild.containment!, oldIndex, replacedChild));
321
356
  }
357
+ replacedChild.detach();
322
358
  } else {
323
359
  // not a move+replace, but a regular replace instead:
324
360
  this.emitDelta(() => new ChildReplacedDelta(this.container, this.containment, newIndex, replacedChild, movedChild));
@@ -380,3 +416,27 @@ export class RequiredMultiContainmentValueManager<T extends INodeBase> extends M
380
416
 
381
417
  }
382
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
+
@@ -106,7 +106,7 @@ export class OptionalSingleReferenceValueManager<T extends INodeBase> extends Si
106
106
  if (oldTarget === undefined) {
107
107
  this.emitDelta(() => new ReferenceAddedDelta(this.container, this.reference, 0, newTarget));
108
108
  } else {
109
- this.emitDelta(() => new ReferenceChangedDelta(this.container, this.reference, 0, oldTarget, newTarget));
109
+ this.emitDelta(() => new ReferenceChangedDelta(this.container, this.reference, 0, newTarget, oldTarget));
110
110
  }
111
111
  }
112
112
  }
@@ -142,7 +142,7 @@ export class RequiredSingleReferenceValueManager<T extends INodeBase> extends Si
142
142
  if (oldTarget === undefined) {
143
143
  this.emitDelta(() => new ReferenceAddedDelta(this.container, this.reference, 0, newTarget));
144
144
  } else {
145
- this.emitDelta(() => new ReferenceChangedDelta(this.container, this.reference, 0, oldTarget, newTarget));
145
+ this.emitDelta(() => new ReferenceChangedDelta(this.container, this.reference, 0, newTarget, oldTarget));
146
146
  }
147
147
  }
148
148
  }