From 9e327606eac2f606c73b8ddaf1bb68d5f11e3b3c Mon Sep 17 00:00:00 2001 From: "@nheuser" Date: Tue, 23 Jun 2026 00:54:23 +0200 Subject: [PATCH 1/9] added README.md with structured explanation for class methods --- .../lib/referencing/referencable/README.md | 309 ++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 projects/emfular/src/lib/referencing/referencable/README.md diff --git a/projects/emfular/src/lib/referencing/referencable/README.md b/projects/emfular/src/lib/referencing/referencable/README.md new file mode 100644 index 0000000..647e50d --- /dev/null +++ b/projects/emfular/src/lib/referencing/referencable/README.md @@ -0,0 +1,309 @@ +# Referencable Runtime + +This directory contains the reference runtime for EMFular model elements. The code here is responsible for object identity, containment parents, containment children, cross-object links, inverse reference updates, serialization references, deserialization hooks, and controlled list mutation. + +The implementation has three reference categories: + +- Tree references own their children and serialize them inline. +- Link references point to existing objects and serialize as `Ref` values. +- Shallow and derived references expose computed or parent relationships without owning serialized state. + +## Top-Level Files + +### `referenceable.ts` + +Defines `Referencable`, the abstract base class for generated model classes. + +Fields and metadata: + +- `$gId`: generated UUID for graphical identity. +- `$classMeta`: class metadata supplied by the binding layer. +- `$modelMeta`: model metadata supplied by the binding layer. +- `$ParentType`: TypeScript-only marker for the expected containment parent type. + +Public modeling API: + +- `$getEParent()`: returns the current containment parent by reading the hidden parent container. +- `$getEClass()`: resolves the instance eClass through `ModelRegistry`. +- `$destruct(mode = DeletionMode.RELAXED)`: removes this object from its containment parent, removes inverse links from linked objects, and deletes contained tree children. Parent removal is always performed in relaxed mode to avoid recursive deletion loops. +- `toJson(ctxOpt?)`: serializes the object. If no `SerializationContext` is provided, it walks to the tree root first and assigns `Ref` paths from the root downward before serializing attributes and references. + +Private serialization helpers: + +- `attributesToJson(json)`: serializes decorated attributes, skips `undefined`, empty strings, `false`, and configured default values, and respects `jsonName` aliases. +- `referencesToJson(json, ctx)`: serializes tree and link containers, omitting `undefined` and empty arrays. +- `initReferences()`: executes prototype reference initializers stored under `REFERENCE_INITIALIZERS`. + +`ReferenceApi` is the symbol-backed internal API exposed through `REFERENCE_INTERNAL_API`. Containers use it to interact with a `Referencable` without accessing private fields directly. + +Internal API methods: + +- `serialize_assignRefs(ctx, path)`: stores this object's `Ref` in the context and asks tree children to assign their refs. +- `deserializeAttributes(json)`: applies JSON attribute values and configured defaults. +- `deserializeChildren(context, parent, json)`: builds containment children from JSON. +- `deserializeOtherReferences(context, json)`: resolves link references and then resolves references inside tree children. +- `treeChildren()`: returns registered tree child containers. +- `otherLinks()`: returns registered link containers. +- `getContainer(refName)`: finds the reference metadata on the prototype chain and returns the initialized hidden container. +- `getParentContainer()`: returns the tree container that currently owns this instance. +- `setParentContainer(parent)`: switches the owner container, removing the instance from the previous owner first. +- `addToReference(name, item)`: adds an item to a named reference. Link containers use this for inverse updates. +- `removeFromReference(name, item, mode)`: removes an item from a named reference. In cascade mode, if a required reference becomes empty, the owning parent is destructed. + +### `referencable-symbols.ts` + +Exports the symbols used for hidden reference machinery. + +- `REFERENCE_INTERNAL_API`: key for each instance's internal reference API. +- `REFERENCE_INITIALIZERS`: key for per-class hidden container initializer functions. + +### `referenceable.spec.ts` + +Placeholder test suite for `Referencable` construction. + +## Base Container Files + +### `container/re-container.ts` + +Defines `ReContainer`, the abstract base for every reference container. + +Constructor state: + +- `_parent`: object that owns the container. +- `meta`: `ReferenceMeta` describing the reference. +- `referenceName`: name used for metadata and JSON fields. +- `inverseName`: configured opposite reference, if any. +- `isRequired`: true when `meta.min` is greater than zero. + +Methods: + +- `get()`: abstract read method returning one object, many objects, or `undefined`. +- `add(item)`: checks the item type against the target eClass and calls `addWithoutTypeCheck`; returns `false` for rejected types. +- `addWithoutTypeCheck(item)`: subclass hook for actual insertion. +- `isAcceptableItem(item)`: checks whether the item is an instance of the target class from `ModelRegistry`. +- `isAcceptableEclass(eClass)`: instantiates the class for an eClass and validates it with `isAcceptableItem`. +- `remove(item, mode?)`: abstract removal hook. +- `delete(mode?)`: abstract cleanup hook for deleting all values owned by the container. +- `toJson(ctx)`: abstract serialization hook. + +### `container/re-single-container.ts` + +Defines `ReSingleContainer`, the base for single-valued references. + +- `_instance`: protected optional stored reference. +- `get()`: returns `_instance`. + +### `container/re-list-container.ts` + +Defines `ReListContainer`, the base for list-valued references. + +- `_instance`: backing array. +- `proxy`: lazily-created `ModelList` wrapper from `createListProxy`. +- `get()`: returns the backing array. +- `delete(mode = RELAXED)`: destructs every element from the changing list through `ListUpdater`. +- `move(from, to)`: moves an item inside the backing array and throws on invalid indexes. +- `swap(from, to)`: swaps two items and throws on invalid indexes. + +### `container/re-single-interface.ts` + +Defines the single-reference interface. + +- `get()`: returns one referenced object or `undefined`. + +### `container/re-list-interface.ts` + +Defines the list-reference interface. + +- `proxy`: controlled array-like `ModelList`. +- `get()`: returns the referenced objects. +- `move(from, to)`: reorders an object. +- `swap(from, to)`: swaps two objects. + +## Tree Container Files + +Tree containers implement containment. They register themselves with `treeChildren()` on the parent, serialize children inline, and maintain each child's parent container. + +### `container/tree/re-tree-children-container.ts` + +Shared interface for containment containers. + +- `assignRefs(ctx, path)`: assigns serialization refs to contained children. +- `toJson(ctx)`: serializes contained children. +- `fromJson(formerPrefix, context, json)`: creates child backbones from JSON. +- `createRefsOnChildren(context, json)`: resolves child links after the containment backbone exists. + +### `container/tree/re-tree-single-container.ts` + +Single-valued containment container. + +- `constructor(parent, referenceName, refMeta, eClass?)`: stores an optional default eClass and registers the container as a tree child container. +- `assignRefs(ctx, path)`: assigns the contained child's ref path using the reference name. +- `toJson(ctx)`: serializes the contained child or returns `undefined`. +- `addWithoutTypeCheck(item)`: sets the item as the contained child and updates its parent container; returns `false` if the same item is already set. +- `remove(item, mode = RELAXED)`: in relaxed mode clears the reference and parent container; in cascade mode destructs the child. +- `delete(mode = RELAXED)`: cascades destruction in cascade mode, otherwise removes the current child from its parent container. +- `fromJson(formerPrefix, context, json)`: determines the eClass, creates a `Ref`, creates the tree backbone via `Deserializer`, and adds it. +- `createRefsOnChildren(context, json)`: resolves the contained child's non-containment references. + +### `container/tree/re-tree-list-container.ts` + +List-valued containment container. + +- `constructor(parent, name, refMeta, eClass?)`: stores an optional default eClass and registers the container as a tree child container. +- `assignRefs(ctx, path)`: assigns indexed child ref paths. +- `toJson(ctx)`: serializes each contained child. +- `addWithoutTypeCheck(item)`: moves the item out of its old parent, updates its parent container, and adds it if missing. +- `remove(item, mode = RELAXED)`: in relaxed mode removes the item and clears its parent container; in cascade mode destructs the item if present. +- `fromJson(formerPrefix, context, json)`: determines all child eClasses, creates child refs, creates child backbones, and adds them. +- `createRefsOnChildren(context, json)`: resolves each child's non-containment references when the JSON array length matches the current list. + +The adjacent `*.spec.ts` files verify containment behavior, parent updates, ordering, deserialization, and deletion modes. + +## Link Container Files + +Link containers implement non-containment references. They register with `otherLinks()` on the parent, serialize as refs from `SerializationContext`, and maintain configured opposite references. + +### `container/link/re-link-container.ts` + +Shared interface for link containers. + +- `removeFromInverse(item, mode?)`: removes inverse references related to the supplied item. +- `toJson(ctx)`: serializes links as one `Ref`, many `Ref` values, or `undefined`. + +### `container/link/re-link-single-container.ts` + +Single-valued non-containment link. + +- `constructor(parent, referenceName, refMeta)`: registers the container as a link container. +- `set(instance)`: replaces the stored instance and updates the configured inverse reference. The old inverse is removed in relaxed mode. +- `addWithoutTypeCheck(item)`: sets the link unless the item is already linked. +- `remove(item, mode = RELAXED)`: clears the link and removes the inverse reference when configured. +- `delete(mode = RELAXED)`: destructs the currently linked instance. +- `removeFromInverse(item, mode = RELAXED)`: asks the linked instance to remove the supplied item from the inverse reference. +- `toJson(ctx)`: returns the linked instance's `Ref` or `undefined`. + +### `container/link/re-link-list-container.ts` + +List-valued non-containment link. + +- `constructor(parent, name, refMeta)`: registers the container as a link container. +- `addWithoutTypeCheck(item)`: adds the item if missing and updates the configured inverse reference. +- `toJson(ctx)`: returns a `Ref` for every linked item. +- `remove(item, mode = RELAXED)`: removes the item and updates the configured inverse reference. +- `delete(mode = RELAXED)`: removes every linked item through `remove()`, so inverse references are cleaned up as the list is emptied. +- `removeFromInverse(item, mode = RELAXED)`: asks every linked child to remove the supplied item from the inverse reference. + +The adjacent specs cover single and list link behavior, including inverse updates and deletion behavior. + +## Shallow And Derived Container Files + +Shallow and derived containers expose relationships that are not stored as normal serialized references. + +### `container/shallow/re-shallow-interface.ts` + +Shared interface for shallow references. + +- `toJson(ctx)`: returns either `undefined` or an empty list because shallow references do not serialize owned state. + +### `container/shallow/re-tree-parent-container.ts` + +Single-valued shallow view of an object's containment parent. + +- `constructor(parent, referenceName, refMeta)`: creates the shallow parent container. The reference name is not used directly. +- `get()`: returns `_parent.$getEParent()`. +- `addWithoutTypeCheck(item)`: adds this object to the inverse tree reference on `item`. +- `remove(item, mode = RELAXED)`: removes this object from the inverse tree reference on `item`. +- `delete()`: no-op. +- `toJson(ctx)`: returns `undefined`. + +### `container/shallow/re-derivation-resolver.ts` + +Helper for derived references. + +- `constructor(computeOrSymbol)`: accepts either a compute function or a symbol identifying a method on the parent. +- `resolve(parent)`: resolves and caches the compute function, binding symbol-derived methods to the parent instance, then returns the computed value. + +### `container/shallow/re-derived-single-container.ts` + +Read-only single-valued derived reference. + +- `constructor(parent, computeOrSymbol, referenceName, refMeta)`: creates a resolver-backed reference. +- `get()`: returns the resolver result. +- `toJson(ctx)`: returns `undefined`. +- `addWithoutTypeCheck(item)`: returns `false`. +- `remove(item)`: returns `false`. +- `delete()`: no-op. + +### `container/shallow/re-derived-list-container.ts` + +Read-only list-valued derived reference. + +- `constructor(parent, computeOrSymbol, referenceName, refMeta)`: creates a resolver-backed derived list. +- `proxy`: lazily-created controlled `ModelList`. +- `get()`: returns a defensive copy of the resolver result. +- `toJson(ctx)`: returns an empty list. +- `addWithoutTypeCheck(item)`: returns `false`. +- `remove(item)`: returns `false`. +- `delete()`: no-op. +- `move(from, to)`: no-op. +- `swap(from, to)`: no-op. + +The shallow specs cover parent references and derived single/list resolution. + +## Hidden List Proxy Files + +### `container/hide/model-list.ts` + +Defines the array-like types used by list containers. + +- `RefKind`: union of `tree`, `link`, and `parent` reference kinds. +- `SingleRef`: single-value reference type alias. +- `ModelList`: public list proxy type alias. +- `MetaAwareModelList`: array interface with controlled mutation helpers. + +`MetaAwareModelList` adds: + +- `move(from, to)` +- `swap(from, to)` +- `remove(...items)` +- `removeCascade(...items)` +- `delete()` +- `__item` and `__kind` type markers + +Direct index assignment is marked deprecated because the proxy rejects it at runtime. + +### `container/hide/list-proxy.ts` + +Defines `createListProxy(container)`, which wraps a `ReListInterface` in an array-like `Proxy`. + +Read behavior: + +- Numeric property reads return items from `container.get()`. +- `length`, `in`, own keys, and iteration reflect the current container contents. +- Non-mutating array methods are delegated to `Array.prototype` on the current list. + +Supported mutation behavior: + +- `push(...items)`: adds through `container.add()` and returns the new length. +- `remove(...items)`: removes items in relaxed mode and returns whether any removal happened. +- `removeCascade(...items)`: removes items in cascade mode and returns whether any removal happened. +- `delete(mode?)`: calls `container.delete(mode)`. +- `pop(mode?)`: removes and returns the last item. +- `shift(mode?)`: removes and returns the first item. +- `unshift(...items)`: rebuilds the list with new items at the front. +- `splice(start, deleteCount, ...items)`: removes, adds, and reorders through container operations. +- `move(from, to)`: delegates to `container.move()`. +- `swap(from, to)`: bounds-checks and swaps via container moves. +- `sort(compareFn?)`: sorts a copy and reorders through `container.move()`. +- `reverse()`: reverses by reordering through `container.move()`. + +Rejected operations: + +- `at()` +- `fill()` +- `copyWithin()` +- Direct numeric index assignment +- Direct `length` assignment +- Deleting non-index properties + +Deleting a numeric index destructs the object at that index when the index exists. From 277ece3c3e48c0948a3a1f2015e6aedf587e6812 Mon Sep 17 00:00:00 2001 From: Noah1891 Date: Tue, 23 Jun 2026 11:21:52 +0200 Subject: [PATCH 2/9] Update README --- .../emfular/src/lib/referencing/referencable/README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/projects/emfular/src/lib/referencing/referencable/README.md b/projects/emfular/src/lib/referencing/referencable/README.md index 647e50d..cdae840 100644 --- a/projects/emfular/src/lib/referencing/referencable/README.md +++ b/projects/emfular/src/lib/referencing/referencable/README.md @@ -57,10 +57,6 @@ Exports the symbols used for hidden reference machinery. - `REFERENCE_INTERNAL_API`: key for each instance's internal reference API. - `REFERENCE_INITIALIZERS`: key for per-class hidden container initializer functions. -### `referenceable.spec.ts` - -Placeholder test suite for `Referencable` construction. - ## Base Container Files ### `container/re-container.ts` @@ -157,8 +153,6 @@ List-valued containment container. - `fromJson(formerPrefix, context, json)`: determines all child eClasses, creates child refs, creates child backbones, and adds them. - `createRefsOnChildren(context, json)`: resolves each child's non-containment references when the JSON array length matches the current list. -The adjacent `*.spec.ts` files verify containment behavior, parent updates, ordering, deserialization, and deletion modes. - ## Link Container Files Link containers implement non-containment references. They register with `otherLinks()` on the parent, serialize as refs from `SerializationContext`, and maintain configured opposite references. @@ -193,8 +187,6 @@ List-valued non-containment link. - `delete(mode = RELAXED)`: removes every linked item through `remove()`, so inverse references are cleaned up as the list is emptied. - `removeFromInverse(item, mode = RELAXED)`: asks every linked child to remove the supplied item from the inverse reference. -The adjacent specs cover single and list link behavior, including inverse updates and deletion behavior. - ## Shallow And Derived Container Files Shallow and derived containers expose relationships that are not stored as normal serialized references. @@ -248,8 +240,6 @@ Read-only list-valued derived reference. - `move(from, to)`: no-op. - `swap(from, to)`: no-op. -The shallow specs cover parent references and derived single/list resolution. - ## Hidden List Proxy Files ### `container/hide/model-list.ts` From 4c242d4efd972f7d4bea74cc4ab03c71c8a970fb Mon Sep 17 00:00:00 2001 From: noah Date: Sat, 12 Sep 2026 14:52:59 +0200 Subject: [PATCH 3/9] deleted old structure --- .idea/.gitignore | 10 + .idea/EMFular-core.iml | 8 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .../README.md => README_referncable.md | 0 projects/emfular/README.md | 38 --- projects/emfular/ng-package.json | 7 - projects/emfular/package-lock.json | 84 ----- projects/emfular/package.json | 21 -- .../src/lib/binding/attribute-collector.ts | 23 -- .../src/lib/binding/attribute-decorator.ts | 21 -- .../src/lib/binding/eclass-decorator.ts | 24 -- .../src/lib/binding/model-definition.ts | 27 -- .../src/lib/binding/model-registry.spec.ts | 7 - .../emfular/src/lib/binding/model-registry.ts | 37 --- .../src/lib/binding/model-root.spec.ts | 14 - .../emfular/src/lib/binding/model-root.ts | 10 - .../src/lib/binding/reference-creator.ts | 51 --- .../src/lib/binding/reference-decorator.ts | 79 ----- .../src/lib/binding/reference-typing.ts | 17 - .../lib/referencing/ref/ref-handler.spec.ts | 56 ---- .../src/lib/referencing/ref/ref-handler.ts | 49 --- .../emfular/src/lib/referencing/ref/ref.ts | 6 - .../referencable/container/hide/list-proxy.ts | 299 ------------------ .../referencable/container/hide/model-list.ts | 28 -- .../container/link/re-link-container.ts | 20 -- .../link/re-link-list-container.spec.ts | 91 ------ .../container/link/re-link-list-container.ts | 58 ---- .../link/re-link-single-container.spec.ts | 101 ------ .../link/re-link-single-container.ts | 70 ---- .../referencable/container/re-container.ts | 55 ---- .../container/re-list-container.ts | 62 ---- .../container/re-list-interface.ts | 16 - .../container/re-single-container.ts | 21 -- .../container/re-single-interface.ts | 9 - .../shallow/re-derivation-resolver.spec.ts | 7 - .../shallow/re-derivation-resolver.ts | 27 -- .../shallow/re-derived-list-container.spec.ts | 9 - .../shallow/re-derived-list-container.ts | 60 ---- .../re-derived-single-container.spec.ts | 9 - .../shallow/re-derived-single-container.ts | 34 -- .../container/shallow/re-shallow-interface.ts | 10 - .../shallow/re-tree-parent-container.spec.ts | 13 - .../shallow/re-tree-parent-container.ts | 40 --- .../tree/re-tree-children-container.ts | 18 -- .../tree/re-tree-list-container.spec.ts | 90 ------ .../container/tree/re-tree-list-container.ts | 98 ------ .../tree/re-tree-single-container.spec.ts | 56 ---- .../tree/re-tree-single-container.ts | 89 ------ .../referencable/referencable-symbols.ts | 4 - .../referencable/referenceable.spec.ts | 5 - .../referencing/referencable/referenceable.ts | 287 ----------------- .../test/default-eclass-test.spec.ts | 44 --- .../re-containers-with-inheritance.spec.ts | 43 --- .../test/re-containers-with-inheritance.ts | 78 ----- .../re-containers-with-single-child.spec.ts | 91 ------ .../test/re-containers-with-single-child.ts | 191 ----------- .../test/referencable-tester.spec.ts | 9 - .../referencing/test/referencable-tester.ts | 26 -- .../test/referencables-with-children.spec.ts | 154 --------- .../test/referencables-with-children.ts | 192 ----------- .../lib/serialization/deserializer.spec.ts | 7 - .../src/lib/serialization/deserializer.ts | 62 ---- .../lib/serialization/json-deserializable.ts | 63 ---- .../serialization-context.spec.ts | 7 - .../serialization/serialization-context.ts | 19 -- .../emfular/src/lib/utils/deletion-mode.ts | 4 - .../src/lib/utils/json-comparer.spec.ts | 57 ---- .../emfular/src/lib/utils/json-comparer.ts | 130 -------- .../src/lib/utils/list-updater.spec.ts | 44 --- .../emfular/src/lib/utils/list-updater.ts | 40 --- projects/emfular/src/public-api.ts | 43 --- projects/emfular/tsconfig.lib.json | 15 - projects/emfular/tsconfig.lib.prod.json | 11 - projects/emfular/tsconfig.spec.json | 15 - 75 files changed, 32 insertions(+), 3602 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/EMFular-core.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml rename projects/emfular/src/lib/referencing/referencable/README.md => README_referncable.md (100%) delete mode 100644 projects/emfular/README.md delete mode 100644 projects/emfular/ng-package.json delete mode 100644 projects/emfular/package-lock.json delete mode 100644 projects/emfular/package.json delete mode 100644 projects/emfular/src/lib/binding/attribute-collector.ts delete mode 100644 projects/emfular/src/lib/binding/attribute-decorator.ts delete mode 100644 projects/emfular/src/lib/binding/eclass-decorator.ts delete mode 100644 projects/emfular/src/lib/binding/model-definition.ts delete mode 100644 projects/emfular/src/lib/binding/model-registry.spec.ts delete mode 100644 projects/emfular/src/lib/binding/model-registry.ts delete mode 100644 projects/emfular/src/lib/binding/model-root.spec.ts delete mode 100644 projects/emfular/src/lib/binding/model-root.ts delete mode 100644 projects/emfular/src/lib/binding/reference-creator.ts delete mode 100644 projects/emfular/src/lib/binding/reference-decorator.ts delete mode 100644 projects/emfular/src/lib/binding/reference-typing.ts delete mode 100644 projects/emfular/src/lib/referencing/ref/ref-handler.spec.ts delete mode 100644 projects/emfular/src/lib/referencing/ref/ref-handler.ts delete mode 100644 projects/emfular/src/lib/referencing/ref/ref.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/hide/list-proxy.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/hide/model-list.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/link/re-link-container.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.spec.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.spec.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/re-container.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/re-list-container.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/re-list-interface.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/re-single-container.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/re-single-interface.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/shallow/re-derivation-resolver.spec.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/shallow/re-derivation-resolver.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-list-container.spec.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-list-container.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-single-container.spec.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-single-container.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/shallow/re-shallow-interface.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/shallow/re-tree-parent-container.spec.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/shallow/re-tree-parent-container.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-children-container.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.spec.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.spec.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/referencable-symbols.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/referenceable.spec.ts delete mode 100644 projects/emfular/src/lib/referencing/referencable/referenceable.ts delete mode 100644 projects/emfular/src/lib/referencing/test/default-eclass-test.spec.ts delete mode 100644 projects/emfular/src/lib/referencing/test/re-containers-with-inheritance.spec.ts delete mode 100644 projects/emfular/src/lib/referencing/test/re-containers-with-inheritance.ts delete mode 100644 projects/emfular/src/lib/referencing/test/re-containers-with-single-child.spec.ts delete mode 100644 projects/emfular/src/lib/referencing/test/re-containers-with-single-child.ts delete mode 100644 projects/emfular/src/lib/referencing/test/referencable-tester.spec.ts delete mode 100644 projects/emfular/src/lib/referencing/test/referencable-tester.ts delete mode 100644 projects/emfular/src/lib/referencing/test/referencables-with-children.spec.ts delete mode 100644 projects/emfular/src/lib/referencing/test/referencables-with-children.ts delete mode 100644 projects/emfular/src/lib/serialization/deserializer.spec.ts delete mode 100644 projects/emfular/src/lib/serialization/deserializer.ts delete mode 100644 projects/emfular/src/lib/serialization/json-deserializable.ts delete mode 100644 projects/emfular/src/lib/serialization/serialization-context.spec.ts delete mode 100644 projects/emfular/src/lib/serialization/serialization-context.ts delete mode 100644 projects/emfular/src/lib/utils/deletion-mode.ts delete mode 100644 projects/emfular/src/lib/utils/json-comparer.spec.ts delete mode 100644 projects/emfular/src/lib/utils/json-comparer.ts delete mode 100644 projects/emfular/src/lib/utils/list-updater.spec.ts delete mode 100644 projects/emfular/src/lib/utils/list-updater.ts delete mode 100644 projects/emfular/src/public-api.ts delete mode 100644 projects/emfular/tsconfig.lib.json delete mode 100644 projects/emfular/tsconfig.lib.prod.json delete mode 100644 projects/emfular/tsconfig.spec.json diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..30cf57e --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/EMFular-core.iml b/.idea/EMFular-core.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/EMFular-core.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..0ce8094 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/projects/emfular/src/lib/referencing/referencable/README.md b/README_referncable.md similarity index 100% rename from projects/emfular/src/lib/referencing/referencable/README.md rename to README_referncable.md diff --git a/projects/emfular/README.md b/projects/emfular/README.md deleted file mode 100644 index f174bfe..0000000 --- a/projects/emfular/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# EMFular - -This small TS library deals with the conversion between "flat" model representations of [emf-jackson](https://emfjson.github.io/projects/jackson/latest/), which we call **json** representation, and a traversable in-memory representation which we call **core** representation. -Basically, it converts the XPath-based references of [emf-jackson](https://emfjson.github.io/projects/jackson/latest/) into object references and vice versa. -It consists of two parts: -1. **Referencing:** defines the main properties both for the json (flat) and the core (traversable) representation. It also holds methods for serialization. -2. **Deserialization:** deals with the conversion of json models into core models. - -## Referencing -Referencing consists of two classes: - * The **Ref** class consists of an XPath reference $ref and the EClass eClass. It holds basic facilities to work with XPath, including traversal and path concatenation and is the basis for working with references in the JSON form that emf-jackson exports. - * The **Referencable** class defines a contract for the core representation: Referencables have a UUID gId which is used for global identification and cross-referencing on graphical components. There are also references to all EMF-tree-children. These relationships are used on object creation and destruction. Each object also holds a Ref which might be out-dated but is updated and used on serialization into json. The necessary methods to prepare the XPath references are included. - -## Deserialization - -Deserialization handles the construction of the core model from the json model. -We handle the replacement of Ref references by object references during object construction. -Hence, we need a deserializer that manages the already created objects and constructs new ones on demand. -Basically, the whole construction process is triggered by calling the constructor of the EMF model's root element with the deserializer as only parameter. - -The objects' constructors are responsible for the correct creation of their representations. -The deserializer offers general methods to help them and relies on certain types, that the library exposes: - -* **Deserializer context management:** The deserializer stores objects via put, and delivers objects on getOrCreate(ref: Ref). (See below for an explanation of the creation process). -* **Access to json parts:** There is a method getJsonFromTree($ref: string) to get the json for a specific XPath. -* **Helper methods:** Additionally, there are methods to adapt eClasses. Emf-jackson sometimes omits them but since our deserialization heavily relies on them, we need to set them right upfront. - -### Object Creation by the Deserializer -The creation of new objects relies on a **ConstructorPointer** that should be available for the type of the object that is identified by its EClass. -These pointers for all EClasses are a necessary parameter when creating a deserializer. -``export type ConstructorPointer = ($ref:string) => (p: Deserializer) => Referencable;`` -The definition of these pointers must be handled by the programmer. - -We recommend to write the ConstructorPointer so that it first sets the complete Ref and then calls a (dummy) constructor with the deserializer and the Ref as parameters. There is a typed helper ConstructorPointerFor for all ConstructorPointer definitions. -The deserializer based object construction needs to be done on the constructor. This way, the object can be put into the deserializer as soon as it exists. -The deserializer offers the method getJsonFromTree($ref: string) to access all relevant attributes and XPath references from the json model. - -In our current reference implementation, [KEML.web](https://github.com/keml-group/keml.web), the interplay of deserializer and constructors is visible. diff --git a/projects/emfular/ng-package.json b/projects/emfular/ng-package.json deleted file mode 100644 index b79e53d..0000000 --- a/projects/emfular/ng-package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", - "dest": "../../dist/emfular", - "lib": { - "entryFile": "src/public-api.ts" - } -} \ No newline at end of file diff --git a/projects/emfular/package-lock.json b/projects/emfular/package-lock.json deleted file mode 100644 index 314fc4e..0000000 --- a/projects/emfular/package-lock.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "name": "emfular", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "emfular", - "version": "0.0.1", - "dependencies": { - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/common": "^19.2.0", - "@angular/core": "^19.2.0", - "@types/uuid": "^10.0.0" - } - }, - "node_modules/@angular/common": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.2.4.tgz", - "integrity": "sha512-5iBerI1hkY8rAt0gZQgOlfzR69jj5j25JyfkDOhdZhezE0pqhDc69OnbkUM20LTau4bFRYOj015eiKWzE2DOzQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "@angular/core": "19.2.4", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/core": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.2.4.tgz", - "integrity": "sha512-ZuSMg+LWG0ADLEvMzSqU+D6M5KcQtxBssEFq4UskGIYuvNGqC91hAl4sbnXDQ5C7GgFcLY6ouaemS6dBOIfc/g==", - "license": "MIT", - "peer": true, - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0" - }, - "peerDependencies": { - "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.15.0" - } - }, - "node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "license": "MIT", - "peer": true - }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/zone.js": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.0.tgz", - "integrity": "sha512-9oxn0IIjbCZkJ67L+LkhYWRyAy7axphb3VgE2MBDlOqnmHMPWGYMxJxBYFueFq/JGY2GMwS0rU+UCLunEmy5UA==", - "license": "MIT", - "peer": true - } - } -} diff --git a/projects/emfular/package.json b/projects/emfular/package.json deleted file mode 100644 index d783a50..0000000 --- a/projects/emfular/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "emfular", - "version": "10.1.0", - "description": "Pure TS library with basic types and parser to move from XPath references to runtime models", - "keywords": ["XPath", "Parser", "TS", "EMF"], - "author": { - "name": "Susanne Göbel", - "email": "goebel@uni-koblenz.de" - }, - "repository": { - "type": "git", - "url": "https://github.com/softlang/EMFular/tree/main/projects/emfular" - }, - "peerDependencies": { - "@types/uuid": "^10.0.0" - }, - "dependencies": { - "tslib": "^2.3.0" - }, - "sideEffects": false -} diff --git a/projects/emfular/src/lib/binding/attribute-collector.ts b/projects/emfular/src/lib/binding/attribute-collector.ts deleted file mode 100644 index 8c84822..0000000 --- a/projects/emfular/src/lib/binding/attribute-collector.ts +++ /dev/null @@ -1,23 +0,0 @@ -import "reflect-metadata"; -import { ATTRIBUTES_KEY, AttributeOptions } from "./attribute-decorator"; - -export function getAllAttributes(ctor: Function): Map { - const result = new Map(); - - let current = ctor.prototype; - while (current && current !== Object.prototype) { - const attrs: Record = - Reflect.getMetadata(ATTRIBUTES_KEY, current) || {}; - - // base class attributes should not override subclass attributes - for (const key of Object.keys(attrs)) { - if (!result.has(key)) { - result.set(key, attrs[key]); - } - } - - current = Object.getPrototypeOf(current); - } - - return result; -} diff --git a/projects/emfular/src/lib/binding/attribute-decorator.ts b/projects/emfular/src/lib/binding/attribute-decorator.ts deleted file mode 100644 index 01ce55e..0000000 --- a/projects/emfular/src/lib/binding/attribute-decorator.ts +++ /dev/null @@ -1,21 +0,0 @@ -// attribute-decorator.ts -import "reflect-metadata"; - -export const ATTRIBUTES_KEY = Symbol("attributes"); - -export interface AttributeOptions { - default?: any; - jsonName?: string; -} - -export function attribute(options: AttributeOptions = {}) { - return function (target: any, propertyKey: string) { - // target is the prototype - const existing: Record = - Reflect.getMetadata(ATTRIBUTES_KEY, target) || {}; - - existing[propertyKey] = options; - - Reflect.defineMetadata(ATTRIBUTES_KEY, existing, target); - }; -} diff --git a/projects/emfular/src/lib/binding/eclass-decorator.ts b/projects/emfular/src/lib/binding/eclass-decorator.ts deleted file mode 100644 index 1e94fd6..0000000 --- a/projects/emfular/src/lib/binding/eclass-decorator.ts +++ /dev/null @@ -1,24 +0,0 @@ -import 'reflect-metadata'; -import {ModelRegistry} from "./model-registry"; -import {ClassMeta, ModelDefinition} from "./model-definition"; - -export const ECLASS_METADATA_KEY = "eClass"; - - -export function eClass(model: ModelDefinition, className: keyof typeof model.classes): ClassDecorator { - return function (ctor: any) { - - const classMeta: ClassMeta = model.classes[className]; - if (!classMeta) { - throw new Error(`Class ${String(className)} not found in model ${model.uri}`); - } - - ctor.prototype.$classMeta = classMeta; - ctor.prototype.$modelUri = model.uri; - ctor.prototype.$modelMeta = model - - const eClass = model.uri + className; - Reflect.defineMetadata(ECLASS_METADATA_KEY, eClass, ctor); - ModelRegistry.register(eClass, ctor); - }; -} diff --git a/projects/emfular/src/lib/binding/model-definition.ts b/projects/emfular/src/lib/binding/model-definition.ts deleted file mode 100644 index fe38275..0000000 --- a/projects/emfular/src/lib/binding/model-definition.ts +++ /dev/null @@ -1,27 +0,0 @@ -export type TsEnum = Record; - -export interface ReferenceMeta { - target: string; // name of target class - containment?: true; - isParent?: true; - derivingMethod?: symbol; - opposite?: string; - min?: number; - max?: number; - - // assigned by @reference - containerKey?: symbol -} - -export interface ClassMeta { - references: Record; -} - -export interface ModelDefinition { - name: string; - prefix: string; - uri: string; - - classes: Record; - enums?: Record; -} \ No newline at end of file diff --git a/projects/emfular/src/lib/binding/model-registry.spec.ts b/projects/emfular/src/lib/binding/model-registry.spec.ts deleted file mode 100644 index e9f4be4..0000000 --- a/projects/emfular/src/lib/binding/model-registry.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ModelRegistry } from './model-registry'; - -describe('ModelRegistry', () => { - it('should create an instance', () => { - expect(new ModelRegistry()).toBeTruthy(); - }); -}); diff --git a/projects/emfular/src/lib/binding/model-registry.ts b/projects/emfular/src/lib/binding/model-registry.ts deleted file mode 100644 index dc52e86..0000000 --- a/projects/emfular/src/lib/binding/model-registry.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Referencable } from "../referencing/referencable/referenceable"; -import { JsonDeserializable } from "../serialization/json-deserializable"; -import {ECLASS_METADATA_KEY} from "./eclass-decorator"; - -export class ModelRegistry { - private static map = new Map>(); - - static register>( - key: string, - cls: JsonDeserializable - ) { - this.map.set(key, cls); - } - - static get>(key: string): JsonDeserializable { - const entry = this.map.get(key); - if (!entry) { - throw new Error(`Unable to find a constructor for ${key}`); - } - return entry as JsonDeserializable; - } - - static getEClassForInstance(instance: Referencable): string { - const ctor = instance.constructor as Function - return this.getEClassForConstructor(ctor) - } - - static getEClassForConstructor(ctor: Function): string { - const eClass = Reflect.getMetadata(ECLASS_METADATA_KEY, ctor); - if (!eClass) { - throw new Error( `ModelRegistry: Class '${ctor.name}' has no EClass metadata. ` - + `Did you forget to add @eClass or @autoEClass?` - ); - } - return eClass; - } -} diff --git a/projects/emfular/src/lib/binding/model-root.spec.ts b/projects/emfular/src/lib/binding/model-root.spec.ts deleted file mode 100644 index 9a553e7..0000000 --- a/projects/emfular/src/lib/binding/model-root.spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ModelRoot } from './model-root'; - -class RootTester extends ModelRoot { - constructor() { - super(); - } - -} - -describe('ModelRoot', () => { - it('should create an instance', () => { - expect(new RootTester()).toBeTruthy(); - }); -}); diff --git a/projects/emfular/src/lib/binding/model-root.ts b/projects/emfular/src/lib/binding/model-root.ts deleted file mode 100644 index 1b29860..0000000 --- a/projects/emfular/src/lib/binding/model-root.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {Referencable} from "../referencing/referencable/referenceable"; -import {ModelDefinition} from "./model-definition"; - -export abstract class ModelRoot extends Referencable { - static modelDefinition: ModelDefinition; - - protected constructor() { - super(); - } -} diff --git a/projects/emfular/src/lib/binding/reference-creator.ts b/projects/emfular/src/lib/binding/reference-creator.ts deleted file mode 100644 index d0cb808..0000000 --- a/projects/emfular/src/lib/binding/reference-creator.ts +++ /dev/null @@ -1,51 +0,0 @@ -import {Referencable} from "../referencing/referencable/referenceable"; -import {ReferenceMeta} from "./model-definition"; -import {ReContainer} from "../referencing/referencable/container/re-container"; -import {ReTreeParentContainer} from "../referencing/referencable/container/shallow/re-tree-parent-container"; -import {ReTreeListContainer} from "../referencing/referencable/container/tree/re-tree-list-container"; -import {ReTreeSingleContainer} from "../referencing/referencable/container/tree/re-tree-single-container"; -import {ReLinkListContainer} from "../referencing/referencable/container/link/re-link-list-container"; -import {ReLinkSingleContainer} from "../referencing/referencable/container/link/re-link-single-container"; -import {ReDerivedListContainer} from "../referencing/referencable/container/shallow/re-derived-list-container"; -import {ReDerivedSingleContainer} from "../referencing/referencable/container/shallow/re-derived-single-container"; - - -export function createContainer< - T extends Referencable, - P extends Referencable ->( - parent: P, - meta: ReferenceMeta, - propertyKey: string -): ReContainer { - - - if (meta.isParent) { - return new ReTreeParentContainer

(parent, propertyKey, meta) - } - - const isList = meta.max !== 1; - - if(meta.derivingMethod) { - if (isList) { - return new ReDerivedListContainer(parent, meta.derivingMethod, propertyKey, meta) - } else { - return new ReDerivedSingleContainer(parent, meta.derivingMethod, propertyKey, meta) - } - } - - if (meta.containment) { - const defaultEClass = parent.$modelMeta.uri + meta.target; - if (isList) { - return new ReTreeListContainer(parent, propertyKey, meta, defaultEClass ) - } else { - return new ReTreeSingleContainer(parent, propertyKey, meta, defaultEClass ) - } - } - - if (isList) { - return new ReLinkListContainer(parent, propertyKey, meta); - } else { - return new ReLinkSingleContainer(parent, propertyKey, meta); - } -} diff --git a/projects/emfular/src/lib/binding/reference-decorator.ts b/projects/emfular/src/lib/binding/reference-decorator.ts deleted file mode 100644 index 16be0d7..0000000 --- a/projects/emfular/src/lib/binding/reference-decorator.ts +++ /dev/null @@ -1,79 +0,0 @@ -import {Referencable} from "../referencing/referencable/referenceable"; -import {ReferenceMeta} from "./model-definition"; -import {createContainer} from "./reference-creator"; -import {ReSingleInterface} from "../referencing/referencable/container/re-single-interface"; -import {KindFromMeta, RefineReference} from "./reference-typing"; -import {ReListInterface} from "../referencing/referencable/container/re-list-interface"; -import {REFERENCE_INITIALIZERS} from "../referencing/referencable/referencable-symbols"; - -export function reference, M extends ReferenceMeta>( - meta: M -): PropertyDecorator { - - return function (prototype: any, propertyKey: string | symbol) { - - const symbol = Symbol(String(propertyKey)); - meta.containerKey = symbol; - - // infer kind + multiplicity from meta - type Kind = KindFromMeta; - type IsList = M["max"] extends 1 ? false : true; - - // user-declared type (TS substitutes this automatically) - type UserType = any; - - // final refined type - type FinalType = RefineReference< - UserType, - T, - Kind, - IsList - >; - - if ( meta.max !== 1) { - Object.defineProperty(prototype, propertyKey, { - get(this: any): FinalType { - const c = this[symbol] as ReListInterface; - return c.proxy as FinalType; - }, - set(_: T | null) { - throw new Error( - `Cannot assign directly to multi-valued reference '${String(propertyKey)}'. Use list operations instead.` - ); - }, - enumerable: true, - configurable: true - }); - } else { - Object.defineProperty(prototype, propertyKey, { - get(this: any): FinalType { - const c = this[symbol] as ReSingleInterface; - return c?.get() as FinalType; - }, - set(this: any, value: T | null) { - const c = this[symbol] as ReSingleInterface; - if (!c) throw new Error("Container not initialized"); - if (value == null) { - const val = c.get(); - if (val) c.remove(val); - } else { - c.add(value); - } - }, - enumerable: true, - configurable: true - }); - - } - - if (!prototype[REFERENCE_INITIALIZERS]) { - prototype[REFERENCE_INITIALIZERS] = []; - } - (prototype[REFERENCE_INITIALIZERS] as Array<(this: any) => void>) - .push(function (this: any) { - this[symbol] = createContainer( - this, meta, String(propertyKey) - ); - }); - }; -} diff --git a/projects/emfular/src/lib/binding/reference-typing.ts b/projects/emfular/src/lib/binding/reference-typing.ts deleted file mode 100644 index d393766..0000000 --- a/projects/emfular/src/lib/binding/reference-typing.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {ReferenceMeta} from "./model-definition"; -import {MetaAwareModelList, RefKind, SingleRef} from "../referencing/referencable/container/hide/model-list"; - -export type KindFromMeta = - M["isParent"] extends true ? "parent" : - M["containment"] extends true ? "tree" : - "link"; - -export type RefineReference< - UserType, - Target, - Kind extends RefKind, - IsList extends boolean -> = - IsList extends true - ? MetaAwareModelList & UserType - : SingleRef & UserType; diff --git a/projects/emfular/src/lib/referencing/ref/ref-handler.spec.ts b/projects/emfular/src/lib/referencing/ref/ref-handler.spec.ts deleted file mode 100644 index 69c03ad..0000000 --- a/projects/emfular/src/lib/referencing/ref/ref-handler.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { RefHandler } from './ref-handler'; - -describe('RefHandler', () => { - it('should create an instance', () => { - expect(new RefHandler()).toBeTruthy(); - }); - - it('getIndexFromString: should deliver the final part (after last .) from the string as number', () => { - expect(RefHandler.getIndexFromString('/...../vhgvh.78')).toBe(78); - }) - - it('computePrefix: should combine prefix and suffix into path', () => { - expect(RefHandler.computePrefix('', '')).toEqual('/@') - expect(RefHandler.computePrefix('hakvhv.kjlb7889', 'bjklbj')).toEqual('hakvhv.kjlb7889/@bjklbj') - }) - - it('getParentAddress: should remove own part from path', () => { - expect(RefHandler.getParentAddress('//fd/@g.89@j.89/h/gh.6')).toEqual('//fd') - expect(RefHandler.getParentAddress('//fd/@g.89@j.89/h/@gh.6')).toEqual('//fd/@g.89@j.89/h') - expect(RefHandler.getParentAddress('//fd/@g.89@j.89/h/@gh6')).toEqual('//fd/@g.89@j.89/h') - }) - - it('mixWithIndex: should combine prefix and number into path', () => { - expect(RefHandler.mixWithIndex('', 0)).toEqual('.0') - expect(RefHandler.mixWithIndex('.', 10)).toEqual('..10') - expect(RefHandler.mixWithIndex('vjkhv/@vh.6/', 36)).toEqual('vjkhv/@vh.6/.36') - }) - - it('createRef: should create a new ref from both inputs', () => { - expect(RefHandler.createRef('@0', 'http://test')) - .toEqual({$ref: '@0', eClass: 'http://test'}) - }) - - it('createRefIfMissing: should create a new ref if there is none given, otherwise return the given one', () => { - let ref0 = {$ref: '@0', eClass: 'http://test'}; - let ref1 = {$ref: '', eClass: 'http://test'}; - let ref2 = {$ref: '', eClass: ''}; - let eclass0 = 'http://test2'; - let eclass1 = ''; - expect(RefHandler.createRefIfMissing(eclass0, undefined)).toEqual({ - $ref: '', eClass: eclass0 - }) - expect(RefHandler.createRefIfMissing(eclass1, undefined)).toEqual({ - $ref: '', eClass: eclass1 - }) - expect(RefHandler.createRefIfMissing(eclass0, ref0)).toEqual( - ref0 - ) - expect(RefHandler.createRefIfMissing(eclass0, ref1)).toEqual( - ref1 - ) - expect(RefHandler.createRefIfMissing(eclass0, ref2)).toEqual( - ref2 - ) - }) -}); diff --git a/projects/emfular/src/lib/referencing/ref/ref-handler.ts b/projects/emfular/src/lib/referencing/ref/ref-handler.ts deleted file mode 100644 index d8b4ab0..0000000 --- a/projects/emfular/src/lib/referencing/ref/ref-handler.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {Ref} from "./ref"; - -export class RefHandler { - - public static readonly rootPath: string = "/"; - - static readonly pathDivider: string = '/@'; - - static getIndexFromString(ref: string): number { - let substrings = ref.split('.'); - return parseInt(substrings[substrings.length-1]); - } - - static computePrefix(formerPrefix: string, ownHeader: string): string { - return formerPrefix+RefHandler.pathDivider+ownHeader; - } - - static getParentAddress(ref: string): string { - let pos = ref.lastIndexOf(this.pathDivider) - return ref.substring(0, pos) - } - - static mixWithIndex(prefix: string, index: number): string { - return prefix+'.'+index; - } - - static mixWithPrefixAndIndex(formerPrefix: string, ownHeader: string, index: number): string { - let prefix = this.computePrefix(formerPrefix, ownHeader); - return this.mixWithIndex(prefix, index); - } - - static createRef(ref: string, eClass: string): Ref { - return { - $ref: ref, - eClass: eClass - } - } - - static createRefIfMissing(eClass: string, ref?: Ref) { - return ref? ref: {$ref: '', eClass: eClass}; - } - - // for setting eClass assignment (i.e. on subtypes) - static createRefList(formerPrefix: string, ownHeader: string, eClasses: string[] = []): Ref[] { - const prefix = RefHandler.computePrefix(formerPrefix, ownHeader); - let res = eClasses? eClasses : [] - return res.map((eClass, index) => RefHandler.createRef(RefHandler.mixWithIndex(prefix, index), eClass)) - } -} diff --git a/projects/emfular/src/lib/referencing/ref/ref.ts b/projects/emfular/src/lib/referencing/ref/ref.ts deleted file mode 100644 index 725a77d..0000000 --- a/projects/emfular/src/lib/referencing/ref/ref.ts +++ /dev/null @@ -1,6 +0,0 @@ - -export interface Ref { - $ref: string; - eClass: string; -} - diff --git a/projects/emfular/src/lib/referencing/referencable/container/hide/list-proxy.ts b/projects/emfular/src/lib/referencing/referencable/container/hide/list-proxy.ts deleted file mode 100644 index 62f3641..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/hide/list-proxy.ts +++ /dev/null @@ -1,299 +0,0 @@ -import {Referencable} from "../../referenceable"; -import {ModelList} from "./model-list"; -import {ReListInterface} from "../re-list-interface"; -import {DeletionMode} from "../../../../utils/deletion-mode"; - -export function createListProxy< - T extends Referencable, - P extends Referencable ->(container: ReListInterface): ModelList { - - const forbidden = (name: string) => { - throw new Error(`Operation '${name}' is not supported on model lists`); - }; - - function indexOutOfBounds(operation: string, index: number, length: number): never { - throw new Error( - `Invalid ${operation}: index ${index} out of bounds (length ${length})` - ); - } - - function verifyIndexInBounds(operation: string, index: number, length: number): void { - if (index < 0 || index >= length) { - indexOutOfBounds(operation, index, length); - } - } - - function isIndexAccess(prop: PropertyKey) { - return typeof prop === "string" && /^\d+$/.test(prop) - } - - return new Proxy([] as unknown as ModelList, { - - has(_: ModelList, prop: PropertyKey) { - const list = container.get(); - if (isIndexAccess(prop)) { - const index = Number(prop); - return index >= 0 && index < list.length; - } - return prop in list; - }, - - ownKeys(_: ModelList) { - const list = container.get(); - const keys = []; - - for (let i = 0; i < list.length; i++) { - keys.push(String(i)); - } - - // include "length" and any other properties you want to expose - keys.push("length"); - keys.push(Symbol.iterator); - - return keys; - }, - - - // ============================================================ - // property reads and method lookups - // ============================================================ - get(_: ModelList, prop: string|symbol, receiver) { - const list = container.get(); - - if (isIndexAccess(prop)) { - return list[Number(prop)]; - } - - /* - The at() method of Array instances takes an integer value and returns the item at that index, allowing for positive and negative integers. - Negative integers count back from the last item in the array. - */ - if(prop == "at") { - return forbidden(prop) - } - - if (prop === "length") { - return list.length; - } - - if (prop === Symbol.iterator) { - return function* () { - yield* list; - }; - } - - // ============================================================ - // MUTATING METHODS - // ============================================================ - - // push(...items): number =new length - if (prop === "push") { - return (...items: T[]) => { - for (const item of items) { - container.add(item); - } - return container.get().length; - }; - } - - //own convenience method: - // returns true if it removed at least one item - if (prop === "remove") { - return (...items: T[]) => - items.some(item => container.remove(item)); - } - - if (prop === "removeCascade") { - return (...items: T[]) => - items.some(item => container.remove(item, DeletionMode.CASCADE)) - } - - if(prop === "delete") { - return (mode?: DeletionMode) => { - container.delete(mode); - } - } - - // The pop() method removes the last element from an array and returns that value to the caller. - // If you call pop() on an empty array, it returns undefined. - if (prop === "pop") { - return (mode?: DeletionMode) => { - const arr = container.get(); - if (arr.length === 0) return undefined; - const last = arr[arr.length - 1]; - container.remove(last, mode); - return last; - }; - } - - // The shift() method of Array instances removes the first element from an array and returns that removed element. - // If you call shift() on an empty array, it returns undefined. - if (prop === "shift") { - return (mode?: DeletionMode) => { - const arr = container.get(); - if (arr.length === 0) return undefined; - const first = arr[0]; - container.remove(first, mode); - return first; - }; - } - - // The unshift(..items) method of Array instances adds the specified elements to the beginning of an array and returns the new length of the array. - if (prop === "unshift") { //todo - return (...items: T[]) => { - // Insert at front: we need a container.insertAt(0, item) - // For now: remove all, re-add in new order - const arr = container.get().slice(); - const newArr = [...items, ...arr]; - - // Clear and rebuild - for (const el of arr) container.remove(el); - for (const el of newArr) container.add(el); - - return newArr.length; - }; - } - - // splice(start, deleteCount, ...items) - if (prop === "splice") { - return (start: number, deleteCount: number, ...items: T[]) => { - const arr = container.get().slice(); - const removed = arr.slice(start, start + deleteCount); - - // remove old - for (const r of removed) container.remove(r); - - // insert new - // naive: append then reorder - for (const i of items) container.add(i); - - // reorder to match expected splice result - const final = arr.slice(); - final.splice(start, deleteCount, ...items); - - const current = container.get(); - for (let i = 0; i < final.length; i++) { - const expected = final[i]; - const actual = current[i]; - if (expected !== actual) { - const from = current.indexOf(expected); - container.move(from, i); - } - } - - return removed; - }; - } - - if (prop === "move") { - return (from: number, to: number) => { - container.move(from, to); - }; - } - - if (prop === "swap") { - return (from: number, to: number) => { - const list = container.get(); - verifyIndexInBounds("swap", from, list.length); - verifyIndexInBounds("swap", to, list.length); - if (from === to) return; - const b = list[to]; - container.move(from, to); - // indices have changed - container.move(list.indexOf(b), from) - }; - } - - - // sort(compareFn) - if (prop === "sort") { - return (compareFn?: (a: T, b: T) => number) => { - const arr = container.get().slice(); - arr.sort(compareFn); - - // reorder container to match sorted array - const current = container.get(); - for (let i = 0; i < arr.length; i++) { - const expected = arr[i]; - const actual = current[i]; - if (expected !== actual) { - const from = current.indexOf(expected); - container.move(from, i); - } - } - - return receiver; - }; - } - - // reverse() - if (prop === "reverse") { - return () => { - const arr = container.get().slice().reverse(); - const current = container.get(); - - for (let i = 0; i < arr.length; i++) { - const expected = arr[i]; - const actual = current[i]; - if (expected !== actual) { - const from = current.indexOf(expected); - container.move(from, i); - } - } - - return receiver; - }; - } - - // ============================================================ - // FORBIDDEN OPERATIONS - // ============================================================ - if (prop === "fill") return () => forbidden("fill"); - if (prop === "copyWithin") return () => forbidden("copyWithin"); - - // ============================================================ - // NON-MUTATING METHODS — delegate to Array.prototype - // ============================================================ - const value = (Array.prototype as any)[prop]; - if (typeof value === "function") { - return value.bind(list); - } - - return value; - }, - - // ============================================================ - // SET — index assignment and length assignment - // ============================================================ - set(_target: ModelList, prop: string|symbol, _value: any) { - if (typeof prop === "string" && /^\d+$/.test(prop)) { - forbidden("index assignment"); - } - - if (prop === "length") { - forbidden("length assignment"); - } - - return false; - }, - - // ============================================================ - // DELETE — delete arr[i] - // ============================================================ - deleteProperty(_, prop) { - if (typeof prop === "string" && /^\d+$/.test(prop)) { - const index = Number(prop); - const arr = container.get(); - if (index < arr.length) { - arr[index].$destruct(); - return true; - } - return false; - } - - forbidden("delete"); - return false; - } - }); -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/hide/model-list.ts b/projects/emfular/src/lib/referencing/referencable/container/hide/model-list.ts deleted file mode 100644 index 2eda522..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/hide/model-list.ts +++ /dev/null @@ -1,28 +0,0 @@ -export type ForbiddenArrayMethods = - | "copyWithin" - | "fill" - | "sort" - | "reverse" - | "unshift"; - -export type RefKind = "tree" | "link" | "parent"; - -export type SingleRef = T | undefined; - -export type ModelList = MetaAwareModelList; - -export interface MetaAwareModelList - extends Array { - - /** @deprecated Direct index assignment is not supported. Use push()+move or swap(). */ - [index: number]: T; - - move(from: number, to: number): void; - swap(from: number, to: number): void; - remove(...items: T[]): boolean; - removeCascade(...items: T[]): boolean; - delete(): void; - - readonly __item: T; - readonly __kind: Kind; -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-container.ts b/projects/emfular/src/lib/referencing/referencable/container/link/re-link-container.ts deleted file mode 100644 index 929b656..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-container.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {Referencable} from "../../referenceable"; -import { ReContainer } from "../re-container"; -import {SerializationContext} from "../../../../serialization/serialization-context"; -import {Ref} from "../../../ref/ref"; -import { DeletionMode } from "../../../../utils/deletion-mode"; - -export interface ReLinkContainer< - T extends Referencable, - P extends Referencable -> extends ReContainer { - - /*protected constructor(parent: P, referenceName: string, isRequired: boolean, inverseName?: string) { - super(parent, referenceName, isRequired, inverseName); - this._parent.$otherReferences.push(this) - }*/ - - removeFromInverse(item: T, mode?: DeletionMode): boolean; - - toJson(ctx: SerializationContext): Ref[] | Ref | undefined -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.spec.ts b/projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.spec.ts deleted file mode 100644 index 6e633af..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { ReLinkListContainer } from './re-link-list-container'; -import {ReferencableTester, refTesterRef} from "../../../test/referencable-tester"; -import {RootWithChildren, ReChild3, ReChild4, Middle2WithChildren} from "../../../test/referencables-with-children"; - -describe('ReLinkListContainer', () => { - it('should create an instance', () => { - let tester = new ReferencableTester() - expect(new ReLinkListContainer(tester, 'refName', refTesterRef.references.test)).toBeTruthy(); - }); - - it("should give true if the remove and remove inverse chain triggered an element removal without removing element from different container", () => { - let tester = new RootWithChildren() - let middle = new Middle2WithChildren() - let elem1 = new ReChild3() - let elem2 = new ReChild4() - expect(tester.link3.remove(elem1)).toBeFalse() - expect(tester.link4.remove(elem2)).toBeFalse() - tester.link3.push(elem1) - tester.link4.push(elem2) - middle.child3.push(elem1) - middle.child4.push(elem2) - expect(tester.link3.length).toBe(1) - expect(tester.link4.length).toBe(1) - expect(tester.link3).toContain(elem1) - expect(tester.link4).toContain(elem2) - expect(middle.child3.length).toBe(1) - expect(middle.child4.length).toBe(1) - expect(middle.child3).toContain(elem1) - expect(middle.child4).toContain(elem2) - expect(elem1.link1.length).toBe(1) - expect(elem2.link1.length).toBe(1) - expect(elem1.link1).toContain(tester) - expect(elem2.link1).toContain(tester) - expect(elem1.parentPointer).toBeDefined() - expect(elem2.parentPointer).toBeDefined() - expect(elem1.parentPointer).toEqual(middle) - expect(elem2.parentPointer).toEqual(middle) - expect(tester.link3.remove(elem1)).toBeTrue() - expect(tester.link4.remove(elem2)).toBeTrue() - expect(tester.link3.length).toBe(0) - expect(tester.link4.length).toBe(0) - expect(middle.child3.length).toBe(1) - expect(middle.child4.length).toBe(1) - expect(elem1.link1.length).toBe(0) - expect(elem2.link1.length).toBe(0) - expect(elem1.parentPointer).toBeDefined() - expect(elem2.parentPointer).toBeDefined() - expect(elem1.parentPointer).toEqual(middle) - expect(elem2.parentPointer).toEqual(middle) - }); - - it("should give true if the removeCascade and remove inverse chain triggered an element removal, also removing it from other containers if required reference is deleted", () => { - let tester = new RootWithChildren() - let middle = new Middle2WithChildren() - let elem1 = new ReChild3() - let elem2 = new ReChild4() - expect(tester.link3.removeCascade(elem1)).toBeFalse() - expect(tester.link4.removeCascade(elem2)).toBeFalse() - tester.link3.push(elem1) - tester.link4.push(elem2) - middle.child3.push(elem1) - middle.child4.push(elem2) - expect(tester.link3.length).toBe(1) - expect(tester.link4.length).toBe(1) - expect(tester.link3).toContain(elem1) - expect(tester.link4).toContain(elem2) - expect(middle.child3.length).toBe(1) - expect(middle.child4.length).toBe(1) - expect(middle.child3).toContain(elem1) - expect(middle.child4).toContain(elem2) - expect(elem1.link1.length).toBe(1) - expect(elem2.link1.length).toBe(1) - expect(elem1.link1).toContain(tester) - expect(elem2.link1).toContain(tester) - expect(elem1.parentPointer).toBeDefined() - expect(elem2.parentPointer).toBeDefined() - expect(elem1.parentPointer).toEqual(middle) - expect(elem2.parentPointer).toEqual(middle) - expect(tester.link3.removeCascade(elem1)).toBeTrue() - expect(tester.link4.removeCascade(elem2)).toBeTrue() - expect(tester.link3.length).toBe(0) - expect(tester.link4.length).toBe(0) - expect(middle.child3.length).toBe(1) - expect(middle.child4.length).toBe(0) - expect(elem1.link1.length).toBe(0) - expect(elem2.link1.length).toBe(0) - expect(elem1.parentPointer).toBeDefined() - expect(elem2.parentPointer).toBeUndefined() - expect(elem1.parentPointer).toEqual(middle) - }) -}); diff --git a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.ts b/projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.ts deleted file mode 100644 index 2db6574..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-list-container.ts +++ /dev/null @@ -1,58 +0,0 @@ -import {Referencable} from "../../referenceable"; -import {Ref} from "../../../ref/ref"; -import {SerializationContext} from "../../../../serialization/serialization-context"; -import {ReLinkContainer} from "./re-link-container"; -import {ListUpdater} from "../../../../utils/list-updater"; -import {ReListContainer} from "../re-list-container"; -import {ReferenceMeta} from "../../../../binding/model-definition"; -import {DeletionMode} from "../../../../utils/deletion-mode"; -import {REFERENCE_INTERNAL_API} from "../../referencable-symbols"; - -export class ReLinkListContainer< - T extends Referencable, - P extends Referencable -> extends ReListContainer -implements ReLinkContainer { - - constructor(parent: P, name: string, refMeta: ReferenceMeta) { - super(parent, name, refMeta); - this._parent[REFERENCE_INTERNAL_API].otherLinks().push(this) - } - - addWithoutTypeCheck(item: T): boolean { - let res = ListUpdater.addToListIfMissing(item, this._instance) - if (res) { - if(this.inverseName !== undefined) { - return item[REFERENCE_INTERNAL_API].addToReference(this.inverseName, this._parent) - } - return true; - } else { - return false; - } - } - - override toJson(ctx: SerializationContext): Ref[] { - return this._instance.map(i => ctx.get(i)) - } - - override remove(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean { - const res = ListUpdater.removeFromList(item, this._instance) - if (res) { - if(this.inverseName !== undefined) { - item[REFERENCE_INTERNAL_API].removeFromReference(this.inverseName, this._parent, mode) - } - } - return res; //todo behaviour of flag different to add?? - } - - removeFromInverse(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean { - if(this.inverseName !== undefined) { - for (const child of [...this._instance]) { - child[REFERENCE_INTERNAL_API].removeFromReference(this.inverseName, item, mode) - } - return true; // todo - refine? - } - return false; - } - -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.spec.ts b/projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.spec.ts deleted file mode 100644 index 4341f74..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.spec.ts +++ /dev/null @@ -1,101 +0,0 @@ -import {ReLinkSingleContainer} from './re-link-single-container'; -import {ReferencableTester, refTesterRef} from "../../../test/referencable-tester"; -import { - ReContainersWithSingleChild, - ReContainersWithSingleChild2, - ReSingleChildExample, - ReSingleChildExample2 -} from "../../../test/re-containers-with-single-child"; -import {DeletionMode} from "../../../../utils/deletion-mode"; -import {REFERENCE_INTERNAL_API} from "../../referencable-symbols"; - -describe('ReLinkSingleContainer', () => { - it('should create an instance', () => { - let tester = new ReferencableTester() - expect(new ReLinkSingleContainer(tester, 'refName', refTesterRef.references.test)).toBeTruthy(); - }); - - it('should remove child from container without it getting removed from other containers, even if required reference is removed', () => { - let tester1 = new ReContainersWithSingleChild(); - let middle1 = new ReSingleChildExample(); - let elem1 = new ReContainersWithSingleChild(); - let tester2 = new ReContainersWithSingleChild2(); - let middle2 = new ReSingleChildExample2(); - let elem2 = new ReContainersWithSingleChild2(); - tester1.child = middle1; - middle1.otherLink = elem1; - tester2.child = middle2; - middle2.otherLink = elem2; - expect(tester1.child).toBeDefined(); - expect(tester1.child).toEqual(middle1); - expect(tester2.child).toBeDefined(); - expect(tester2.child).toEqual(middle2); - expect(middle1.myParent).toBeDefined(); - expect(middle1.myParent).toEqual(tester1); - expect(middle2.myParent).toBeDefined(); - expect(middle2.myParent).toEqual(tester2); - expect(middle1.otherLink).toBeDefined(); - expect(middle1.otherLink).toEqual(elem1); - expect(middle2.otherLink).toBeDefined(); - expect(middle2.otherLink).toEqual(elem2); - expect(elem1.link).toBeDefined(); - expect(elem1.link).toEqual(middle1); - expect(elem2.link).toBeDefined(); - expect(elem2.link).toEqual(middle2); - expect(elem1[REFERENCE_INTERNAL_API].otherLinks()[0].remove(middle1)).toBeTrue(); - expect(elem2[REFERENCE_INTERNAL_API].otherLinks()[0].remove(middle2)).toBeTrue(); - expect(tester1.child).toBeDefined(); - expect(tester1.child).toEqual(middle1); - expect(tester2.child).toBeDefined(); - expect(tester2.child).toEqual(middle2); - expect(middle1.myParent).toBeDefined(); - expect(middle1.myParent).toEqual(tester1); - expect(middle2.myParent).toBeDefined(); - expect(middle2.myParent).toEqual(tester2); - expect(middle1.otherLink).toBeUndefined(); - expect(middle2.otherLink).toBeUndefined(); - expect(elem1.link).toBeUndefined(); - expect(elem2.link).toBeUndefined(); - }); - - it('should remove reference from container, triggering its deletion in case of required reference getting removed', () => { - let tester1 = new ReContainersWithSingleChild(); - let middle1 = new ReSingleChildExample(); - let elem1 = new ReContainersWithSingleChild(); - let tester2 = new ReContainersWithSingleChild2(); - let middle2 = new ReSingleChildExample2(); - let elem2 = new ReContainersWithSingleChild2(); - tester1.child = middle1; - middle1.otherLink = elem1; - tester2.child = middle2; - middle2.otherLink = elem2; - expect(tester1.child).toBeDefined(); - expect(tester1.child).toEqual(middle1); - expect(tester2.child).toBeDefined(); - expect(tester2.child).toEqual(middle2); - expect(middle1.myParent).toBeDefined(); - expect(middle1.myParent).toEqual(tester1); - expect(middle2.myParent).toBeDefined(); - expect(middle2.myParent).toEqual(tester2); - expect(middle1.otherLink).toBeDefined(); - expect(middle1.otherLink).toEqual(elem1); - expect(middle2.otherLink).toBeDefined(); - expect(middle2.otherLink).toEqual(elem2); - expect(elem1.link).toBeDefined(); - expect(elem1.link).toEqual(middle1); - expect(elem2.link).toBeDefined(); - expect(elem2.link).toEqual(middle2); - expect(elem1[REFERENCE_INTERNAL_API].otherLinks()[0].remove(middle1, DeletionMode.CASCADE)).toBeTrue(); - expect(elem2[REFERENCE_INTERNAL_API].otherLinks()[0].remove(middle2, DeletionMode.CASCADE)).toBeTrue(); - expect(tester1.child).toBeDefined(); - expect(tester1.child).toEqual(middle1); - expect(tester2.child).toBeUndefined(); - expect(middle1.myParent).toBeDefined(); - expect(middle1.myParent).toEqual(tester1); - expect(middle2.myParent).toBeUndefined(); - expect(middle1.otherLink).toBeUndefined(); - expect(middle2.otherLink).toBeUndefined(); - expect(elem1.link).toBeUndefined(); - expect(elem2.link).toBeUndefined(); - }); -}); diff --git a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.ts b/projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.ts deleted file mode 100644 index 383ff67..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/link/re-link-single-container.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {Referencable} from "../../referenceable"; -import {Ref} from "../../../ref/ref"; -import {SerializationContext} from "../../../../serialization/serialization-context"; -import {ReLinkContainer} from "./re-link-container"; -import {ReSingleContainer} from "../re-single-container"; -import {ReferenceMeta} from "../../../../binding/model-definition"; -import { DeletionMode } from "../../../../utils/deletion-mode"; -import {REFERENCE_INTERNAL_API} from "../../referencable-symbols"; - -export class ReLinkSingleContainer< - T extends Referencable, - P extends Referencable -> extends ReSingleContainer -implements ReLinkContainer { - - constructor(parent: P, referenceName: string, refMeta: ReferenceMeta) { - super(parent, referenceName, refMeta); - this._parent[REFERENCE_INTERNAL_API].otherLinks().push(this) - } - - protected set(instance: T): void { - if(this.inverseName !== undefined) { - this._instance?.[REFERENCE_INTERNAL_API].removeFromReference(this.inverseName, this._parent, DeletionMode.RELAXED) - this._instance = instance; - instance[REFERENCE_INTERNAL_API].addToReference(this.inverseName, this._parent) - } else { - this._instance = instance; - } - } - - addWithoutTypeCheck(item: T): boolean { - if (this._instance == item) { - return false; - } else { - this.set(item); - return true; - } - } - - remove(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean { - if(this._instance == item) { - this._instance = undefined; - if (this.inverseName != undefined) { - item[REFERENCE_INTERNAL_API].removeFromReference(this.inverseName, this._parent, mode) - } - return true; - } else { - return false; - } - } - - override delete(mode: DeletionMode = DeletionMode.RELAXED) { - this._instance?.$destruct(mode) - } - - removeFromInverse(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean { - if(this.inverseName !== undefined) { - this._instance?.[REFERENCE_INTERNAL_API].removeFromReference(this.inverseName, item, mode) - return true; // todo refine? - } - return false; - } - - override toJson(ctx: SerializationContext): Ref|undefined { - if (this._instance) - return ctx.get(this._instance) - else return undefined - } - -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/re-container.ts b/projects/emfular/src/lib/referencing/referencable/container/re-container.ts deleted file mode 100644 index 837f19e..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/re-container.ts +++ /dev/null @@ -1,55 +0,0 @@ -import {Referencable} from "../referenceable"; -import {SerializationContext} from "../../../serialization/serialization-context"; -import {ReferenceMeta} from "../../../binding/model-definition"; -import {ModelRegistry} from "../../../binding/model-registry"; -import {DeletionMode} from "../../../utils/deletion-mode"; - -export abstract class ReContainer< - T extends Referencable, - P extends Referencable -> { - readonly _parent: P; - readonly meta: ReferenceMeta; - readonly referenceName: string; - readonly inverseName?: string; - readonly isRequired: boolean; - - protected constructor(parent: P, referenceName: string, refMeta: ReferenceMeta) { - this._parent = parent; - this.meta = refMeta; - this.referenceName = referenceName; - this.isRequired = this.meta.min != undefined && this.meta.min > 0; - this.inverseName = refMeta.opposite; - } - - abstract get(): T[] | T | undefined; - - protected abstract addWithoutTypeCheck(item: T): boolean; - add(item: T): boolean { - if (this.isAcceptableItem(item)) { - return this.addWithoutTypeCheck(item); - } else { - console.log("Forbidden") - return false; - } - } - - isAcceptableItem(item: Referencable): boolean { - const expectedType = this.meta.target - const targetEclass = this._parent.$modelMeta.uri+expectedType //eclass composition only works since we work inside one model - let targetConstr = ModelRegistry.get(targetEclass) - return item instanceof targetConstr; - } - - isAcceptableEclass(eClass: string): boolean { - let srcConstr = ModelRegistry.get(eClass) - return this.isAcceptableItem( new srcConstr()) //todo - } - - abstract remove(item: T, mode?: DeletionMode): boolean; - - //called to $destruct all elements in the container (e.g. when destroying a parent - abstract delete(mode?: DeletionMode): void - - abstract toJson(ctx: SerializationContext): any -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/re-list-container.ts b/projects/emfular/src/lib/referencing/referencable/container/re-list-container.ts deleted file mode 100644 index fe52bef..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/re-list-container.ts +++ /dev/null @@ -1,62 +0,0 @@ -import {Referencable} from "../referenceable"; -import {ReContainer} from "./re-container"; -import {ListUpdater} from "../../../utils/list-updater"; -import {ModelList} from "./hide/model-list"; -import {createListProxy} from "./hide/list-proxy"; -import {ReListInterface} from "./re-list-interface"; -import {ReferenceMeta} from "../../../binding/model-definition"; -import {DeletionMode} from "../../../utils/deletion-mode"; - - -export abstract class ReListContainer< - T extends Referencable, - P extends Referencable -> extends ReContainer -implements ReListInterface{ - - readonly _instance: T[] = []; - - private _proxy?: ModelList; - - protected constructor(parent: P, referenceName: string, refMeta: ReferenceMeta) { - super(parent, referenceName, refMeta); - } - - override get(): T[] { - return this._instance; - } - - get proxy(): ModelList { - if (!this._proxy) { - this._proxy = createListProxy(this); - } - return this._proxy; - } - - override delete(mode: DeletionMode = DeletionMode.RELAXED) { - ListUpdater.destructAllFromChangingList(this._instance, mode) - } - - move(from: number, to: number) { - const le = this._instance.length; - if(from<0 || to<0 || from>=le || to>=le ) { - throw new Error("Move called with index out of bounds: length "+le+" and indices from "+from+" and to "+to+"."); - } else { - const elem: T = this._instance[from] - this._instance.splice(from, 1); - this._instance.splice(to,0, elem); - } - } - - swap(from: number, to: number) { - const le = this._instance.length; - if(from<0 || to<0 || from>=le || to>=le ) { - throw new Error("Swap called with index out of bounds: length "+le+" and indices from "+from+" and to "+to+"."); - } else { - const fromElem = this._instance[from]; - this._instance[from] = this._instance[to]; - this._instance[to] = fromElem; - } - } - -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/re-list-interface.ts b/projects/emfular/src/lib/referencing/referencable/container/re-list-interface.ts deleted file mode 100644 index dee8765..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/re-list-interface.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {Referencable} from "../referenceable"; -import {ReContainer} from "./re-container"; -import {ModelList} from "./hide/model-list"; - -export interface ReListInterface< - T extends Referencable, - P extends Referencable -> extends ReContainer { - - get proxy(): ModelList - - get(): T[] - - move(from: number, to: number): void - swap(from: number, to: number): void -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/re-single-container.ts b/projects/emfular/src/lib/referencing/referencable/container/re-single-container.ts deleted file mode 100644 index cb1e542..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/re-single-container.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {Referencable} from "../referenceable"; -import {ReContainer} from "./re-container"; -import {ReSingleInterface} from "./re-single-interface"; -import {ReferenceMeta} from "../../../binding/model-definition"; - -export abstract class ReSingleContainer< - T extends Referencable, - P extends Referencable -> extends ReContainer -implements ReSingleInterface{ - - protected _instance?: T ; - - protected constructor(parent: P, referenceName: string, refMeta: ReferenceMeta) { - super(parent, referenceName, refMeta); - } - - override get(): T | undefined { - return this._instance; - } -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/re-single-interface.ts b/projects/emfular/src/lib/referencing/referencable/container/re-single-interface.ts deleted file mode 100644 index 620852a..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/re-single-interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {Referencable} from "../referenceable"; -import {ReContainer} from "./re-container"; - -export interface ReSingleInterface< - T extends Referencable, - P extends Referencable -> extends ReContainer{ - get(): T | undefined -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derivation-resolver.spec.ts b/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derivation-resolver.spec.ts deleted file mode 100644 index b58ec10..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derivation-resolver.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ReDerivationResolver } from './re-derivation-resolver'; - -describe('ReDerivationResolver', () => { - it('should create an instance', () => { - expect(new ReDerivationResolver( () => {})).toBeTruthy(); - }); -}); diff --git a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derivation-resolver.ts b/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derivation-resolver.ts deleted file mode 100644 index 1035a01..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derivation-resolver.ts +++ /dev/null @@ -1,27 +0,0 @@ -export class ReDerivationResolver { - private computeFn?: (owner: P) => R; - readonly derivingSymbol?: symbol; - - constructor(computeOrSymbol: ((owner: P) => R) | symbol) { - if (typeof computeOrSymbol === "function") { - this.computeFn = computeOrSymbol; - } else { - this.derivingSymbol = computeOrSymbol; - } - } - - resolve(parent: P): R { - if (!this.computeFn) { - if (!this.derivingSymbol) { - throw new Error("No derivation logic defined."); - } - - const fn = Reflect.get(parent as any, this.derivingSymbol); - if (typeof fn !== "function") { - throw new Error("Derived method not found on instance."); - } - this.computeFn = fn.bind(parent); - } - return this.computeFn!(parent); - } -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-list-container.spec.ts b/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-list-container.spec.ts deleted file mode 100644 index a90f96b..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-list-container.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ReDerivedListContainer } from './re-derived-list-container'; -import {ReferencableTester, refTesterRef} from "../../../test/referencable-tester"; - -describe('ReDerivedListContainer', () => { - it('should create an instance', () => { - let tester = new ReferencableTester() - expect(new ReDerivedListContainer(tester, () => [],'refName', refTesterRef.references.test)).toBeTruthy(); - }); -}); diff --git a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-list-container.ts b/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-list-container.ts deleted file mode 100644 index c8d76c0..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-list-container.ts +++ /dev/null @@ -1,60 +0,0 @@ -import {Referencable} from "../../referenceable"; -import {SerializationContext} from "../../../../serialization/serialization-context"; -import {ReContainer} from "../re-container"; -import {ReListInterface} from "../re-list-interface"; -import {ModelList} from "../hide/model-list"; -import {createListProxy} from "../hide/list-proxy"; -import {ReShallowInterface} from "./re-shallow-interface"; -import {ReDerivationResolver} from "./re-derivation-resolver"; -import {ReferenceMeta} from "../../../../binding/model-definition"; - -export class ReDerivedListContainer< - T extends Referencable, - P extends Referencable -> extends ReContainer - implements ReListInterface, - ReShallowInterface { - - private _proxy?: ModelList; - private resolver: ReDerivationResolver; - - constructor( - parent: P, - computeOrSymbol: ((owner: P) => T[]) | symbol, - referenceName: string, - refMeta: ReferenceMeta - ) { - super(parent, referenceName, refMeta); - this.resolver = new ReDerivationResolver(computeOrSymbol); - } - - get proxy(): ModelList { - if (!this._proxy) { - this._proxy = createListProxy(this); - } - return this._proxy; - } - - override get(): T[] { - // Defensive copy so callers cannot mutate the underlying result - const result = this.resolver.resolve(this._parent); - return result.slice(); - } - - override toJson(_: SerializationContext): any { - return []; - } - - override addWithoutTypeCheck(_: T): boolean { - return false; - } - - override remove(_: T): boolean { - return false; - } - - override delete() {} - - move(from: number, to: number) {} - swap(from: number, to: number) {} -} \ No newline at end of file diff --git a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-single-container.spec.ts b/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-single-container.spec.ts deleted file mode 100644 index 93537bc..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-single-container.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ReDerivedSingleContainer } from './re-derived-single-container'; -import {ReferencableTester, refTesterRef} from "../../../test/referencable-tester"; - -describe('ReDerivedSingleContainer', () => { - it('should create an instance', () => { - let tester = new ReferencableTester() - expect(new ReDerivedSingleContainer(tester, () => {},'refName', refTesterRef.references.test)).toBeTruthy(); - }); -}); diff --git a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-single-container.ts b/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-single-container.ts deleted file mode 100644 index f0e81c1..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-derived-single-container.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {Referencable} from "../../referenceable"; -import {ReContainer} from "../re-container"; -import {ReSingleInterface} from "../re-single-interface"; -import {ReDerivationResolver} from "./re-derivation-resolver"; -import {SerializationContext} from "../../../../serialization/serialization-context"; -import {ReferenceMeta} from "../../../../binding/model-definition"; - -export class ReDerivedSingleContainer< - T extends Referencable, - P extends Referencable -> extends ReContainer - implements ReSingleInterface { - - private resolver: ReDerivationResolver; - - constructor( - parent: P, - computeOrSymbol: ((owner: P) => T | undefined) | symbol, - referenceName: string, - refMeta: ReferenceMeta - ) { - super(parent, referenceName, refMeta); - this.resolver = new ReDerivationResolver(computeOrSymbol); - } - - get(): T | undefined { - return this.resolver.resolve(this._parent); - } - - override toJson(_: SerializationContext) { return undefined; } - override addWithoutTypeCheck(_:T) { return false; } - override remove(_:T) { return false; } - override delete() {} -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-shallow-interface.ts b/projects/emfular/src/lib/referencing/referencable/container/shallow/re-shallow-interface.ts deleted file mode 100644 index f1274da..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-shallow-interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {Referencable} from "../../referenceable"; -import {ReContainer} from "../re-container"; -import {SerializationContext} from "../../../../serialization/serialization-context"; - -export interface ReShallowInterface< - T extends Referencable, - P extends Referencable -> extends ReContainer { - toJson(_: SerializationContext): undefined |[] -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-tree-parent-container.spec.ts b/projects/emfular/src/lib/referencing/referencable/container/shallow/re-tree-parent-container.spec.ts deleted file mode 100644 index 4ebd6e4..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-tree-parent-container.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ReTreeParentContainer } from './re-tree-parent-container'; -import {ReferencableTester, refTesterRef} from "../../../test/referencable-tester"; - -describe('ReferencableTreeParentContainer', () => { - it('should create an instance', () => { - let tester = new ReferencableTester() - expect(new ReTreeParentContainer(tester, 'refName', refTesterRef.references.test)).toBeTruthy(); - }); - - it('should serialize a contained ref', () => { - - }) -}); diff --git a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-tree-parent-container.ts b/projects/emfular/src/lib/referencing/referencable/container/shallow/re-tree-parent-container.ts deleted file mode 100644 index ef626f3..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/shallow/re-tree-parent-container.ts +++ /dev/null @@ -1,40 +0,0 @@ -import {Referencable} from "../../referenceable"; -import {SerializationContext} from "../../../../serialization/serialization-context"; -import {ReContainer} from "../re-container"; -import {ReSingleInterface} from "../re-single-interface"; -import {ReShallowInterface} from "./re-shallow-interface"; -import {ReferenceMeta} from "../../../../binding/model-definition"; -import { DeletionMode } from "../../../../utils/deletion-mode"; -import {REFERENCE_INTERNAL_API} from "../../referencable-symbols"; - -export class ReTreeParentContainer< - T extends Referencable, - P extends Referencable = T["$ParentType"]> - extends ReContainer -implements ReSingleInterface, - ReShallowInterface{ - - constructor(parent: T, referenceName: string, refMeta: ReferenceMeta) { - super(parent, referenceName, refMeta); // referenceName is actually unused for this container type - } - - get(): P | undefined { - return (this._parent.$getEParent()) - } - - //todo rewrite without using item parent explicitly? - addWithoutTypeCheck(item: P): boolean { - return item[REFERENCE_INTERNAL_API].addToReference(this.inverseName!, this._parent) - } - - remove(item: P, mode: DeletionMode = DeletionMode.RELAXED): boolean { - return item[REFERENCE_INTERNAL_API].removeFromReference(this.inverseName!, this._parent, mode) - } - - delete(): void {} - - toJson(_: SerializationContext): undefined { - return undefined - } - -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-children-container.ts b/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-children-container.ts deleted file mode 100644 index 247adc8..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-children-container.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {SerializationContext} from "../../../../serialization/serialization-context"; -import {Deserializer} from "../../../../serialization/deserializer"; -import {Referencable} from "../../referenceable"; -import { ReContainer } from "../re-container"; -import {JsonOf} from "../../../../serialization/json-deserializable"; - -export interface ReTreeChildrenContainer< - T extends Referencable

, - P extends Referencable =T["$ParentType"] -> extends ReContainer { - - // serialization - assignRefs(ctx: SerializationContext, path: string) : void - toJson(ctx: SerializationContext): JsonOf[] | JsonOf | undefined - //deserialization - fromJson(formerPrefix: string, context: Deserializer, json: any): void - createRefsOnChildren(context: Deserializer, json: any): void -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.spec.ts b/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.spec.ts deleted file mode 100644 index 46c1d80..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { ReTreeListContainer } from './re-tree-list-container'; -import {ReferencableTester, refTesterRef} from "../../../test/referencable-tester"; -import {Middle2WithChildren, ReChild3, ReChild4, RootWithChildren} from "../../../test/referencables-with-children"; - -describe('ReferencableTreeListContainer', () => { - it('should create an instance', () => { - let tester = new ReferencableTester() - expect(new ReTreeListContainer(tester, 'refName', refTesterRef.references.test)).toBeTruthy(); - }); - - it("should give true if the remove and remove inverse chain triggered an element removal without removing element from different container", () => { - let tester = new RootWithChildren() - let middle = new Middle2WithChildren() - let elem1 = new ReChild3() - let elem2 = new ReChild4() - expect(tester.child2.remove(middle)).toBeFalse() - tester.link3.push(elem1) - tester.link4.push(elem2) - tester.child2.push(middle) - middle.child3.push(elem1) - middle.child4.push(elem2) - expect(tester.link3.length).toBe(1) - expect(tester.link4.length).toBe(1) - expect(tester.link3).toContain(elem1) - expect(tester.link4).toContain(elem2) - expect(middle.child3.length).toBe(1) - expect(middle.child4.length).toBe(1) - expect(middle.child3).toContain(elem1) - expect(middle.child4).toContain(elem2) - expect(elem1.link1.length).toBe(1) - expect(elem2.link1.length).toBe(1) - expect(elem1.link1).toContain(tester) - expect(elem2.link1).toContain(tester) - expect(elem1.parentPointer).toBeDefined() - expect(elem2.parentPointer).toBeDefined() - expect(elem1.parentPointer).toEqual(middle) - expect(elem2.parentPointer).toEqual(middle) - expect(tester.child2.remove(middle)).toBeTrue() - expect(tester.child2.length).toBe(0) - expect(tester.link3.length).toBe(1) - expect(tester.link4.length).toBe(1) - expect(middle.child3.length).toBe(1) - expect(middle.child4.length).toBe(1) - expect(elem1.link1.length).toBe(1) - expect(elem2.link1.length).toBe(1) - expect(elem1.parentPointer).toBeDefined() - expect(elem2.parentPointer).toBeDefined() - expect(elem1.parentPointer).toEqual(middle) - expect(elem2.parentPointer).toEqual(middle) - }); - - it("should give true if the removeCascade and remove inverse chain triggered an element removal, also removing it from other containers if required reference is deleted", () => { - let tester = new RootWithChildren() - let middle = new Middle2WithChildren() - let elem1 = new ReChild3() - let elem2 = new ReChild4() - expect(tester.child2.removeCascade(middle)).toBeFalse() - tester.link3.push(elem1) - tester.link4.push(elem2) - tester.child2.push(middle) - middle.child3.push(elem1) - middle.child4.push(elem2) - expect(tester.link3.length).toBe(1) - expect(tester.link4.length).toBe(1) - expect(tester.link3).toContain(elem1) - expect(tester.link4).toContain(elem2) - expect(middle.child3.length).toBe(1) - expect(middle.child4.length).toBe(1) - expect(middle.child3).toContain(elem1) - expect(middle.child4).toContain(elem2) - expect(elem1.link1.length).toBe(1) - expect(elem2.link1.length).toBe(1) - expect(elem1.link1).toContain(tester) - expect(elem2.link1).toContain(tester) - expect(elem1.parentPointer).toBeDefined() - expect(elem2.parentPointer).toBeDefined() - expect(elem1.parentPointer).toEqual(middle) - expect(elem2.parentPointer).toEqual(middle) - expect(tester.child2.removeCascade(middle)).toBeTrue() - expect(tester.child2.length).toBe(0) - expect(tester.link3.length).toBe(0) - expect(tester.link4.length).toBe(0) - expect(middle.child3.length).toBe(0) - expect(middle.child4.length).toBe(0) - expect(elem1.link1.length).toBe(0) - expect(elem2.link1.length).toBe(0) - expect(elem1.parentPointer).toBeUndefined() - expect(elem2.parentPointer).toBeUndefined() - }) -}); diff --git a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.ts b/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.ts deleted file mode 100644 index c3b6257..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-list-container.ts +++ /dev/null @@ -1,98 +0,0 @@ -import {Referencable} from "../../referenceable"; -import {RefHandler} from "../../../ref/ref-handler"; -import {Deserializer} from "../../../../serialization/deserializer"; -import {JsonOf} from "../../../../serialization/json-deserializable"; -import {SerializationContext} from "../../../../serialization/serialization-context"; -import {ReTreeChildrenContainer} from "./re-tree-children-container"; -import {ListUpdater} from "../../../../utils/list-updater"; -import {DeletionMode} from "../../../../utils/deletion-mode"; -import {ReListContainer} from "../re-list-container"; -import {ReferenceMeta} from "../../../../binding/model-definition"; -import {REFERENCE_INTERNAL_API} from "../../referencable-symbols"; - -export class ReTreeListContainer< - T extends Referencable

, - P extends Referencable =T["$ParentType"] -> extends ReListContainer -implements ReTreeChildrenContainer { - - readonly defaultEClass?: string; - - constructor(parent: P, name: string, refMeta: ReferenceMeta, eClass?: string) { - super(parent, name, refMeta); - this.defaultEClass = eClass; - this._parent[REFERENCE_INTERNAL_API].treeChildren().push(this) - } - - assignRefs(ctx: SerializationContext, path: string) { - const ownPath = RefHandler.computePrefix(path, this.referenceName) - this._instance.map((elem, index) => - elem[REFERENCE_INTERNAL_API].serialize_assignRefs(ctx, RefHandler.mixWithIndex(ownPath, index)) - ) - } - - override toJson(ctx: SerializationContext): JsonOf[] { - return this._instance.map( - (ref: T) => ref.toJson(ctx) - ) - } - - //todo rewrite without using item parent explicitly? - addWithoutTypeCheck(item: T): boolean { - const oldParent = item[REFERENCE_INTERNAL_API].getParentContainer(); - if(oldParent == this) { - return false; - } else { - item[REFERENCE_INTERNAL_API].setParentContainer(this); - oldParent?.remove(item) - return ListUpdater.addToListIfMissing(item, this._instance) - } - } - - override remove(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean { - if (mode === DeletionMode.CASCADE) { - if (this._instance.indexOf(item) > -1) { - // if remove is called on an items parent the CASCADE mode would cause an infinite loop, - // however this can be easily avoided since parent removal does not require any kind of following cascading deletes - item.$destruct(mode); - return true; - } - return false; - } - let removed = ListUpdater.removeFromList(item, this._instance) - if(removed){ - item[REFERENCE_INTERNAL_API].setParentContainer(undefined); - return true - } - return false; - } - - //creates one child level plus calls next createChildren - fromJson(formerPrefix: string, context: Deserializer, json: any) { - let myJson: JsonOf[] = json[this.referenceName]; - if (myJson) { - let eClasses: (string|undefined)[] = Deserializer.getEClasses(myJson, this.defaultEClass); - let definedEClasses: string[] = eClasses.filter(i => i != undefined) - if (definedEClasses.length < eClasses.length) { - throw new Error("Could not determine all EClasses for "+formerPrefix+": we have "+eClasses.join(",")) - } - if(definedEClasses.length != myJson.length) { - throw new Error(`Determined eclasses for ${formerPrefix} do not fit available json elements: `+definedEClasses.join(",")) - } - let refList = RefHandler.createRefList(formerPrefix, this.referenceName, definedEClasses) - myJson.forEach((js, index) => { - this.add(context.createTreeBackbone(refList[index], js)) - }) - } - } - - createRefsOnChildren(context: Deserializer, json: any) { - let myJson: JsonOf[] = json[this.referenceName]; - if(myJson && myJson.length == this._instance.length) { - myJson.forEach((ref, index) => { - this._instance[index][REFERENCE_INTERNAL_API].deserializeOtherReferences(context, ref) - }) - } - } - -} diff --git a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.spec.ts b/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.spec.ts deleted file mode 100644 index 87353ee..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { ReTreeSingleContainer } from './re-tree-single-container'; -import {ReferencableTester, refTesterRef} from "../../../test/referencable-tester"; -import {ReContainersWithSingleChild, ReSingleChildExample} from "../../../test/re-containers-with-single-child"; -import {DeletionMode} from "../../../../utils/deletion-mode"; -import {REFERENCE_INTERNAL_API} from "../../referencable-symbols"; - -describe('ReferencableTreeSingletonContainer', () => { - it('should create an instance', () => { - let tester = new ReferencableTester() - expect(new ReTreeSingleContainer(tester, 'test', refTesterRef.references.test)).toBeTruthy(); - }); - - it('should remove child from specified container without it being deleted from any other references', () => { - let tester = new ReContainersWithSingleChild(); - let middle = new ReSingleChildExample(); - let elem1 = new ReContainersWithSingleChild(); - tester.child = middle; - middle.otherLink = elem1; - expect(tester.child).toBeDefined(); - expect(tester.child).toEqual(middle); - expect(middle.myParent).toBeDefined(); - expect(middle.myParent).toEqual(tester); - expect(middle.otherLink).toBeDefined(); - expect(middle.otherLink).toEqual(elem1); - expect(elem1.link).toBeDefined(); - expect(elem1.link).toEqual(middle); - expect(tester[REFERENCE_INTERNAL_API].treeChildren()[0].remove(middle)).toBeTrue(); - expect(tester.child).toBeUndefined(); - expect(middle.myParent).toBeUndefined(); - expect(middle.otherLink).toBeDefined(); - expect(middle.otherLink).toEqual(elem1); - expect(elem1.link).toBeDefined(); - expect(elem1.link).toEqual(middle); - }); - - it('should remove child from specified container and delete its references from all other containers', () => { - let tester = new ReContainersWithSingleChild(); - let middle = new ReSingleChildExample(); - let elem1 = new ReContainersWithSingleChild(); - tester.child = middle; - middle.otherLink = elem1; - expect(tester.child).toBeDefined(); - expect(tester.child).toEqual(middle); - expect(middle.myParent).toBeDefined(); - expect(middle.myParent).toEqual(tester); - expect(middle.otherLink).toBeDefined(); - expect(middle.otherLink).toEqual(elem1); - expect(elem1.link).toBeDefined(); - expect(elem1.link).toEqual(middle); - expect(tester[REFERENCE_INTERNAL_API].treeChildren()[0].remove(middle, DeletionMode.CASCADE)).toBeTrue(); - expect(tester.child).toBeUndefined(); - expect(middle.myParent).toBeUndefined(); - expect(middle.otherLink).toBeUndefined(); - expect(elem1.link).toBeUndefined(); - }); -}); diff --git a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.ts b/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.ts deleted file mode 100644 index a4bc81e..0000000 --- a/projects/emfular/src/lib/referencing/referencable/container/tree/re-tree-single-container.ts +++ /dev/null @@ -1,89 +0,0 @@ -import {Referencable} from "../../referenceable"; -import {RefHandler} from "../../../ref/ref-handler"; -import {Deserializer} from "../../../../serialization/deserializer"; -import {JsonOf} from "../../../../serialization/json-deserializable"; -import {SerializationContext} from "../../../../serialization/serialization-context"; -import {ReTreeChildrenContainer} from "./re-tree-children-container"; -import {ReSingleContainer} from "../re-single-container"; -import {ReferenceMeta} from "../../../../binding/model-definition"; -import {DeletionMode} from "../../../../utils/deletion-mode"; -import {REFERENCE_INTERNAL_API} from "../../referencable-symbols"; - -export class ReTreeSingleContainer< - T extends Referencable

, - P extends Referencable =T["$ParentType"] -> extends ReSingleContainer -implements ReTreeChildrenContainer { - - readonly defaultEClass?: string; - - constructor(parent: P, referenceName: string, refMeta: ReferenceMeta, eClass?: string) { - super(parent, referenceName, refMeta); - this.defaultEClass = eClass; - this._parent[REFERENCE_INTERNAL_API].treeChildren().push(this) - } - - assignRefs(ctx: SerializationContext, path: string) { - this._instance?.[REFERENCE_INTERNAL_API].serialize_assignRefs(ctx, RefHandler.computePrefix(path, this.referenceName)) - } - - toJson(ctx: SerializationContext): JsonOf|undefined { - return this._instance?.toJson(ctx) - } - - addWithoutTypeCheck(item: T): boolean { - if(item == this._instance) { - return false; - } else { - item[REFERENCE_INTERNAL_API].setParentContainer(this); - this._instance = item; - return true; - } - } - - remove(item: T, mode: DeletionMode = DeletionMode.RELAXED): boolean { - if(this._instance == item) { - if (mode === DeletionMode.RELAXED) { - this._instance = undefined; - item[REFERENCE_INTERNAL_API].setParentContainer(undefined); - return true; - } - // if remove is called on an items parent the CASCADE mode would cause an infinite loop, - // however this can be easily avoided since parent removal does not require any kind of following cascading deletes - this._instance?.$destruct(mode); - return true; - } - return false; - } - - delete(mode: DeletionMode = DeletionMode.RELAXED) { - if (mode === DeletionMode.CASCADE) { - this._instance?.$destruct(mode) - } else if (mode === DeletionMode.RELAXED) { - this._instance?.[REFERENCE_INTERNAL_API].getParentContainer()?.remove(this._instance, mode) - } - } - - fromJson(formerPrefix: string, context: Deserializer, json: any) { - let myJson: JsonOf = json[this.referenceName] - if (myJson) { - let eClass = Deserializer.getEClass(myJson, this.defaultEClass); - if (!eClass){ - eClass = this.defaultEClass - if(!eClass){ - throw new Error("Cannot determine eClass for "+formerPrefix) - } - } - let refStr = RefHandler.computePrefix(formerPrefix, this.referenceName) - let ref = RefHandler.createRef(refStr, eClass) - this.add(context.createTreeBackbone(ref, myJson)) - } - } - - createRefsOnChildren(context: Deserializer, json: any) { - let myJson: JsonOf = json[this.referenceName]; - if(this._instance && myJson ) { - this._instance[REFERENCE_INTERNAL_API].deserializeOtherReferences(context, myJson) - } - } -} diff --git a/projects/emfular/src/lib/referencing/referencable/referencable-symbols.ts b/projects/emfular/src/lib/referencing/referencable/referencable-symbols.ts deleted file mode 100644 index 3aab504..0000000 --- a/projects/emfular/src/lib/referencing/referencable/referencable-symbols.ts +++ /dev/null @@ -1,4 +0,0 @@ -// internal API of referencable -export const REFERENCE_INTERNAL_API = Symbol("reference_internal_api"); -// initialization hook for all hidden containers, called on Referencable construction -export const REFERENCE_INITIALIZERS = Symbol("referenceInitializers"); diff --git a/projects/emfular/src/lib/referencing/referencable/referenceable.spec.ts b/projects/emfular/src/lib/referencing/referencable/referenceable.spec.ts deleted file mode 100644 index ab891b5..0000000 --- a/projects/emfular/src/lib/referencing/referencable/referenceable.spec.ts +++ /dev/null @@ -1,5 +0,0 @@ -describe('Referenceable', () => { - it("should create an instance", () => { - - }) -}) diff --git a/projects/emfular/src/lib/referencing/referencable/referenceable.ts b/projects/emfular/src/lib/referencing/referencable/referenceable.ts deleted file mode 100644 index 2b4701c..0000000 --- a/projects/emfular/src/lib/referencing/referencable/referenceable.ts +++ /dev/null @@ -1,287 +0,0 @@ -import {Ref} from "../ref/ref"; -import {v4 as uuidv4} from 'uuid'; -import {ReContainer} from "./container/re-container"; -import {Deserializer} from "../../serialization/deserializer"; -import {getAllAttributes} from "../../binding/attribute-collector"; -import {AttributeOptions} from "../../binding/attribute-decorator"; -import {JsonOf} from "../../serialization/json-deserializable"; -import {SerializationContext} from "../../serialization/serialization-context"; -import {RefHandler} from "../ref/ref-handler"; -import {ReTreeChildrenContainer} from "./container/tree/re-tree-children-container"; -import {ReLinkContainer} from "./container/link/re-link-container"; -import {ModelRegistry} from "../../binding/model-registry"; -import {ClassMeta, ModelDefinition, ReferenceMeta} from "../../binding/model-definition"; -import {DeletionMode} from "../../utils/deletion-mode"; -import {REFERENCE_INITIALIZERS, REFERENCE_INTERNAL_API} from "./referencable-symbols"; - -//private, no export -const TREE_CHILDREN = Symbol("treeChildren"); -const LINKS = Symbol("links"); -const PARENT = Symbol("parent"); - -const REFERENCES_TO_JSON = Symbol("referenceToJson"); -const ATTRIBUTES_TO_JSON = Symbol("attributesToJson"); -const INIT_REFERENCES = Symbol("initReferences"); - - -/** base class for CORE models. - * - */ -export abstract class Referencable< - Parent extends Referencable -> { - - $gId: string; //graphical ID - - declare $classMeta: ClassMeta; - declare $modelMeta: ModelDefinition; - - declare readonly $ParentType: Parent; - private [PARENT]?: ReTreeChildrenContainer; - - private [TREE_CHILDREN]: ReTreeChildrenContainer[] = []; - private [LINKS]: ReLinkContainer[] = []; - - protected constructor() { - this.$gId = uuidv4(); - this[INIT_REFERENCES]() - } - - // ************* public modeling API ******************** - $getEParent(): Parent | undefined { - return this[PARENT]?._parent - } - - $getEClass(): string { - return ModelRegistry.getEClassForInstance(this) - } - - $destruct(mode: DeletionMode = DeletionMode.RELAXED) { - // removal from parent is always called with deletion mode RELAXED, otherwise infinite loops occur (see remove in re-tree-list/single-container.ts) - // tests in files re-link-list/single-container.spec.ts and re-tree-list/single-container.spec.ts fail when not setting RELAXED mode explicitly - this[PARENT]?.remove(this, DeletionMode.RELAXED) - this[LINKS].forEach(refContainer => { - refContainer.removeFromInverse(this, mode) - }) - this[TREE_CHILDREN].forEach(child => { - child.delete(mode) - }) - } - - //************** Serialization ************************* - toJson(ctxOPt?: SerializationContext): JsonOf { - const ctx = ctxOPt ?? new SerializationContext() - if(!ctxOPt) {//initialize new context - //1) first find the root - let root: Referencable|undefined = this - let oldRoot: Referencable|undefined = this - while (root !== undefined) { - oldRoot = root - root = root.$getEParent() - } - //2) then assign refs from the root down - oldRoot[REFERENCE_INTERNAL_API].serialize_assignRefs(ctx, RefHandler.rootPath) - } - const json: any = {}; - json["eClass"] = this.$getEClass(); //todo not always necessary - this[ATTRIBUTES_TO_JSON](json); - this[REFERENCES_TO_JSON](json, ctx); - - return json as JsonOf; - } - - private [ATTRIBUTES_TO_JSON](json: any) { - const ctor = this.constructor as any; - const attributes = getAllAttributes(ctor); - attributes.forEach((options: AttributeOptions, key) => { - if (this.hasOwnProperty(key)) { // skip sibling attributes that are on prototype - let value: any = (this as any)[key]; - //suppress defaults here: - if(value == undefined || value == "" || value ==false || value == options.default) return; - // use right name (jsonName) - if(options.jsonName) { - json[options.jsonName] = value; - } else { - json[key] = value; - } - } - }) - } - - private [REFERENCES_TO_JSON](json: any, ctx: SerializationContext) { - const relevantReferences = [ - ...this[TREE_CHILDREN], - ...this[LINKS] - ]; - - relevantReferences.forEach(child => { - const jsc = child.toJson(ctx) - if (jsc != undefined && !(Array.isArray(jsc) && jsc.length == 0)) { - json[child.referenceName] = jsc - } - }) - } - - - public [REFERENCE_INTERNAL_API]: ReferenceApi = { - - //************** Serialization ************************* - serialize_assignRefs: (ctx: SerializationContext, path: string): void => { - const ref: Ref = RefHandler.createRef(path, this.$getEClass()) - ctx.put(this, ref) - for(let child of this[TREE_CHILDREN]) { - child.assignRefs(ctx, path) - } - }, - - //****************** Deserialization ************************ - deserializeAttributes: >(jsonTyped: J): void => { - const json: any = jsonTyped as any; - const ctor = this.constructor as any; - const attributes = getAllAttributes(ctor); - attributes.forEach((options, key) => { - if (json[key] !== undefined) { - (this as any)[key] = json[key]; - } else if (options?.default !== undefined) { - (this as any)[key] = options.default; - } - }); - }, - - deserializeChildren: >( - context: Deserializer, - parent: Ref, - json: J - ): void => { - this[TREE_CHILDREN].forEach(child => { - child.fromJson(parent.$ref, context, json); - }); - }, - - deserializeOtherReferences: >( - context: Deserializer, - jsonTyped: J - ): void => { - const json = jsonTyped as any; - for (const container of this[LINKS]) { - let jsonElem: Ref[] | Ref | undefined = json[container.referenceName]; - if (jsonElem != undefined) { - const refArray = Array.isArray(jsonElem) ? jsonElem : [jsonElem]; - refArray.map((ref: Ref) => { - container.add(context.get(ref.$ref)); - }); - } - } - for (const container of this[TREE_CHILDREN]) { - container.createRefsOnChildren(context, json); - } - }, - - // ****************** inverse handling (called by link containers) ********************** - addToReference: >(name: string, item: U): boolean => { - return this[REFERENCE_INTERNAL_API].getContainer(name).add(item) - }, - - removeFromReference: >(name: string, item: U, mode: DeletionMode = DeletionMode.RELAXED): boolean => { - let container = this[REFERENCE_INTERNAL_API].getContainer(name) - let result = container.remove(item, mode) - if (result && mode === DeletionMode.CASCADE && container.isRequired) { - const instance = container.get() - if (instance === undefined || (Array.isArray(instance) && instance.length === 0)) { - container._parent.$destruct(mode) - } - } - return result - }, - - treeChildren: (): ReTreeChildrenContainer[] => this[TREE_CHILDREN], - otherLinks: (): ReLinkContainer[] => this[LINKS], - - - getContainer: < - U extends Referencable - >(refName: string): ReContainer => { - let proto: any = Object.getPrototypeOf(this); - let meta: ReferenceMeta | undefined; - // Walk up the prototype chain until we find the reference - while (proto) { - const classMeta = proto.$classMeta; - if (classMeta && classMeta.references && refName in classMeta.references) { - meta = classMeta.references[refName]; - break; - } - proto = Object.getPrototypeOf(proto); - } - if (!meta) { - throw new Error(`Reference '${refName}' not found on class '${this.constructor.name}'`); - } - const key: symbol = meta.containerKey!; - const container = (this as any)[key]; - if (!container) { - throw new Error(`Container for reference '${refName}' not initialized`); - } - return container as ReContainer; - }, - // parent - getParentContainer: (): ReTreeChildrenContainer | undefined => { - return this[PARENT]; - }, - setParentContainer: (parent: ReTreeChildrenContainer | undefined): void => { - if (this[PARENT]) { - this[PARENT].remove(this); - } - this[PARENT] = (parent as (ReTreeChildrenContainer | undefined)); - } - - }; - - // ************* container construction ******************* - private [INIT_REFERENCES]() { - const proto = Object.getPrototypeOf(this); - const inits = proto[REFERENCE_INITIALIZERS]; - if (inits) { - for (const init of inits) { - init.call(this); - } - } - } - -} - - -export interface ReferenceApi< - Self extends Referencable, - Parent extends Referencable -> { - //************** Serialization ************************* - serialize_assignRefs: (ctx: SerializationContext, path: string) => void; - - //****************** Deserialization ************************ - deserializeAttributes: >(json: J) => void; - deserializeChildren: >( - context: Deserializer, - parent: Ref, - json: J - ) => void; - deserializeOtherReferences: >( - context: Deserializer, - json: J - ) => void; - - // *************** container accessors ******************* - treeChildren: () => ReTreeChildrenContainer[]; - otherLinks: () => ReLinkContainer[]; - getContainer: >(refName: string) => ReContainer; - // parent - getParentContainer: () => ReTreeChildrenContainer|undefined, - setParentContainer: (parent: ReTreeChildrenContainer|undefined) => void; - - // ****************** inverse handling (called by link containers) ********************** - addToReference: >(name: string, item: U) => boolean; - removeFromReference: >( - name: string, - item: U, - mode?: DeletionMode - ) => boolean; - -} \ No newline at end of file diff --git a/projects/emfular/src/lib/referencing/test/default-eclass-test.spec.ts b/projects/emfular/src/lib/referencing/test/default-eclass-test.spec.ts deleted file mode 100644 index 3fe732a..0000000 --- a/projects/emfular/src/lib/referencing/test/default-eclass-test.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - EClassesSingleChild, - ReContainersWithSingleChild, - ReContainersWithSingleChildRefs -} from "./re-containers-with-single-child"; -import {EClasses, RootWithChildren, RootWithChildrenRefs} from "./referencables-with-children"; - - -describe('defaultEClass initialization for containment references', () => { - - it('sets defaultEClass on single containment container', () => { - const parent = new ReContainersWithSingleChild(); - - // Get the symbol under which the container is stored - const childKey = ReContainersWithSingleChildRefs.child.containerKey; - expect(childKey).toBeDefined(); - - const childContainer = (parent as any)[childKey!]; - expect(childContainer).toBeDefined(); - - expect(childContainer.defaultEClass).toBe( - EClassesSingleChild.ReSingleChildExample - ); - }); - - it('sets defaultEClass on list containment container', () => { - const root = new RootWithChildren(); - - // Symbol under which the container is stored - const child2Key = RootWithChildrenRefs.child2.containerKey; - expect(child2Key).toBeDefined(); - - // Access the actual container instance - const child2Container = (root as any)[child2Key!]; - expect(child2Container).toBeDefined(); - - // Assert correct defaultEClass - expect(child2Container.defaultEClass).toBe( - EClasses.Middle2WithChildren - ); - }); - - -}); diff --git a/projects/emfular/src/lib/referencing/test/re-containers-with-inheritance.spec.ts b/projects/emfular/src/lib/referencing/test/re-containers-with-inheritance.spec.ts deleted file mode 100644 index f9a7c72..0000000 --- a/projects/emfular/src/lib/referencing/test/re-containers-with-inheritance.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import {A, B, InheritanceRoot, ModelInheritance} from "./re-containers-with-inheritance"; -import {ReContainer} from "../referencable/container/re-container"; -import {REFERENCE_INTERNAL_API} from "../referencable/referencable-symbols"; - -describe("ReferencablesWithInheritance", () => { - - it("shows that subclasses can access the base class meta", () => { - const a = new A(); - const b = new B(); - const r = new InheritanceRoot(); - - expect(a.$classMeta).toBe(ModelInheritance.classes["A"]); - expect(b.$classMeta).toBe(ModelInheritance.classes["B"]); - expect(r.$classMeta).toBe(ModelInheritance.classes["InheritanceRoot"]); - -// âť— InheritanceRoot should have children reference - expect("children" in r.$classMeta.references).toBeTrue(); - // âť— A and B do not have the reference from its base - expect("myParent" in a.$classMeta.references).toBeFalse(); - expect("myParent" in b.$classMeta.references).toBeFalse(); - - expect(r.children.length).toBe(0); - //still access works: - b.myParent = r - // even inverse chain is correctly triggered, resulting in add to children - expect(r.children.length).toBe(1); - }) - - it("should forbid adding a wrong element to a container", ()=> { - const root = new InheritanceRoot(); - const root2 = new InheritanceRoot(); - const a = new A(); - const ref = root[REFERENCE_INTERNAL_API].treeChildren()[0] as ReContainer; - expect(root.children.length).toBe(0); - const res = ref.add(root2) - expect(res).toBeFalse() - expect(root.children.length).toBe(0); - const res2 = ref.add(a) - expect(res2).toBeTrue() - expect(root.children.length).toBe(1); - }) - -}); diff --git a/projects/emfular/src/lib/referencing/test/re-containers-with-inheritance.ts b/projects/emfular/src/lib/referencing/test/re-containers-with-inheritance.ts deleted file mode 100644 index f09a06a..0000000 --- a/projects/emfular/src/lib/referencing/test/re-containers-with-inheritance.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { ModelDefinition } from "../../binding/model-definition"; -import { Referencable } from "../referencable/referenceable"; -import { eClass } from "../../binding/eclass-decorator"; -import { reference } from "../../binding/reference-decorator"; -import {ModelList} from "../referencable/container/hide/model-list"; - -export const ModelInheritance: ModelDefinition = { - name: "ModelInheritance", - prefix: "inh", - uri: "inh://", - classes: { - InheritanceRoot: { - references: { - children: { - target: "AbstractBase", - containment: true, - min: 0, - max: -1 - } - } - }, - AbstractBase: { - references: { - myParent: { - target: "InheritanceRoot", - isParent: true, - max: 1, - opposite: 'children', - } - } - }, - - A: { - references: {} - }, - - B: { - references: {} - }, - } -} as const; - -@eClass(ModelInheritance, "InheritanceRoot") -export class InheritanceRoot extends Referencable { - - constructor() { - super(); - } - - @reference(ModelInheritance.classes["InheritanceRoot"].references["children"]) - declare children: ModelList; -} - -@eClass(ModelInheritance, "AbstractBase") -export abstract class AbstractBase extends Referencable { - - protected constructor() { - super(); - } - - @reference(ModelInheritance.classes["AbstractBase"].references["myParent"]) - declare myParent: InheritanceRoot | undefined; -} - -@eClass(ModelInheritance, "A") -export class A extends AbstractBase { - constructor() { - super(); - } -} - -@eClass(ModelInheritance, "B") -export class B extends AbstractBase { - constructor() { - super(); - } -} - diff --git a/projects/emfular/src/lib/referencing/test/re-containers-with-single-child.spec.ts b/projects/emfular/src/lib/referencing/test/re-containers-with-single-child.spec.ts deleted file mode 100644 index 11515c5..0000000 --- a/projects/emfular/src/lib/referencing/test/re-containers-with-single-child.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { - EClassesSingleChild, - ReContainersWithSingleChild, - ReSingleChildExample -} from "./re-containers-with-single-child"; -import {Ref} from "../ref/ref"; -import {JsonOf} from "../../serialization/json-deserializable"; -import {REFERENCE_INTERNAL_API} from "../referencable/referencable-symbols"; - -describe('ReContainersWithSingleChild tests', () => { - - - it("should register the containers correctly on the parent", () => { - const root: ReContainersWithSingleChild = new ReContainersWithSingleChild() - const child: ReSingleChildExample = new ReSingleChildExample() - - - expect(root[REFERENCE_INTERNAL_API].treeChildren().length).toBe(1) - expect(root[REFERENCE_INTERNAL_API].otherLinks().length).toBe(1) - - expect(child[REFERENCE_INTERNAL_API].treeChildren().length).toBe(0) - expect(child[REFERENCE_INTERNAL_API].otherLinks().length).toBe(1) - }) - - it('should manage parent pointers correctly', () => { - const root: ReContainersWithSingleChild = new ReContainersWithSingleChild(); - expect(root[REFERENCE_INTERNAL_API].getParentContainer()).toBeUndefined() - expect(root.child).toBeUndefined() - const child: ReSingleChildExample = new ReSingleChildExample(); - expect(child.myParent).toBeUndefined() - expect(child[REFERENCE_INTERNAL_API].getParentContainer()).toBeUndefined() - //set tree parent: - child.myParent = root; - expect(child[REFERENCE_INTERNAL_API].getParentContainer()).toBeDefined() - expect(child.myParent).toEqual(root); - expect(root.child).toBe(child); - - const root2 = new ReContainersWithSingleChild(); - expect(root.child).toBe(child); - expect(root2.child).toBeUndefined(); - child.myParent = root2; - expect(child.myParent).toEqual(root2); - expect(root2.child).toBe(child); - expect(root.child).toBeUndefined(); - - //also set from parent end: - root.child = child; - expect(root.child).toEqual(child); - expect(child.myParent).toEqual(root); - expect(root2.child).toBeUndefined() - }) - - it('should serialize and deserialize a ReContainersWithSingleChild', () => { - const root: ReContainersWithSingleChild = new ReContainersWithSingleChild(); - const child: ReSingleChildExample = new ReSingleChildExample(); - - const rootJson = root.toJson() - - expect(rootJson).toEqual({name: "re1", eClass: EClassesSingleChild.ReContainersWithSingleChild}) - const rootFromJson: ReContainersWithSingleChild = ReContainersWithSingleChild.fromJSON(rootJson) - expect(rootFromJson.name).toEqual(root.name) - expect(rootFromJson.child).toBeUndefined() - expect(rootFromJson.link).toBeUndefined() - - // now create parent/child: - child.myParent = root; - expect(child.myParent?.name).toEqual(root.name) - expect(child.myParent?.link).toBeUndefined() - - const jsonNoLinks = root.toJson() - expect(jsonNoLinks.child).toBeDefined() - expect(jsonNoLinks.link).toBeUndefined() - - const fromNoLinks : ReContainersWithSingleChild = ReContainersWithSingleChild.fromJSON(jsonNoLinks) - expect(fromNoLinks.link).toBeUndefined() - expect(fromNoLinks.child?.myParent?.name).toEqual(root.name) - - //add other links: - root.link = child; - expect(child.otherLink).toEqual(root) - const completeJson: JsonOf = root.toJson() - expect(completeJson.name).toEqual(root.name) - expect(completeJson.link).toBeDefined() - //todo: must compile withoutcast for correct jsonOf: - let ref: Ref|undefined = completeJson?.link as unknown as Ref - expect(ref.eClass).toEqual(EClassesSingleChild.ReSingleChildExample) - const completeFromJson : ReContainersWithSingleChild = ReContainersWithSingleChild.fromJSON(completeJson) - expect(completeFromJson.name).toEqual(root.name) - expect(completeFromJson.link).toEqual(completeFromJson.child) - }) -}) \ No newline at end of file diff --git a/projects/emfular/src/lib/referencing/test/re-containers-with-single-child.ts b/projects/emfular/src/lib/referencing/test/re-containers-with-single-child.ts deleted file mode 100644 index f4117e4..0000000 --- a/projects/emfular/src/lib/referencing/test/re-containers-with-single-child.ts +++ /dev/null @@ -1,191 +0,0 @@ -import {Referencable} from "../referencable/referenceable"; -import {eClass} from "../../binding/eclass-decorator"; -import {attribute} from "../../binding/attribute-decorator"; -import {JsonOf} from "../../serialization/json-deserializable"; -import {Deserializer} from "../../serialization/deserializer"; -import {ModelDefinition} from "../../binding/model-definition"; -import {reference} from "../../binding/reference-decorator"; - - -export const ModelSingleChild: ModelDefinition = { - name: "ModelSingleChild", - prefix: "class", - uri:"class://", - classes: { - ReContainersWithSingleChild: { - references: { - child: { - target: "ReSingleChildExample", - containment: true, - opposite: "myParent", - max: 1 - }, - link: { - target: "ReSingleChildExample", - opposite: "otherLink", - max: 1 - } - } - }, - ReContainersWithSingleChild2: { - references: { - child: { - target: "ReSingleChildExample2", - containment: true, - opposite: "myParent", - max: 1 - }, - link: { - target: "ReSingleChildExample2", - opposite: "otherLink", - max: 1 - } - } - }, - - ReSingleChildExample: { - references: { - myParent: { - target: "ReContainersWithSingleChild", - isParent: true, - opposite: "child", - max: 1 - }, - otherLink: { - target: "ReContainersWithSingleChild", - opposite: "link", - max: 1 - } - } - }, - - ReSingleChildExample2: { - references: { - myParent: { - target: "ReContainersWithSingleChild2", - isParent: true, - opposite: "child", - max: 1 - }, - otherLink: { - target: "ReContainersWithSingleChild2", - opposite: "link", - min: 1, - max: 1 - } - } - } - } -} as const; - -// Generated ergonomic reference accessors -export const ReContainersWithSingleChildRefs = { - child: ModelSingleChild.classes["ReContainersWithSingleChild"].references["child"], - link: ModelSingleChild.classes["ReContainersWithSingleChild"].references["link"] -}; - -export const ReContainersWithSingleChild2Refs = { - child: ModelSingleChild.classes["ReContainersWithSingleChild2"].references["child"], - link: ModelSingleChild.classes["ReContainersWithSingleChild2"].references["link"] -}; - -export const ReSingleChildExampleRefs = { - myParent: ModelSingleChild.classes["ReSingleChildExample"].references["myParent"], - otherLink: ModelSingleChild.classes["ReSingleChildExample"].references["otherLink"] -}; - -export const ReSingleChildExample2Refs = { - myParent: ModelSingleChild.classes["ReSingleChildExample2"].references["myParent"], - otherLink: ModelSingleChild.classes["ReSingleChildExample2"].references["otherLink"] -}; - -export enum EClassesSingleChild { - 'ReContainersWithSingleChild' = 'class://ReContainersWithSingleChild', - 'ReContainersWithSingleChild2' = 'class://ReContainersWithSingleChild2', - 'ReSingleChildExample' = 'class://ReSingleChildExample', - 'ReSingleChildExample2' = 'class://ReSingleChildExample2' -} - -@eClass(ModelSingleChild, "ReContainersWithSingleChild") -export class ReContainersWithSingleChild extends Referencable { - - @reference(ReContainersWithSingleChildRefs.child) - declare child: ReSingleChildExample | undefined; - - @reference(ReContainersWithSingleChildRefs.link) - declare link: ReSingleChildExample | undefined; - - @attribute() - name: string = "re1"; - - constructor() { - super(); - } - - static fromJSON (convJson: JsonOf): ReContainersWithSingleChild { - return Deserializer.fromJSON( - convJson, - EClassesSingleChild.ReContainersWithSingleChild - ) - } - -} - -@eClass(ModelSingleChild, "ReContainersWithSingleChild2") -export class ReContainersWithSingleChild2 extends Referencable { - - @reference(ReContainersWithSingleChild2Refs.child) - declare child: ReSingleChildExample2 | undefined; - - @reference(ReContainersWithSingleChild2Refs.link) - declare link: ReSingleChildExample2 | undefined; - - @attribute() - name: string = "re1"; - - constructor() { - super(); - } - - static fromJSON (convJson: JsonOf): ReContainersWithSingleChild2 { - return Deserializer.fromJSON( - convJson, - EClassesSingleChild.ReContainersWithSingleChild2 - ) - } - -} - -@eClass(ModelSingleChild, "ReSingleChildExample") -export class ReSingleChildExample extends Referencable { - - @reference(ReSingleChildExampleRefs.myParent) - declare myParent: ReContainersWithSingleChild | undefined; - - @reference(ReSingleChildExampleRefs.otherLink) - declare otherLink: ReContainersWithSingleChild | undefined; - - @attribute() - myBool = true; - - constructor() { - super(); - } -} - -@eClass(ModelSingleChild, "ReSingleChildExample2") -export class ReSingleChildExample2 extends Referencable { - - @reference(ReSingleChildExample2Refs.myParent) - declare myParent: ReContainersWithSingleChild2 | undefined; - - @reference(ReSingleChildExample2Refs.otherLink) - declare otherLink: ReContainersWithSingleChild2 | undefined; - - @attribute() - myBool = true; - - constructor() { - super(); - } -} \ No newline at end of file diff --git a/projects/emfular/src/lib/referencing/test/referencable-tester.spec.ts b/projects/emfular/src/lib/referencing/test/referencable-tester.spec.ts deleted file mode 100644 index 44c51c8..0000000 --- a/projects/emfular/src/lib/referencing/test/referencable-tester.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ReferencableTester } from './referencable-tester'; - -describe('ReferencableTester', () => { - it('should create an instance', () => { - expect( - new ReferencableTester() - ).toBeTruthy(); - }); -}); diff --git a/projects/emfular/src/lib/referencing/test/referencable-tester.ts b/projects/emfular/src/lib/referencing/test/referencable-tester.ts deleted file mode 100644 index f00f45d..0000000 --- a/projects/emfular/src/lib/referencing/test/referencable-tester.ts +++ /dev/null @@ -1,26 +0,0 @@ -import {Referencable} from "../referencable/referenceable"; -import {eClass} from "../../binding/eclass-decorator"; -import {ModelDefinition, ReferenceMeta} from "../../binding/model-definition"; - -export const refTesterRef = { - references: { - test: { - target: "ReferencableTester", - max: -1, - } satisfies ReferenceMeta - } -} - -export const modelDef= { - name: "", prefix: "", uri: "", - classes: { - ReferencableTester: refTesterRef - } -} as const satisfies ModelDefinition; - -@eClass(modelDef, "ReferencableTester") -export class ReferencableTester extends Referencable { - constructor() { - super(); - } -} diff --git a/projects/emfular/src/lib/referencing/test/referencables-with-children.spec.ts b/projects/emfular/src/lib/referencing/test/referencables-with-children.spec.ts deleted file mode 100644 index a99fec0..0000000 --- a/projects/emfular/src/lib/referencing/test/referencables-with-children.spec.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { - EClasses, - Middle2WithChildren, - Middle2WithChildrenJson, - ReChild3, - ReChild3Json, - RootWithChildren, - RootWithChildrenJson -} from "./referencables-with-children"; -import {RefHandler} from "../ref/ref-handler"; -import {Ref} from "../ref/ref"; -import {REFERENCE_INTERNAL_API} from "../referencable/referencable-symbols"; - -describe('ReContainersWithListChild tests', () => { - - let r1: RootWithChildren; - let r2_1: Middle2WithChildren; - let r2_2: Middle2WithChildren; - let r3_1: ReChild3; - let r3_2: ReChild3; - - beforeEach(() => { - r1 = new RootWithChildren(); - r2_1 = new Middle2WithChildren(); - r2_2 = new Middle2WithChildren(); - r3_1 = new ReChild3(); - r3_2 = new ReChild3(); - }) - - it('should manage parent pointers correctly', () => { - expect(r2_1.child3.length).toBe(0); - r2_1.child3.push(r3_1) - expect(r2_1.child3.length).toBe(1); - expect(r3_1.parentPointer).toEqual(r2_1) - r3_2.parentPointer=r2_1 - expect(r2_1.child3.length).toBe(2); - expect(r3_1.parentPointer).toEqual(r2_1) - expect(r3_2.parentPointer).toEqual(r2_1) - - expect(r2_2.child3.length).toBe(0); - r2_2.child3.push(r3_1) - expect(r2_2.child3.length).toBe(1); - expect(r2_1.child3.length).toBe(1); - expect(r3_1.parentPointer).toEqual(r2_2) - expect(r3_2.parentPointer).toEqual(r2_1) - r3_2.parentPointer=r2_1 - expect(r2_2.child3.length).toBe(1); - expect(r2_1.child3.length).toBe(1); - expect(r3_1.parentPointer).toEqual(r2_2) - expect(r3_2.parentPointer).toEqual(r2_1) - r3_2.parentPointer=r2_2 - expect(r2_2.child3.length).toBe(2); - expect(r2_1.child3.length).toBe(0); - expect(r3_1.parentPointer).toEqual(r2_2) - expect(r3_2.parentPointer).toEqual(r2_2) - - //removal: - r2_2.child3.remove(r3_1) - expect(r2_2.child3.length).toBe(1); - expect(r2_1.child3.length).toBe(0); - expect(r3_1.parentPointer).toEqual(undefined) - expect(r3_2.parentPointer).toEqual(r2_2) - // not possible removal: - r2_2.child3.remove(r3_1) - expect(r2_2.child3.length).toBe(1); - expect(r2_1.child3.length).toBe(0); - expect(r3_1.parentPointer).toEqual(undefined) - expect(r3_2.parentPointer).toEqual(r2_2) - //not possible either: - r2_1.child3.remove(r3_1) - expect(r2_2.child3.length).toBe(1); - expect(r2_1.child3.length).toBe(0); - expect(r3_1.parentPointer).toEqual(undefined) - expect(r3_2.parentPointer).toEqual(r2_2) - - }); - - it ('should serialize a Referencable1WithChildren correctly', () => { - r1.child2.push(r2_1, r2_2) - r2_1.child3.push(r3_1) - r3_1.link1.push(r1) - const r3json: ReChild3Json = { - name: 'referencable3', - eClass: EClasses.ReChild3, - link1: [{ - $ref: RefHandler.rootPath, - eClass: EClasses.RootWithChildren}] - } - const r21json: Middle2WithChildrenJson = { - name: 'referencable2', - eClass: EClasses.Middle2WithChildren, - child3: [r3json] - } - const r22json: Middle2WithChildrenJson = { - name: 'referencable2', - eClass: EClasses.Middle2WithChildren, - } - const r1json: RootWithChildrenJson = { - name: 'referencable1', - eClass: EClasses.RootWithChildren, - child2: [r21json, r22json], - link3: [{ - $ref: "//@child2.0/@child3.0", - eClass: EClasses.ReChild3 - }] - } - expect(r1.toJson()).toEqual(r1json) - const json: RootWithChildrenJson = r1.toJson() - let refs: Ref[] = json.link3 as Ref[] - expect(refs.length).toBe(1) - }) - - //todo deserialization test - - it("should register the containers correctly on the parent", () => { - expect(r1[REFERENCE_INTERNAL_API].treeChildren().length).toBe(1) - expect(r1[REFERENCE_INTERNAL_API].otherLinks().length).toBe(2) - - expect(r2_1[REFERENCE_INTERNAL_API].treeChildren().length).toBe(2) - expect(r2_1[REFERENCE_INTERNAL_API].otherLinks().length).toBe(0) - - expect(r3_1[REFERENCE_INTERNAL_API].treeChildren().length).toBe(0) - expect(r1[REFERENCE_INTERNAL_API].otherLinks().length).toBe(2) - }) - - it('should allow swapping elements in a ModelList created via decorators', () => { - const r = new RootWithChildren(); - const m1 = new Middle2WithChildren(); - const m2 = new Middle2WithChildren(); - const m3 = new Middle2WithChildren(); - - r.child2.push(m1, m2, m3); - expect(r.child2.map(x => x)).toEqual([m1, m2, m3]); - - (r.child2 as any).swap(0, 2); - - expect(r.child2.map(x => x)).toEqual([m3, m2, m1]); - }); - - it('should allow flatMap on ModelList proxies (shows proxy behaves like array)', () => { - // Build a small containment structure - r1.child2.push(r2_1, r2_2); - - r2_1.child3.push(r3_1); - r2_2.child3.push(r3_2); - - // This is the critical line that used to fail: - const allChildren = r1.child2.flatMap(m => m.child3); - - expect(allChildren.length).toBe(2); - expect(allChildren).toContain(r3_1); - expect(allChildren).toContain(r3_2); - }); -}); \ No newline at end of file diff --git a/projects/emfular/src/lib/referencing/test/referencables-with-children.ts b/projects/emfular/src/lib/referencing/test/referencables-with-children.ts deleted file mode 100644 index f87be80..0000000 --- a/projects/emfular/src/lib/referencing/test/referencables-with-children.ts +++ /dev/null @@ -1,192 +0,0 @@ -import {Referencable} from "../referencable/referenceable"; -import {eClass} from "../../binding/eclass-decorator"; -import {attribute} from "../../binding/attribute-decorator"; -import {JsonOf} from "../../serialization/json-deserializable"; -import {ModelDefinition} from "../../binding/model-definition"; -import {reference} from "../../binding/reference-decorator"; -import {ModelList} from "../referencable/container/hide/model-list"; - -export const ModelWithChildren: ModelDefinition = { - name: "namespace", - prefix: "ns", - uri: "namespace/", - - classes: { - RootWithChildren: { - references: { - child2: { - target: "Middle2WithChildren", - containment: true, - min: 0, - max: -1 - }, - link3: { - target: "ReChild3", - opposite: "link1", - min: 0, - max: -1 - }, - link4: { - target: "ReChild4", - opposite: "link1", - min: 0, - max: -1 - } - } - }, - - Middle2WithChildren: { - references: { - child3: { - target: "ReChild3", - containment: true, - opposite: "parentPointer", - min: 0, - max: -1 - }, - child4: { - target: "ReChild4", - containment: true, - opposite: "parentPointer", - min: 0, - max: -1 - } - } - }, - - ReChild3: { - references: { - link1: { - target: "RootWithChildren", - opposite: "link3", - min: 0, - max: -1 - }, - parentPointer: { - target: "Middle2WithChildren", - containment: true, - isParent: true, - opposite: "child3", - min: 0, - max: 1 - } - } - }, - ReChild4: { - references: { - link1: { - target: "RootWithChildren", - opposite: "link4", - min: 1, - max: -1 - }, - parentPointer: { - target: "Middle2WithChildren", - containment: true, - isParent: true, - opposite: "child4", - min: 0, - max: 1 - } - } - } - } -} as const; - -export const RootWithChildrenRefs = { - child2: ModelWithChildren.classes["RootWithChildren"].references["child2"], - link3: ModelWithChildren.classes["RootWithChildren"].references["link3"], - link4: ModelWithChildren.classes["RootWithChildren"].references["link4"] -} as const; -export const Middle2WithChildrenRefs = { - child3: ModelWithChildren.classes["Middle2WithChildren"].references["child3"], - child4: ModelWithChildren.classes["Middle2WithChildren"].references["child4"] -} as const; -export const ReChild3Refs = { - link1: ModelWithChildren.classes["ReChild3"].references["link1"], - parentPointer: ModelWithChildren.classes["ReChild3"].references["parentPointer"], -} as const; -export const ReChild4Refs = { - link1: ModelWithChildren.classes["ReChild4"].references["link1"], - parentPointer: ModelWithChildren.classes["ReChild4"].references["parentPointer"], -} as const; - -export enum EClasses { - 'RootWithChildren' = 'namespace/RootWithChildren', - 'Middle2WithChildren' = 'namespace/Middle2WithChildren', - 'ReChild3' = 'namespace/ReChild3', - 'ReChild4' = 'namespace/ReChild4' -} - -@eClass(ModelWithChildren, "RootWithChildren") -export class RootWithChildren extends Referencable { - - @reference(RootWithChildrenRefs.child2) - declare child2: ModelList - @reference(RootWithChildrenRefs.link3) - declare link3: ModelList; - @reference(RootWithChildrenRefs.link4) - declare link4: ModelList; - - @attribute() - name: string = "referencable1"; - - constructor() { - super(); - } -} - -@eClass(ModelWithChildren, "Middle2WithChildren") -export class Middle2WithChildren extends Referencable { - - @reference(Middle2WithChildrenRefs.child3) - declare child3: ModelList - @reference(Middle2WithChildrenRefs.child4) - declare child4: ModelList - - @attribute() - name: string = "referencable2"; - - constructor() { - super(); - } -} - -@eClass(ModelWithChildren, "ReChild3") -export class ReChild3 extends Referencable { - @reference(ReChild3Refs.link1) - declare link1: ModelList - - @reference(ReChild3Refs.parentPointer) - declare parentPointer?: Middle2WithChildren; - - @attribute() - name: string = "referencable3"; - - constructor() { - super(); - } -} - -@eClass(ModelWithChildren, "ReChild4") -export class ReChild4 extends Referencable { - @reference(ReChild4Refs.link1) - declare link1: ModelList - - @reference(ReChild4Refs.parentPointer) - declare parentPointer?: Middle2WithChildren; - - @attribute() - name: string = "referencable4"; - - constructor() { - super(); - } -} - - -export type RootWithChildrenJson = JsonOf -export type Middle2WithChildrenJson = JsonOf -export type ReChild3Json = JsonOf -export type ReChild4Json = JsonOf - diff --git a/projects/emfular/src/lib/serialization/deserializer.spec.ts b/projects/emfular/src/lib/serialization/deserializer.spec.ts deleted file mode 100644 index f86f9c2..0000000 --- a/projects/emfular/src/lib/serialization/deserializer.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Deserializer } from './deserializer'; - -describe('Deserializer', () => { - it('should create an instance', () => { - expect(new Deserializer()).toBeTruthy(); - }); -}); \ No newline at end of file diff --git a/projects/emfular/src/lib/serialization/deserializer.ts b/projects/emfular/src/lib/serialization/deserializer.ts deleted file mode 100644 index 4bdf2ff..0000000 --- a/projects/emfular/src/lib/serialization/deserializer.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* -idea: - 1) create the tree backbone, only following tree relationships - 2) use addRefWithJson to add all created references afterwards - */ -import {Referencable} from "../referencing/referencable/referenceable"; -import {RefHandler} from "../referencing/ref/ref-handler"; -import {Ref} from "../referencing/ref/ref"; -import {JsonOf} from "./json-deserializable"; -import {ModelRegistry} from "../binding/model-registry"; -import {REFERENCE_INTERNAL_API} from "../referencing/referencable/referencable-symbols"; - -export class Deserializer { - - // all so far parsed objects - private createdObjects: Map> = new Map>(); - - createTreeBackbone>(ref: Ref, json: JsonOf): T { - const entry = ModelRegistry.get(ref.eClass) - const obj: T = new entry() - this.put(ref,obj) - obj[REFERENCE_INTERNAL_API].deserializeAttributes(json) - obj[REFERENCE_INTERNAL_API].deserializeChildren(this, ref, json) - return obj - } - - static getEClass(json: any, defaultStr?: string): (string|undefined) { - const jsonEclass: (string|undefined) = json['eClass'] - return jsonEclass? jsonEclass: defaultStr - } - - static getEClasses(json: any[], defaultStr?: string): (string|undefined)[] { - if (!json) { - return [] - } - return json.map((item) => this.getEClass(item, defaultStr)) - } - - get>($ref: string): T { - return (this.createdObjects.get($ref) as T); - } - - private put>(ref: Ref, elem: T ) { - this.createdObjects.set(ref.$ref, elem); - } - - //only call on the root to trigger the complete deserialization - static fromJSON>( - json: JsonOf, - rootEClass: string - ): C { - const context = new Deserializer(); - const ref: Ref = { - $ref: RefHandler.rootPath, - eClass: rootEClass - } - const model: C = context.createTreeBackbone(ref, json); - model[REFERENCE_INTERNAL_API].deserializeOtherReferences(context, json) - return model; - } - -} diff --git a/projects/emfular/src/lib/serialization/json-deserializable.ts b/projects/emfular/src/lib/serialization/json-deserializable.ts deleted file mode 100644 index e7e8c40..0000000 --- a/projects/emfular/src/lib/serialization/json-deserializable.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Referencable } from "../referencing/referencable/referenceable"; -import { Ref } from "../referencing/ref/ref"; -import {MetaAwareModelList, SingleRef} from "../referencing/referencable/container/hide/model-list"; - -export interface JsonDeserializable> { - new(): T; -} - -type IsReferencable = - T extends Referencable ? true : false; - -type IsReferenceProp = - K extends "$ParentType" ? false : - T extends object - ? T extends MetaAwareModelList ? true - : T extends SingleRef - ? true - : IsReferencable extends true - ? true - : false - : false; - -type ReferenceKeys = { - [K in keyof T]: - IsReferenceProp extends true - ? ( - T[K] extends SingleRef - ? (Kind extends "parent" ? never : K) // exclude SingleRef<..., "parent"> - : K // include all list refs + other refs - ) - : never -}[keyof T]; - -// ignore these on attributes: -type StartsWithPrivate = - K extends `_${string}` | `$${string}` ? true : false; - -type AttributeKeys = { - [K in keyof T]: - K extends "$ParentType" ? never : - StartsWithPrivate extends true ? never : - T[K] extends Function ? never : - IsReferenceProp extends true ? never : - K -}[keyof T]; - -type JsonForReference = - T extends MetaAwareModelList - ? Kind extends "tree" - ? JsonOf[] - : Ref[] - : T extends SingleRef - ? Kind extends "tree" - ? JsonOf | undefined - : Ref | undefined - : never; - -// Final JSON type -export type JsonOf = - & { eClass?: string } - & { [K in AttributeKeys]?: T[K]; } - & { [K in ReferenceKeys]?: JsonForReference }; - diff --git a/projects/emfular/src/lib/serialization/serialization-context.spec.ts b/projects/emfular/src/lib/serialization/serialization-context.spec.ts deleted file mode 100644 index c5e5261..0000000 --- a/projects/emfular/src/lib/serialization/serialization-context.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { SerializationContext } from './serialization-context'; - -describe('SerializationContext', () => { - it('should create an instance', () => { - expect(new SerializationContext()).toBeTruthy(); - }); -}); diff --git a/projects/emfular/src/lib/serialization/serialization-context.ts b/projects/emfular/src/lib/serialization/serialization-context.ts deleted file mode 100644 index 3cd5ede..0000000 --- a/projects/emfular/src/lib/serialization/serialization-context.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {Referencable} from "../referencing/referencable/referenceable"; -import {Ref} from "../referencing/ref/ref"; - -export class SerializationContext { - - private refs = new Map, Ref>(); - - put>(obj: T, ref: Ref) { - this.refs.set(obj, ref); - } - - get>(obj: T): Ref { - const ref = this.refs.get(obj); - if (!ref) { - throw new Error("No ref assigned for object " + obj); - } - return ref; - } -} diff --git a/projects/emfular/src/lib/utils/deletion-mode.ts b/projects/emfular/src/lib/utils/deletion-mode.ts deleted file mode 100644 index baec52f..0000000 --- a/projects/emfular/src/lib/utils/deletion-mode.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum DeletionMode { - CASCADE = "CASCADE", // delete all classes that become inconsistent after deletion - RELAXED = "RELAXED" // delete only the specified class, even if it leaves the model in an inconsistent state -} \ No newline at end of file diff --git a/projects/emfular/src/lib/utils/json-comparer.spec.ts b/projects/emfular/src/lib/utils/json-comparer.spec.ts deleted file mode 100644 index 40e1596..0000000 --- a/projects/emfular/src/lib/utils/json-comparer.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { JsonComparer } from './json-comparer'; - -describe('JsonComparer', () => { - it('should create an instance', () => { - expect(new JsonComparer()).toBeTruthy(); - }); - - it('should match two empty jsons', () => { - expect(JsonComparer.compare({}, {}) - .isLessEquals()).toEqual(true) - }) - - it('should match two string lists without $refs', () => { - expect(JsonComparer.compare(['a', 'b'], ['a', 'b']) - .isLessEquals()).toEqual(true) - }) - - it('should NOT match two reordered string lists without $refs', () => { - expect(JsonComparer.compare(['a', 'b'], ['b', 'a']) - .isLessEquals()).toEqual(false) - }) - - it('should match two reordered object lists with $refs', () => { - expect(JsonComparer.compare([{$ref: 'a'}, {$ref: 'b'}], [{$ref: 'b'}, {$ref:'a'}]) - .isLessEquals()).toEqual(true) - }) - - it('should NOT match two reordered object lists without $refs', () => { - expect(JsonComparer.compare([{ref: 'a'}, {ref: 'b'}], [{ref: 'b'}, {ref:'a'}]) - .isLessEquals()).toEqual(false) - }) - - it('should match two objects with reordered object lists with $refs', () => { - let res = JsonComparer.compare({x: [{$ref: 'a'}, {$ref: 'b'}]}, {x: [{$ref: 'b'}, {$ref:'a'}]}); - expect(res.isLessEquals()).toEqual(true) - }) - - it('should NOT match two objects with reordered object lists without $refs', () => { - let res = JsonComparer.compare({x: [{$re: 'a'}, {$re: 'b'}]}, {x: [{$re: 'b'}, {$re:'a'}]}); - expect(res.isLessEquals()).toEqual(false) - }) - - it('should match a subset with its superset with reordering on $ref', () => { - let res = JsonComparer.compare({x: [{$ref: 'a'}, {$ref: 'b'}]}, {x: [{$ref: 'b'}, {$ref:'a'}], y: 'missing'}); - expect(res.isLessEquals()).toEqual(true) - }) - - it('should NOT match a superset with its subset with reordering on $ref', () => { - let res = JsonComparer.compare({x: [{$ref: 'a'}, {$ref: 'b'}], y: 'missing'}, {x: [{$ref: 'b'}, {$ref:'a'}]}); - expect(res.isLessEquals()).toEqual(false) - }) - - it('should NOT match a subset with its superset because of reordering (no $ref)', () => { - let res = JsonComparer.compare({x: [{$re: 'a'}, {$re: 'b'}]}, {x: [{$re: 'b'}, {$re:'a'}], y: 'missing'}); - expect(res.isLessEquals()).toEqual(false) - }) -}); diff --git a/projects/emfular/src/lib/utils/json-comparer.ts b/projects/emfular/src/lib/utils/json-comparer.ts deleted file mode 100644 index f69547f..0000000 --- a/projects/emfular/src/lib/utils/json-comparer.ts +++ /dev/null @@ -1,130 +0,0 @@ -import {RefHandler} from "../referencing/ref/ref-handler"; - -export type warning = [string, string] - -export class ComparisonResult { - private errors: warning[] = []; - private differences: warning[] = []; - - public addError(err: warning) { - this.errors.push(err); - } - - public addDifference(difference: warning) { - this.differences.push(difference) - } - - public getDifferences() { - return this.differences - } - - public getErrors() { - return this.errors - } - - public isLessEquals(): boolean { - return this.errors.length === 0; - } -} - -export class JsonComparer { - - public static compare(subset: any, superset: any): ComparisonResult { - let result = new ComparisonResult() - if (Array.isArray(subset)) - this.arrayComp(subset, superset, '/', result) - else - this.comp(subset, superset, '/', result); - return result; - } - - private static comp(subset: any, superset: any, ref: string, result: ComparisonResult) { - this.typeComp(subset, superset, ref, result); - this.contentComp(subset, superset, ref, result); - } - - private static typeComp(subset: any, superset: any, ref: string, result: ComparisonResult) { - if (typeof subset !== typeof superset) - result.addError( - [ref, - "Types " + (typeof subset) + " and " + (typeof superset) + " do not agree." - ] - ) - } - - private static contentComp(subset: any, superset: any, ref: string, result: ComparisonResult) { - let superEntries = Object.entries(superset) - for (const entry of Object.entries(subset)) { - let superEntry = superEntries.find(e => entry[0] == e[0]); - if (!superEntry) { - result.addError([ref, "Entry "+ entry[0] + " is missing."]) - } else { - // compare the two entries in depth - let newRef = RefHandler.computePrefix(ref, entry[0]) - this.attrComp(entry[1], superEntry[1], newRef, result); - } - } - this.identifyExtraEntriesOnSuperSet(subset, superset, ref, result) - } - - private static identifyExtraEntriesOnSuperSet(subset: any, superset: any, ref: string, result: ComparisonResult) { - let subEntries = Object.entries(subset) - for (const entry of Object.entries(superset)) { - let match = subEntries.find(e => entry[0] == e[0]); - if (!match) { - result.addDifference([ref, "Entry "+entry[0] + " from superset is missing."]) - } - } - } - - private static attrComp(subset: any, superset: any, ref: string, result: ComparisonResult) { - //todo could be list, simple or object, need to distinguish here - if(Array.isArray(subset)) { - this.arrayComp(subset, superset, ref, result) - } else { - if(typeof subset == "object") { - this.comp(subset, superset, ref, result) - } else {//primitive - let res = (subset === superset) - if (!res) { - result.addError([ref, "Attribute entry "+ subset + " and " + superset + " do not agree"]) - } - } - } - } - - private static arrayComp(subset: any[], superset: any, ref: string, result: ComparisonResult) { - if (!Array.isArray(superset)) { - result.addError([ref, 'The superset is no array']) - } else { - //let superArray = superset as any[]; - if (subset.length !== superset.length) { - result.addError([ref, 'The arrays differ in length: ' - +subset.length + ' vs ' + superset.length]); - } else { - if (this.isRef(subset[0])) { - for(let i = 0; i < subset.length; i++) { - let newRef = RefHandler.mixWithIndex(ref, i) - let s = superset.find(e => - e["$ref"] == subset[i]["$ref"]); - if (s != undefined) { - this.comp(subset[i], s, newRef, result) - } else { - result.addError([ref, "No match on " + newRef + " for "+subset[i]["$ref"] ]); - } - } - } else { - for(let i = 0; i < subset.length; i++) { - let newRef = RefHandler.mixWithIndex(ref, i) - this.comp(subset[i], superset[i], newRef, result) - } - } - } - } - } - - - private static isRef(obj: any) { - return obj["$ref"] != undefined - } -} diff --git a/projects/emfular/src/lib/utils/list-updater.spec.ts b/projects/emfular/src/lib/utils/list-updater.spec.ts deleted file mode 100644 index 24abc3a..0000000 --- a/projects/emfular/src/lib/utils/list-updater.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ListUpdater } from './list-updater'; - -describe('ListUpdater', () => { - - let obj1: any = {name: "obj1"} - let obj2: any = {name: "obj2"} - let obj3: any = {name: "obj3"} - - it('should create an instance', () => { - expect(new ListUpdater()).toBeTruthy(); - }); - - it("should add an element only if it is missing", () => { - const list: any[] = [] - let bool = ListUpdater.addToListIfMissing(obj1, list) - expect(bool).toBeTruthy(); - expect(list).toEqual([obj1]); - bool = ListUpdater.addToListIfMissing(obj1, list) - expect(bool).toBeFalse(); - expect(list).toEqual([obj1]); - bool = ListUpdater.addToListIfMissing(obj2, list) - expect(bool).toBeTruthy(); - expect(list).toEqual([obj1, obj2]); - }) - - it('should remove an element', () => { - const list: any[] = [obj1, obj2, obj3] - let bool = ListUpdater.removeFromList(obj2, list) - expect(bool).toBeTrue(); - expect(list).toEqual([obj1, obj3]); - bool = ListUpdater.removeFromList(obj2, list) - expect(bool).toBeFalse(); - expect(list).toEqual([obj1, obj3]); - bool = ListUpdater.removeFromList(obj3, list) - expect(bool).toBeTrue(); - expect(list).toEqual([obj1]); - bool = ListUpdater.removeFromList(obj3, list) - expect(bool).toBeFalse(); - expect(list).toEqual([obj1]); - bool = ListUpdater.removeFromList(obj1, list) - expect(bool).toBeTrue(); - expect(list).toEqual([]); - }) -}); diff --git a/projects/emfular/src/lib/utils/list-updater.ts b/projects/emfular/src/lib/utils/list-updater.ts deleted file mode 100644 index b4357c4..0000000 --- a/projects/emfular/src/lib/utils/list-updater.ts +++ /dev/null @@ -1,40 +0,0 @@ -import {Referencable} from "../referencing/referencable/referenceable"; -import { DeletionMode } from "./deletion-mode"; -import {REFERENCE_INTERNAL_API} from "../referencing/referencable/referencable-symbols"; - -export class ListUpdater { - - // ************** Helper ********************* - public static removeFromList(elem: T, list: T[]): boolean { - if(!list) { - return false; - } - const index = list.indexOf(elem) - if(index > -1) { - list.splice(index, 1); - return true; - } - return false; - } - - static addToListIfMissing(item: T, list: T[]): boolean { - const index = list.indexOf(item); - if(index > -1) { - return false; - } else { - list.push(item); - return true; - } - } - - static destructAllFromChangingList>(list: T[], mode: DeletionMode) { - while(list?.length > 0){ - if (mode === DeletionMode.CASCADE) { - list[0].$destruct(mode) - } else if (mode === DeletionMode.RELAXED) { - list[0][REFERENCE_INTERNAL_API].getParentContainer()?.remove(list[0], mode) - } - } - } - -} diff --git a/projects/emfular/src/public-api.ts b/projects/emfular/src/public-api.ts deleted file mode 100644 index dd96170..0000000 --- a/projects/emfular/src/public-api.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Public API Surface of emfular - */ - -export * from './lib/referencing/ref/ref'; -export * from './lib/referencing/ref/ref-handler'; -export * from './lib/referencing/referencable/referenceable'; - -export * from './lib/referencing/referencable/container/re-container'; -export * from './lib/referencing/referencable/container/re-list-interface'; -export * from './lib/referencing/referencable/container/re-list-container'; -export * from './lib/referencing/referencable/container/re-single-interface'; -export * from './lib/referencing/referencable/container/re-single-container'; - -export * from './lib/referencing/referencable/container/link/re-link-container' -export * from './lib/referencing/referencable/container/link/re-link-list-container' -export * from './lib/referencing/referencable/container/link/re-link-single-container' - -export * from './lib/referencing/referencable/container/tree/re-tree-children-container'; -export * from './lib/referencing/referencable/container/tree/re-tree-list-container'; -export * from './lib/referencing/referencable/container/tree/re-tree-single-container'; - -export * from './lib/referencing/referencable/container/shallow/re-shallow-interface'; -export * from './lib/referencing/referencable/container/shallow/re-tree-parent-container'; -export * from './lib/referencing/referencable/container/shallow/re-derived-single-container'; -export * from './lib/referencing/referencable/container/shallow/re-derived-list-container'; - -export * from './lib/referencing/referencable/container/hide/model-list' - -export * from './lib/serialization/deserializer'; -export * from './lib/serialization/json-deserializable'; -export * from './lib/serialization/serialization-context'; -export * from './lib/utils/list-updater'; -export * from './lib/utils/json-comparer'; - -export * from './lib/binding/model-registry' -export * from './lib/binding/attribute-decorator' -export * from './lib/binding/attribute-collector' -export * from './lib/binding/eclass-decorator' -export * from './lib/binding/model-definition' -export * from './lib/binding/model-root' -export * from './lib/binding/reference-creator' -export * from './lib/binding/reference-decorator' diff --git a/projects/emfular/tsconfig.lib.json b/projects/emfular/tsconfig.lib.json deleted file mode 100644 index 2359bf6..0000000 --- a/projects/emfular/tsconfig.lib.json +++ /dev/null @@ -1,15 +0,0 @@ -/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ -/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "../../out-tsc/lib", - "declaration": true, - "declarationMap": true, - "inlineSources": true, - "types": [] - }, - "exclude": [ - "**/*.spec.ts" - ] -} diff --git a/projects/emfular/tsconfig.lib.prod.json b/projects/emfular/tsconfig.lib.prod.json deleted file mode 100644 index 9215caa..0000000 --- a/projects/emfular/tsconfig.lib.prod.json +++ /dev/null @@ -1,11 +0,0 @@ -/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ -/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ -{ - "extends": "./tsconfig.lib.json", - "compilerOptions": { - "declarationMap": false - }, - "angularCompilerOptions": { - "compilationMode": "partial" - } -} diff --git a/projects/emfular/tsconfig.spec.json b/projects/emfular/tsconfig.spec.json deleted file mode 100644 index 254686d..0000000 --- a/projects/emfular/tsconfig.spec.json +++ /dev/null @@ -1,15 +0,0 @@ -/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ -/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "../../out-tsc/spec", - "types": [ - "jasmine" - ] - }, - "include": [ - "**/*.spec.ts", - "**/*.d.ts" - ] -} From cfd05bfa4698297ad45b7160dc4307d0b5318066 Mon Sep 17 00:00:00 2001 From: noah Date: Sat, 12 Sep 2026 14:59:26 +0200 Subject: [PATCH 4/9] removed .idea from branch tracking --- .idea/.gitignore | 10 ---------- .idea/EMFular-core.iml | 8 -------- .idea/modules.xml | 8 -------- .idea/vcs.xml | 6 ------ 4 files changed, 32 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/EMFular-core.iml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 30cf57e..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Ignored default folder with query files -/queries/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/EMFular-core.iml b/.idea/EMFular-core.iml deleted file mode 100644 index c956989..0000000 --- a/.idea/EMFular-core.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 0ce8094..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From bfc2d78afb339617f393af12443e5577b69e5dc3 Mon Sep 17 00:00:00 2001 From: noah Date: Sat, 12 Sep 2026 15:05:04 +0200 Subject: [PATCH 5/9] added link to referencable README to top-level README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f174bfe..2414dd0 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ It consists of two parts: ## Referencing Referencing consists of two classes: * The **Ref** class consists of an XPath reference $ref and the EClass eClass. It holds basic facilities to work with XPath, including traversal and path concatenation and is the basis for working with references in the JSON form that emf-jackson exports. - * The **Referencable** class defines a contract for the core representation: Referencables have a UUID gId which is used for global identification and cross-referencing on graphical components. There are also references to all EMF-tree-children. These relationships are used on object creation and destruction. Each object also holds a Ref which might be out-dated but is updated and used on serialization into json. The necessary methods to prepare the XPath references are included. + * The **Referencable** ([README.md](src/lib/referencing/referencable/README.md)) class defines a contract for the core representation: Referencables have a UUID gId which is used for global identification and cross-referencing on graphical components. There are also references to all EMF-tree-children. These relationships are used on object creation and destruction. Each object also holds a Ref which might be out-dated but is updated and used on serialization into json. The necessary methods to prepare the XPath references are included. ## Deserialization From 1bc17266b08654d4562e455b564f5aa795715574 Mon Sep 17 00:00:00 2001 From: noah Date: Sat, 12 Sep 2026 15:27:03 +0200 Subject: [PATCH 6/9] updated referencable README.md to current state of main --- src/lib/referencing/referencable/README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/lib/referencing/referencable/README.md b/src/lib/referencing/referencable/README.md index cdae840..3955499 100644 --- a/src/lib/referencing/referencable/README.md +++ b/src/lib/referencing/referencable/README.md @@ -1,6 +1,6 @@ # Referencable Runtime -This directory contains the reference runtime for EMFular model elements. The code here is responsible for object identity, containment parents, containment children, cross-object links, inverse reference updates, serialization references, deserialization hooks, and controlled list mutation. +This directory contains the reference runtime for EMFular model elements. The code here is responsible for object identity, containment parents, containment children, cross-object links, inverse reference updates, serialization references, deserialization hooks, controlled list mutation, and meta-model constraint violation checking. The implementation has three reference categories: @@ -25,7 +25,11 @@ Public modeling API: - `$getEParent()`: returns the current containment parent by reading the hidden parent container. - `$getEClass()`: resolves the instance eClass through `ModelRegistry`. +- `$treeChildren`: getter exposing the registered tree child containers (delegates to the internal API). +- `$otherLinks`: getter exposing the registered link containers (delegates to the internal API). +- `$violations`: getter exposing the constraint violation map (delegates to the internal API). The map keys are reference names and the values are human-readable violation messages. - `$destruct(mode = DeletionMode.RELAXED)`: removes this object from its containment parent, removes inverse links from linked objects, and deletes contained tree children. Parent removal is always performed in relaxed mode to avoid recursive deletion loops. +- `collectConstraintViolations()`: rebuilds this object's violation map by checking every reference against its meta-model constraints. For derived references it checks derivation-method existence, then derivation-method implementation (by running the computation and capturing thrown errors), then cardinality. For parent references it records a violation when a required parent (`meta.min === 1`) is missing. For link containers and tree child containers it records cardinality violations. Each detected violation is stored in the map under its reference name. - `toJson(ctxOpt?)`: serializes the object. If no `SerializationContext` is provided, it walks to the tree root first and assigns `Ref` paths from the root downward before serializing attributes and references. Private serialization helpers: @@ -44,6 +48,7 @@ Internal API methods: - `deserializeOtherReferences(context, json)`: resolves link references and then resolves references inside tree children. - `treeChildren()`: returns registered tree child containers. - `otherLinks()`: returns registered link containers. +- `violations()`: returns the hidden constraint violation map (reference name to violation message). - `getContainer(refName)`: finds the reference metadata on the prototype chain and returns the initialized hidden container. - `getParentContainer()`: returns the tree container that currently owns this instance. - `setParentContainer(parent)`: switches the owner container, removing the instance from the previous owner first. @@ -81,6 +86,7 @@ Methods: - `remove(item, mode?)`: abstract removal hook. - `delete(mode?)`: abstract cleanup hook for deleting all values owned by the container. - `toJson(ctx)`: abstract serialization hook. +- `checkCardinalityConstraints()`: abstract hook returning a violation message string when the container's current contents violate its `min`/`max` cardinality, or `undefined` otherwise. ### `container/re-single-container.ts` @@ -88,6 +94,7 @@ Defines `ReSingleContainer`, the base for single-valued references. - `_instance`: protected optional stored reference. - `get()`: returns `_instance`. +- `checkCardinalityConstraints()`: returns a minimum-cardinality violation message when `meta.min === 1` and no instance is set, otherwise `undefined`. ### `container/re-list-container.ts` @@ -99,6 +106,7 @@ Defines `ReListContainer`, the base for list-valued references. - `delete(mode = RELAXED)`: destructs every element from the changing list through `ListUpdater`. - `move(from, to)`: moves an item inside the backing array and throws on invalid indexes. - `swap(from, to)`: swaps two items and throws on invalid indexes. +- `checkCardinalityConstraints()`: returns a minimum-cardinality violation message when the list is shorter than `meta.min`, a maximum-cardinality violation message when it exceeds `meta.max` (ignoring the unbounded `-1` sentinel), otherwise `undefined`. ### `container/re-single-interface.ts` @@ -207,12 +215,14 @@ Single-valued shallow view of an object's containment parent. - `remove(item, mode = RELAXED)`: removes this object from the inverse tree reference on `item`. - `delete()`: no-op. - `toJson(ctx)`: returns `undefined`. +- `checkCardinalityConstraints()`: always returns a minimum-cardinality violation message. `collectConstraintViolations()` only calls it after confirming a required parent (`meta.min === 1`) is missing. ### `container/shallow/re-derivation-resolver.ts` Helper for derived references. - `constructor(computeOrSymbol)`: accepts either a compute function or a symbol identifying a method on the parent. +- `canResolve(parent)`: returns `true` when a compute function is stored, or when the deriving symbol resolves to a function on the parent instance; returns `false` otherwise. Used to detect missing derivation methods. - `resolve(parent)`: resolves and caches the compute function, binding symbol-derived methods to the parent instance, then returns the computed value. ### `container/shallow/re-derived-single-container.ts` @@ -225,6 +235,9 @@ Read-only single-valued derived reference. - `addWithoutTypeCheck(item)`: returns `false`. - `remove(item)`: returns `false`. - `delete()`: no-op. +- `checkDerivationMethodExistence()`: returns a derivation violation message when the resolver cannot resolve a derivation function, otherwise `undefined`. +- `checkDerivationMethodImpl()`: runs `get()` and returns the error message if the derivation throws, otherwise `undefined`. +- `checkCardinalityConstraints()`: returns a minimum-cardinality violation message when `meta.min === 1` and the derived value is `undefined`, otherwise `undefined`. ### `container/shallow/re-derived-list-container.ts` @@ -239,6 +252,9 @@ Read-only list-valued derived reference. - `delete()`: no-op. - `move(from, to)`: no-op. - `swap(from, to)`: no-op. +- `checkDerivationMethodExistence()`: returns a derivation violation message when the resolver cannot resolve a derivation function, otherwise `undefined`. +- `checkDerivationMethodImpl()`: runs `get()` and returns the error message if the derivation throws, otherwise `undefined`. +- `checkCardinalityConstraints()`: returns a minimum-cardinality violation message when the derived list is shorter than `meta.min`, a maximum-cardinality violation message when it exceeds `meta.max` (ignoring the unbounded `-1` sentinel), otherwise `undefined`. ## Hidden List Proxy Files From a3a40c774c30ae55ac7432e535b4b44dcc905573 Mon Sep 17 00:00:00 2001 From: noah Date: Fri, 18 Sep 2026 12:25:17 +0200 Subject: [PATCH 7/9] Updated README.md to only refer to fields and methods relevant to users building models --- src/lib/referencing/referencable/README.md | 363 +++++---------------- 1 file changed, 83 insertions(+), 280 deletions(-) diff --git a/src/lib/referencing/referencable/README.md b/src/lib/referencing/referencable/README.md index 3955499..0617bae 100644 --- a/src/lib/referencing/referencable/README.md +++ b/src/lib/referencing/referencable/README.md @@ -1,315 +1,118 @@ -# Referencable Runtime +# Referencable model API -This directory contains the reference runtime for EMFular model elements. The code here is responsible for object identity, containment parents, containment children, cross-object links, inverse reference updates, serialization references, deserialization hooks, controlled list mutation, and meta-model constraint violation checking. +`Referencable` is the base class for generated model elements. Model classes +normally expose references as properties decorated with `@reference`. The +property type and metadata determine whether a reference is a containment, +non-containment, parent, or derived reference, and whether it contains one +object or a list. This README describes the API used when creating and editing models. -The implementation has three reference categories: +## References -- Tree references own their children and serialize them inline. -- Link references point to existing objects and serialize as `Ref` values. -- Shallow and derived references expose computed or parent relationships without owning serialized state. +### Single-valued references -## Top-Level Files +A single-valued reference is read and written like a normal property: -### `referenceable.ts` +```ts +book.author = author; +const author = book.author; +book.author = undefined; // clears the reference +``` -Defines `Referencable`, the abstract base class for generated model classes. +Assigning a new value updates the reference. If the reference declares an +opposite, the opposite reference is updated as well. Values must be instances +of the reference's declared target class. -Fields and metadata: +### List-valued references -- `$gId`: generated UUID for graphical identity. -- `$classMeta`: class metadata supplied by the binding layer. -- `$modelMeta`: model metadata supplied by the binding layer. -- `$ParentType`: TypeScript-only marker for the expected containment parent type. +A multi-valued reference is exposed as a controlled, array-like model list: -Public modeling API: +```ts +book.chapters.push(chapter); +book.chapters.remove(chapter); +book.chapters.removeCascade(chapter); +``` -- `$getEParent()`: returns the current containment parent by reading the hidden parent container. -- `$getEClass()`: resolves the instance eClass through `ModelRegistry`. -- `$treeChildren`: getter exposing the registered tree child containers (delegates to the internal API). -- `$otherLinks`: getter exposing the registered link containers (delegates to the internal API). -- `$violations`: getter exposing the constraint violation map (delegates to the internal API). The map keys are reference names and the values are human-readable violation messages. -- `$destruct(mode = DeletionMode.RELAXED)`: removes this object from its containment parent, removes inverse links from linked objects, and deletes contained tree children. Parent removal is always performed in relaxed mode to avoid recursive deletion loops. -- `collectConstraintViolations()`: rebuilds this object's violation map by checking every reference against its meta-model constraints. For derived references it checks derivation-method existence, then derivation-method implementation (by running the computation and capturing thrown errors), then cardinality. For parent references it records a violation when a required parent (`meta.min === 1`) is missing. For link containers and tree child containers it records cardinality violations. Each detected violation is stored in the map under its reference name. -- `toJson(ctxOpt?)`: serializes the object. If no `SerializationContext` is provided, it walks to the tree root first and assigns `Ref` paths from the root downward before serializing attributes and references. +Model lists support normal read operations such as indexing, iteration, +`length`, and array helpers. Use list operations rather than assigning an +index or replacing the list: -Private serialization helpers: +```ts +for (const chapter of book.chapters) { + // ... +} -- `attributesToJson(json)`: serializes decorated attributes, skips `undefined`, empty strings, `false`, and configured default values, and respects `jsonName` aliases. -- `referencesToJson(json, ctx)`: serializes tree and link containers, omitting `undefined` and empty arrays. -- `initReferences()`: executes prototype reference initializers stored under `REFERENCE_INITIALIZERS`. +book.chapters[0] = anotherChapter; // unsupported +book.chapters = []; // unsupported +``` -`ReferenceApi` is the symbol-backed internal API exposed through `REFERENCE_INTERNAL_API`. Containers use it to interact with a `Referencable` without accessing private fields directly. +The mutation operations are: -Internal API methods: +- `push(...items)`, `pop()`, `shift()`, and `splice(...)` add and remove + referenced objects while preserving reference bookkeeping. +- `remove(...items)` removes objects from the reference without destructing + them. +- `removeCascade(...items)` removes and destructs the selected objects. +- `delete()` removes and destructs every object in the list. +- `move(from, to)` and `swap(from, to)` reorder the list. -- `serialize_assignRefs(ctx, path)`: stores this object's `Ref` in the context and asks tree children to assign their refs. -- `deserializeAttributes(json)`: applies JSON attribute values and configured defaults. -- `deserializeChildren(context, parent, json)`: builds containment children from JSON. -- `deserializeOtherReferences(context, json)`: resolves link references and then resolves references inside tree children. -- `treeChildren()`: returns registered tree child containers. -- `otherLinks()`: returns registered link containers. -- `violations()`: returns the hidden constraint violation map (reference name to violation message). -- `getContainer(refName)`: finds the reference metadata on the prototype chain and returns the initialized hidden container. -- `getParentContainer()`: returns the tree container that currently owns this instance. -- `setParentContainer(parent)`: switches the owner container, removing the instance from the previous owner first. -- `addToReference(name, item)`: adds an item to a named reference. Link containers use this for inverse updates. -- `removeFromReference(name, item, mode)`: removes an item from a named reference. In cascade mode, if a required reference becomes empty, the owning parent is destructed. +Containment lists own their children, so destructing or cascading removal of a +contained object also removes it from the model tree. Link lists only manage +the relationship; configured opposite references are kept in sync. -### `referencable-symbols.ts` +## Derived and parent references -Exports the symbols used for hidden reference machinery. +Derived references are read-only. Their value is computed by the derivation +function declared by the model, so they cannot be assigned, added to, removed +from, or reordered. -- `REFERENCE_INTERNAL_API`: key for each instance's internal reference API. -- `REFERENCE_INITIALIZERS`: key for per-class hidden container initializer functions. +A parent reference is a read-only view of the element's containment parent. For +general parent lookup, use `$getEParent()` on the model element: -## Base Container Files +```ts +const parent = chapter.$getEParent(); +``` -### `container/re-container.ts` +It returns `undefined` when the element is not currently contained. -Defines `ReContainer`, the abstract base for every reference container. +## `Referencable` methods and properties -Constructor state: +### `$getEClass()` -- `_parent`: object that owns the container. -- `meta`: `ReferenceMeta` describing the reference. -- `referenceName`: name used for metadata and JSON fields. -- `inverseName`: configured opposite reference, if any. -- `isRequired`: true when `meta.min` is greater than zero. +Returns the registered eClass identifier for the instance. -Methods: +### `$getEParent()` -- `get()`: abstract read method returning one object, many objects, or `undefined`. -- `add(item)`: checks the item type against the target eClass and calls `addWithoutTypeCheck`; returns `false` for rejected types. -- `addWithoutTypeCheck(item)`: subclass hook for actual insertion. -- `isAcceptableItem(item)`: checks whether the item is an instance of the target class from `ModelRegistry`. -- `isAcceptableEclass(eClass)`: instantiates the class for an eClass and validates it with `isAcceptableItem`. -- `remove(item, mode?)`: abstract removal hook. -- `delete(mode?)`: abstract cleanup hook for deleting all values owned by the container. -- `toJson(ctx)`: abstract serialization hook. -- `checkCardinalityConstraints()`: abstract hook returning a violation message string when the container's current contents violate its `min`/`max` cardinality, or `undefined` otherwise. +Returns the current containment parent, or `undefined` for a root object. -### `container/re-single-container.ts` +### `$destruct(mode?)` -Defines `ReSingleContainer`, the base for single-valued references. +Removes the object from its parent, removes its inverse links, and destructs +its contained children. The optional deletion mode controls how linked and +contained objects are cleaned up; omit it for the default relaxed behavior. -- `_instance`: protected optional stored reference. -- `get()`: returns `_instance`. -- `checkCardinalityConstraints()`: returns a minimum-cardinality violation message when `meta.min === 1` and no instance is set, otherwise `undefined`. +Use this when an object and its owned subtree should be removed from the model. +For removing an item from a list without destructing it, use `remove()` on the +reference instead. -### `container/re-list-container.ts` +### `toJson()` -Defines `ReListContainer`, the base for list-valued references. +Serializes the object and its model subtree to the repository's JSON model +format. Calling it on any contained object serializes the complete tree from +the root and includes references using model `Ref` values. -- `_instance`: backing array. -- `proxy`: lazily-created `ModelList` wrapper from `createListProxy`. -- `get()`: returns the backing array. -- `delete(mode = RELAXED)`: destructs every element from the changing list through `ListUpdater`. -- `move(from, to)`: moves an item inside the backing array and throws on invalid indexes. -- `swap(from, to)`: swaps two items and throws on invalid indexes. -- `checkCardinalityConstraints()`: returns a minimum-cardinality violation message when the list is shorter than `meta.min`, a maximum-cardinality violation message when it exceeds `meta.max` (ignoring the unbounded `-1` sentinel), otherwise `undefined`. +### `collectConstraintViolations()` and `$violations` -### `container/re-single-interface.ts` +Call `collectConstraintViolations()` to validate reference cardinalities and +derived-reference constraints for the current state. Read the resulting +messages from `$violations`, keyed by reference name: -Defines the single-reference interface. +```ts +model.collectConstraintViolations(); -- `get()`: returns one referenced object or `undefined`. +for (const [referenceName, message] of model.$violations) { + console.warn(referenceName, message); +} +``` -### `container/re-list-interface.ts` - -Defines the list-reference interface. - -- `proxy`: controlled array-like `ModelList`. -- `get()`: returns the referenced objects. -- `move(from, to)`: reorders an object. -- `swap(from, to)`: swaps two objects. - -## Tree Container Files - -Tree containers implement containment. They register themselves with `treeChildren()` on the parent, serialize children inline, and maintain each child's parent container. - -### `container/tree/re-tree-children-container.ts` - -Shared interface for containment containers. - -- `assignRefs(ctx, path)`: assigns serialization refs to contained children. -- `toJson(ctx)`: serializes contained children. -- `fromJson(formerPrefix, context, json)`: creates child backbones from JSON. -- `createRefsOnChildren(context, json)`: resolves child links after the containment backbone exists. - -### `container/tree/re-tree-single-container.ts` - -Single-valued containment container. - -- `constructor(parent, referenceName, refMeta, eClass?)`: stores an optional default eClass and registers the container as a tree child container. -- `assignRefs(ctx, path)`: assigns the contained child's ref path using the reference name. -- `toJson(ctx)`: serializes the contained child or returns `undefined`. -- `addWithoutTypeCheck(item)`: sets the item as the contained child and updates its parent container; returns `false` if the same item is already set. -- `remove(item, mode = RELAXED)`: in relaxed mode clears the reference and parent container; in cascade mode destructs the child. -- `delete(mode = RELAXED)`: cascades destruction in cascade mode, otherwise removes the current child from its parent container. -- `fromJson(formerPrefix, context, json)`: determines the eClass, creates a `Ref`, creates the tree backbone via `Deserializer`, and adds it. -- `createRefsOnChildren(context, json)`: resolves the contained child's non-containment references. - -### `container/tree/re-tree-list-container.ts` - -List-valued containment container. - -- `constructor(parent, name, refMeta, eClass?)`: stores an optional default eClass and registers the container as a tree child container. -- `assignRefs(ctx, path)`: assigns indexed child ref paths. -- `toJson(ctx)`: serializes each contained child. -- `addWithoutTypeCheck(item)`: moves the item out of its old parent, updates its parent container, and adds it if missing. -- `remove(item, mode = RELAXED)`: in relaxed mode removes the item and clears its parent container; in cascade mode destructs the item if present. -- `fromJson(formerPrefix, context, json)`: determines all child eClasses, creates child refs, creates child backbones, and adds them. -- `createRefsOnChildren(context, json)`: resolves each child's non-containment references when the JSON array length matches the current list. - -## Link Container Files - -Link containers implement non-containment references. They register with `otherLinks()` on the parent, serialize as refs from `SerializationContext`, and maintain configured opposite references. - -### `container/link/re-link-container.ts` - -Shared interface for link containers. - -- `removeFromInverse(item, mode?)`: removes inverse references related to the supplied item. -- `toJson(ctx)`: serializes links as one `Ref`, many `Ref` values, or `undefined`. - -### `container/link/re-link-single-container.ts` - -Single-valued non-containment link. - -- `constructor(parent, referenceName, refMeta)`: registers the container as a link container. -- `set(instance)`: replaces the stored instance and updates the configured inverse reference. The old inverse is removed in relaxed mode. -- `addWithoutTypeCheck(item)`: sets the link unless the item is already linked. -- `remove(item, mode = RELAXED)`: clears the link and removes the inverse reference when configured. -- `delete(mode = RELAXED)`: destructs the currently linked instance. -- `removeFromInverse(item, mode = RELAXED)`: asks the linked instance to remove the supplied item from the inverse reference. -- `toJson(ctx)`: returns the linked instance's `Ref` or `undefined`. - -### `container/link/re-link-list-container.ts` - -List-valued non-containment link. - -- `constructor(parent, name, refMeta)`: registers the container as a link container. -- `addWithoutTypeCheck(item)`: adds the item if missing and updates the configured inverse reference. -- `toJson(ctx)`: returns a `Ref` for every linked item. -- `remove(item, mode = RELAXED)`: removes the item and updates the configured inverse reference. -- `delete(mode = RELAXED)`: removes every linked item through `remove()`, so inverse references are cleaned up as the list is emptied. -- `removeFromInverse(item, mode = RELAXED)`: asks every linked child to remove the supplied item from the inverse reference. - -## Shallow And Derived Container Files - -Shallow and derived containers expose relationships that are not stored as normal serialized references. - -### `container/shallow/re-shallow-interface.ts` - -Shared interface for shallow references. - -- `toJson(ctx)`: returns either `undefined` or an empty list because shallow references do not serialize owned state. - -### `container/shallow/re-tree-parent-container.ts` - -Single-valued shallow view of an object's containment parent. - -- `constructor(parent, referenceName, refMeta)`: creates the shallow parent container. The reference name is not used directly. -- `get()`: returns `_parent.$getEParent()`. -- `addWithoutTypeCheck(item)`: adds this object to the inverse tree reference on `item`. -- `remove(item, mode = RELAXED)`: removes this object from the inverse tree reference on `item`. -- `delete()`: no-op. -- `toJson(ctx)`: returns `undefined`. -- `checkCardinalityConstraints()`: always returns a minimum-cardinality violation message. `collectConstraintViolations()` only calls it after confirming a required parent (`meta.min === 1`) is missing. - -### `container/shallow/re-derivation-resolver.ts` - -Helper for derived references. - -- `constructor(computeOrSymbol)`: accepts either a compute function or a symbol identifying a method on the parent. -- `canResolve(parent)`: returns `true` when a compute function is stored, or when the deriving symbol resolves to a function on the parent instance; returns `false` otherwise. Used to detect missing derivation methods. -- `resolve(parent)`: resolves and caches the compute function, binding symbol-derived methods to the parent instance, then returns the computed value. - -### `container/shallow/re-derived-single-container.ts` - -Read-only single-valued derived reference. - -- `constructor(parent, computeOrSymbol, referenceName, refMeta)`: creates a resolver-backed reference. -- `get()`: returns the resolver result. -- `toJson(ctx)`: returns `undefined`. -- `addWithoutTypeCheck(item)`: returns `false`. -- `remove(item)`: returns `false`. -- `delete()`: no-op. -- `checkDerivationMethodExistence()`: returns a derivation violation message when the resolver cannot resolve a derivation function, otherwise `undefined`. -- `checkDerivationMethodImpl()`: runs `get()` and returns the error message if the derivation throws, otherwise `undefined`. -- `checkCardinalityConstraints()`: returns a minimum-cardinality violation message when `meta.min === 1` and the derived value is `undefined`, otherwise `undefined`. - -### `container/shallow/re-derived-list-container.ts` - -Read-only list-valued derived reference. - -- `constructor(parent, computeOrSymbol, referenceName, refMeta)`: creates a resolver-backed derived list. -- `proxy`: lazily-created controlled `ModelList`. -- `get()`: returns a defensive copy of the resolver result. -- `toJson(ctx)`: returns an empty list. -- `addWithoutTypeCheck(item)`: returns `false`. -- `remove(item)`: returns `false`. -- `delete()`: no-op. -- `move(from, to)`: no-op. -- `swap(from, to)`: no-op. -- `checkDerivationMethodExistence()`: returns a derivation violation message when the resolver cannot resolve a derivation function, otherwise `undefined`. -- `checkDerivationMethodImpl()`: runs `get()` and returns the error message if the derivation throws, otherwise `undefined`. -- `checkCardinalityConstraints()`: returns a minimum-cardinality violation message when the derived list is shorter than `meta.min`, a maximum-cardinality violation message when it exceeds `meta.max` (ignoring the unbounded `-1` sentinel), otherwise `undefined`. - -## Hidden List Proxy Files - -### `container/hide/model-list.ts` - -Defines the array-like types used by list containers. - -- `RefKind`: union of `tree`, `link`, and `parent` reference kinds. -- `SingleRef`: single-value reference type alias. -- `ModelList`: public list proxy type alias. -- `MetaAwareModelList`: array interface with controlled mutation helpers. - -`MetaAwareModelList` adds: - -- `move(from, to)` -- `swap(from, to)` -- `remove(...items)` -- `removeCascade(...items)` -- `delete()` -- `__item` and `__kind` type markers - -Direct index assignment is marked deprecated because the proxy rejects it at runtime. - -### `container/hide/list-proxy.ts` - -Defines `createListProxy(container)`, which wraps a `ReListInterface` in an array-like `Proxy`. - -Read behavior: - -- Numeric property reads return items from `container.get()`. -- `length`, `in`, own keys, and iteration reflect the current container contents. -- Non-mutating array methods are delegated to `Array.prototype` on the current list. - -Supported mutation behavior: - -- `push(...items)`: adds through `container.add()` and returns the new length. -- `remove(...items)`: removes items in relaxed mode and returns whether any removal happened. -- `removeCascade(...items)`: removes items in cascade mode and returns whether any removal happened. -- `delete(mode?)`: calls `container.delete(mode)`. -- `pop(mode?)`: removes and returns the last item. -- `shift(mode?)`: removes and returns the first item. -- `unshift(...items)`: rebuilds the list with new items at the front. -- `splice(start, deleteCount, ...items)`: removes, adds, and reorders through container operations. -- `move(from, to)`: delegates to `container.move()`. -- `swap(from, to)`: bounds-checks and swaps via container moves. -- `sort(compareFn?)`: sorts a copy and reorders through `container.move()`. -- `reverse()`: reverses by reordering through `container.move()`. - -Rejected operations: - -- `at()` -- `fill()` -- `copyWithin()` -- Direct numeric index assignment -- Direct `length` assignment -- Deleting non-index properties - -Deleting a numeric index destructs the object at that index when the index exists. +The violation map is rebuilt on each call. Validation is not performed +automatically after every model mutation. From 3b4168ea4d242383830e0936473f99ddbc2434fd Mon Sep 17 00:00:00 2001 From: noah Date: Fri, 18 Sep 2026 13:56:03 +0200 Subject: [PATCH 8/9] merged current state of collectConstraintViolations method and tests and updated README.md to reflect changes --- src/lib/referencing/referencable/README.md | 24 ++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/lib/referencing/referencable/README.md b/src/lib/referencing/referencable/README.md index 0617bae..1c2b524 100644 --- a/src/lib/referencing/referencable/README.md +++ b/src/lib/referencing/referencable/README.md @@ -102,17 +102,29 @@ the root and includes references using model `Ref` values. ### `collectConstraintViolations()` and `$violations` -Call `collectConstraintViolations()` to validate reference cardinalities and -derived-reference constraints for the current state. Read the resulting -messages from `$violations`, keyed by reference name: +Call `collectConstraintViolations()` to validate the current model subtree. +It checks each object in the containment tree, clears the local violation map, +and rebuilds violations for derived references, required parent references, +link cardinality, and tree-child cardinality. It also detects cycles while +walking the tree. + +The method returns a nested `Map>` keyed first by +object `$gId`, then by reference name. The current object's local map is also +available through `$violations`: ```ts -model.collectConstraintViolations(); +const violations = model.collectConstraintViolations(); + +for (const [gId, refViolations] of violations) { + for (const [referenceName, message] of refViolations) { + console.warn(gId, referenceName, message); + } +} for (const [referenceName, message] of model.$violations) { console.warn(referenceName, message); } ``` -The violation map is rebuilt on each call. Validation is not performed -automatically after every model mutation. +Validation is not triggered automatically after every mutation; you must call +`collectConstraintViolations()` when you want to inspect the current state. From abee7e53ae5b3ec9516dc78b6ff630e8ce713896 Mon Sep 17 00:00:00 2001 From: noah Date: Fri, 18 Sep 2026 14:40:53 +0200 Subject: [PATCH 9/9] removed leftover comma in re-containers-with-single-child.ts and enum entry in referencables-with-children.ts --- src/lib/referencing/test/re-containers-with-single-child.ts | 2 +- src/lib/referencing/test/referencables-with-children.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/referencing/test/re-containers-with-single-child.ts b/src/lib/referencing/test/re-containers-with-single-child.ts index 513c066..2955a9d 100644 --- a/src/lib/referencing/test/re-containers-with-single-child.ts +++ b/src/lib/referencing/test/re-containers-with-single-child.ts @@ -82,7 +82,7 @@ export const ModelSingleChild: ModelDefinition = { // Generated ergonomic reference accessors export const ReContainersWithSingleChildRefs = { child: ModelSingleChild.classes["ReContainersWithSingleChild"].references["child"], - link: ModelSingleChild.classes["ReContainersWithSingleChild"].references["link"], + link: ModelSingleChild.classes["ReContainersWithSingleChild"].references["link"] }; export const ReContainersWithSingleChild2Refs = { diff --git a/src/lib/referencing/test/referencables-with-children.ts b/src/lib/referencing/test/referencables-with-children.ts index 0b7de91..a2fb901 100644 --- a/src/lib/referencing/test/referencables-with-children.ts +++ b/src/lib/referencing/test/referencables-with-children.ts @@ -132,8 +132,7 @@ export enum EClasses { 'RootWithChildren' = 'namespace/RootWithChildren', 'Middle2WithChildren' = 'namespace/Middle2WithChildren', 'ReChild3' = 'namespace/ReChild3', - 'ReChild4' = 'namespace/ReChild4', - 'ReChildCircle' = 'namespace/ReChildCircle' + 'ReChild4' = 'namespace/ReChild4' } @eClass(ModelWithChildren, "RootWithChildren")