@istar-ts/core 0.14.2 → 0.15.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/index.cjs +230 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +216 -133
- package/dist/index.d.ts +216 -133
- package/dist/index.js +227 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -461,6 +461,21 @@ toPistar(extended); // writes the "metamodel" block
|
|
|
461
461
|
into a store (`store.load` or `store.replace`) to keep editing.
|
|
462
462
|
- `pistarType` works as for any extension, so `istar.<Name>` types on disk still read.
|
|
463
463
|
|
|
464
|
+
## Palette
|
|
465
|
+
|
|
466
|
+
`DEFAULT_PALETTE` (`./palette`) is iStar 2.0's palette as data — the part that isn't React: a
|
|
467
|
+
`PaletteDescription` keyed by element and link kind, each entry a `label`, `order`, `group`
|
|
468
|
+
(entries sharing one collapse into a dropdown button) and `section` ("actors"/"elements" by
|
|
469
|
+
default), plus the presets a toolbar entry creates with — an element's `properties`, a
|
|
470
|
+
contribution link's `value`, a dependency's `dependum` kind. A kind missing from the
|
|
471
|
+
description, or mapped to `false`, has no palette entry.
|
|
472
|
+
|
|
473
|
+
This is framework-free so a host can answer "what can I add here" — GLSP's
|
|
474
|
+
`RequestContextActions` — without depending on `@istar-ts/react`; `@istar-ts/actions`'
|
|
475
|
+
`contextActionsFor` turns a `PaletteDescription` plus a `Metamodel` into `LabeledAction`s. The
|
|
476
|
+
react package's palette registry derives its default entries from `DEFAULT_PALETTE`; icons,
|
|
477
|
+
titles and components stay in react.
|
|
478
|
+
|
|
464
479
|
## Diagnostics
|
|
465
480
|
|
|
466
481
|
`GoalDiagnostic` (`{ elementId, key?, severity, message, source?, range?, code?, data? }`) is the
|
|
@@ -470,6 +485,13 @@ data shape for findings about elements and links. Analysers publish them per sou
|
|
|
470
485
|
diagnostics. Diagnostics are never part of the model and never serialized. `@istar-ts/react`
|
|
471
486
|
shows them as badges and in the inspector (see its README).
|
|
472
487
|
|
|
488
|
+
`ModelOperationError.toDiagnostic()` turns a thrown operation error into a `GoalDiagnostic`
|
|
489
|
+
(`severity: 'error'`, anchored on the `elementId` the error named, or `''` when it named none).
|
|
490
|
+
`connectResultToDiagnostic(result, endpoints)` does the same for a failing `connect`/`canLink`
|
|
491
|
+
result, anchored on whichever of `endpoints.source`/`endpoints.target` the failure is about.
|
|
492
|
+
Both are how `@istar-ts/actions`' dispatcher turns a rejected operation into the diagnostics an
|
|
493
|
+
`ActionResult` carries.
|
|
494
|
+
|
|
473
495
|
## Credits
|
|
474
496
|
|
|
475
497
|
The metamodel, link constraints, element shapes, and file format are derived from
|
package/dist/index.cjs
CHANGED
|
@@ -763,14 +763,64 @@ function defaultHubPosition(model, source, target, size) {
|
|
|
763
763
|
y: (a.y + b.y) / 2 - size.height / 2
|
|
764
764
|
};
|
|
765
765
|
}
|
|
766
|
+
/** Space kept between inner elements and an actor's boundary (piStar's `updateBoundary` adds 10px). */
|
|
767
|
+
const ACTOR_PADDING = 10;
|
|
768
|
+
/** A kind's default size, absent any per-host override (see `actorBounds`' `defaultSizeOf`). */
|
|
769
|
+
function metamodelSizeOf(metamodel, kind) {
|
|
770
|
+
return metamodel.elements.get(kind)?.size ?? {
|
|
771
|
+
width: 0,
|
|
772
|
+
height: 0
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* An actor's boundary: the kind's default-size rectangle at the actor's position, grown to
|
|
777
|
+
* include every inner element (each padded by `ACTOR_PADDING`), like piStar's `updateBoundary`
|
|
778
|
+
* (which floors on the kind's default size, not the actor's last expanded `display` size; an
|
|
779
|
+
* inner element's own `display` size does override, same as its rendered size elsewhere).
|
|
780
|
+
* Shared by `@istar-ts/react`'s node layout and `@istar-ts/glsp`'s GModel, so an actor draws the
|
|
781
|
+
* same box in both: children are looked up by category (`metamodelOf`), not `istar.*` names.
|
|
782
|
+
*
|
|
783
|
+
* `defaultSizeOf` resolves a kind's default size; it defaults to the metamodel's, but a host
|
|
784
|
+
* whose registry overrides a kind's size independently of the metamodel (`@istar-ts/react`'s
|
|
785
|
+
* `IstarRegistry`) must pass one that reads its own sizes, or its boundary would stop matching
|
|
786
|
+
* what it actually draws.
|
|
787
|
+
*/
|
|
788
|
+
function actorBounds(model, actorId, metamodel = metamodelOf(model), defaultSizeOf = (kind) => metamodelSizeOf(metamodel, kind)) {
|
|
789
|
+
const actor = model.elements.get(actorId);
|
|
790
|
+
if (!actor) throw new Error(`no element "${actorId}" in the model`);
|
|
791
|
+
const meta = metamodel;
|
|
792
|
+
const ownSize = defaultSizeOf(actor.kind);
|
|
793
|
+
let minX = actor.x;
|
|
794
|
+
let minY = actor.y;
|
|
795
|
+
let maxX = actor.x + ownSize.width;
|
|
796
|
+
let maxY = actor.y + ownSize.height;
|
|
797
|
+
for (const child of model.elements.values()) {
|
|
798
|
+
if (child.parent !== actorId) continue;
|
|
799
|
+
if (meta.elements.get(child.kind)?.category === "actor") continue;
|
|
800
|
+
const base = defaultSizeOf(child.kind);
|
|
801
|
+
const width = child.display?.width ?? base.width;
|
|
802
|
+
const height = child.display?.height ?? base.height;
|
|
803
|
+
minX = Math.min(minX, child.x - 10);
|
|
804
|
+
minY = Math.min(minY, child.y - 10);
|
|
805
|
+
maxX = Math.max(maxX, child.x + width + 10);
|
|
806
|
+
maxY = Math.max(maxY, child.y + height + 10);
|
|
807
|
+
}
|
|
808
|
+
return {
|
|
809
|
+
x: minX,
|
|
810
|
+
y: minY,
|
|
811
|
+
width: maxX - minX,
|
|
812
|
+
height: maxY - minY
|
|
813
|
+
};
|
|
814
|
+
}
|
|
766
815
|
//#endregion
|
|
767
816
|
//#region src/constraints.ts
|
|
768
817
|
const OK = { ok: true };
|
|
769
|
-
function fail$1(code, reason) {
|
|
818
|
+
function fail$1(code, reason, end) {
|
|
770
819
|
return {
|
|
771
820
|
ok: false,
|
|
772
821
|
code,
|
|
773
|
-
reason
|
|
822
|
+
reason,
|
|
823
|
+
end
|
|
774
824
|
};
|
|
775
825
|
}
|
|
776
826
|
/**
|
|
@@ -800,8 +850,8 @@ function canLink(model, source, target, kind, options = {}) {
|
|
|
800
850
|
const metamodel = options.metamodel ?? metamodelOf(model);
|
|
801
851
|
const s = resolve(any, source);
|
|
802
852
|
const t = resolve(any, target) ?? any.links.get(refId(target));
|
|
803
|
-
if (!s) return fail$1("unknown-element", `unknown source element "${refId(source)}"
|
|
804
|
-
if (!t) return fail$1("unknown-element", `unknown target element "${refId(target)}"
|
|
853
|
+
if (!s) return fail$1("unknown-element", `unknown source element "${refId(source)}"`, "source");
|
|
854
|
+
if (!t) return fail$1("unknown-element", `unknown target element "${refId(target)}"`, "target");
|
|
805
855
|
let definition = metamodel.links.get(kind);
|
|
806
856
|
if (!definition) return fail$1("unknown-link-kind", `unknown link kind "${String(kind)}"`);
|
|
807
857
|
if (definition.parts && !isLinkRef(t) && metamodel.elements.get(t.kind)?.of === definition.kind) definition = metamodel.links.get(definition.parts.leg) ?? definition;
|
|
@@ -852,7 +902,7 @@ function hubCheck(graph, definition, source, target) {
|
|
|
852
902
|
}
|
|
853
903
|
/** The built-in (iStar 2.0) rules only know element targets. */
|
|
854
904
|
function elementTargetOnly(definition) {
|
|
855
|
-
return fail$1("invalid-target", `the target of ${/^[aeiou]/i.test(definition.label) ? "an" : "a"} ${definition.label} link must be an element, not a link
|
|
905
|
+
return fail$1("invalid-target", `the target of ${/^[aeiou]/i.test(definition.label) ? "an" : "a"} ${definition.label} link must be an element, not a link`, "target");
|
|
856
906
|
}
|
|
857
907
|
function refId(ref) {
|
|
858
908
|
return typeof ref === "string" ? ref : ref.id;
|
|
@@ -928,16 +978,16 @@ const is = (graph, element, ...shortNames) => {
|
|
|
928
978
|
};
|
|
929
979
|
const isDependum = (element) => element.isDependum === true;
|
|
930
980
|
const isALink = (graph, source, target) => {
|
|
931
|
-
if (!is(graph, source, "Actor", "Role")) return fail$1("invalid-source", "the source of Is-A links must be a Role or a general Actor (iStar 2.0 Guide, Page 6).");
|
|
981
|
+
if (!is(graph, source, "Actor", "Role")) return fail$1("invalid-source", "the source of Is-A links must be a Role or a general Actor (iStar 2.0 Guide, Page 6).", "source");
|
|
932
982
|
if (source.kind !== target.kind) return fail$1("actor-types-differ", "the source and target of Is-A links must be of the same type - Actor and Actor, or Role and Role (iStar 2.0 Guide, Page 6).");
|
|
933
|
-
if (!is(graph, target, "Actor", "Role")) return fail$1("invalid-target", "the target of Is-A links must be a Role or a general Actor (iStar 2.0 Guide, Page 6).");
|
|
983
|
+
if (!is(graph, target, "Actor", "Role")) return fail$1("invalid-target", "the target of Is-A links must be a Role or a general Actor (iStar 2.0 Guide, Page 6).", "target");
|
|
934
984
|
if (source.id === target.id) return fail$1("self-link", "you cannot make Is-A links from an actor onto itself.");
|
|
935
985
|
if (graph.isThereLinkBetween(source, target)) return fail$1("duplicate-link", "there can only be one Actor link between the same two actors (iStar 2.0 Guide, Page 14).");
|
|
936
986
|
return OK;
|
|
937
987
|
};
|
|
938
988
|
const participatesInLink = (graph, source, target) => {
|
|
939
|
-
if (!graph.isActor(source)) return fail$1("invalid-source", "the source of a Participates-In link must be some kind of actor (iStar 2.0 Guide, Page 6)");
|
|
940
|
-
if (!graph.isActor(target)) return fail$1("invalid-target", "the target of a Participates-In link must be some kind of actor (iStar 2.0 Guide, Page 6)");
|
|
989
|
+
if (!graph.isActor(source)) return fail$1("invalid-source", "the source of a Participates-In link must be some kind of actor (iStar 2.0 Guide, Page 6)", "source");
|
|
990
|
+
if (!graph.isActor(target)) return fail$1("invalid-target", "the target of a Participates-In link must be some kind of actor (iStar 2.0 Guide, Page 6)", "target");
|
|
941
991
|
if (source.id === target.id) return fail$1("self-link", "you cannot make a Participates-In link from an actor onto itself.");
|
|
942
992
|
if (graph.isThereLinkBetween(source, target)) return fail$1("duplicate-link", "there can only be one Actor link between the same two actors (iStar 2.0 Guide, Page 14).");
|
|
943
993
|
return OK;
|
|
@@ -948,30 +998,30 @@ function owningActorId(graph, element) {
|
|
|
948
998
|
}
|
|
949
999
|
const dependencyLink = (graph, source, target) => {
|
|
950
1000
|
if (source.id === target.id) return fail$1("self-link", "a Dependency link cannot link an element onto itself");
|
|
951
|
-
if (isDependum(source)) return fail$1("dependum-endpoint", "a Dependency link cannot start from a dependum");
|
|
952
|
-
if (isDependum(target)) return fail$1("dependum-endpoint", "a Dependency link cannot end in a dependum");
|
|
1001
|
+
if (isDependum(source)) return fail$1("dependum-endpoint", "a Dependency link cannot start from a dependum", "source");
|
|
1002
|
+
if (isDependum(target)) return fail$1("dependum-endpoint", "a Dependency link cannot end in a dependum", "target");
|
|
953
1003
|
if (owningActorId(graph, source) === owningActorId(graph, target)) return fail$1("same-actor", "a Dependency link must involve two different actors (iStar 2.0 Guide, Page 14)");
|
|
954
|
-
if (graph.isTargetOfType(source, "istar.OrRefinementLink") || graph.isTargetOfType(source, "istar.AndRefinementLink")) return fail$1("refined-depender", "a refined element cannot be the Depender Element in a Dependency link (iStar 2.0 Guide, Page 14). Instead, you can try to add the Dependency originating from a child.");
|
|
955
|
-
if (graph.isTargetOfType(source, "istar.ContributionLink")) return fail$1("contributed-depender", "a contributed element cannot be the Depender Element in a Dependency link (iStar 2.0 Guide, Page 14). Instead, you can try to add the Dependency originating from a child.");
|
|
1004
|
+
if (graph.isTargetOfType(source, "istar.OrRefinementLink") || graph.isTargetOfType(source, "istar.AndRefinementLink")) return fail$1("refined-depender", "a refined element cannot be the Depender Element in a Dependency link (iStar 2.0 Guide, Page 14). Instead, you can try to add the Dependency originating from a child.", "source");
|
|
1005
|
+
if (graph.isTargetOfType(source, "istar.ContributionLink")) return fail$1("contributed-depender", "a contributed element cannot be the Depender Element in a Dependency link (iStar 2.0 Guide, Page 14). Instead, you can try to add the Dependency originating from a child.", "source");
|
|
956
1006
|
return OK;
|
|
957
1007
|
};
|
|
958
1008
|
function refinementLink(label) {
|
|
959
1009
|
const other = label === "AND" ? "istar.OrRefinementLink" : "istar.AndRefinementLink";
|
|
960
1010
|
return (graph, source, target) => {
|
|
961
|
-
if (!is(graph, source, "Task", "Goal")) return fail$1("invalid-source", `the source of an ${label}-refinement link must be a Goal or a Task (iStar 2.0 Guide, Table 1)
|
|
962
|
-
if (!is(graph, target, "Task", "Goal")) return fail$1("invalid-target", `the target of an ${label}-refinement link must be a Goal or a Task (iStar 2.0 Guide, Table 1)
|
|
1011
|
+
if (!is(graph, source, "Task", "Goal")) return fail$1("invalid-source", `the source of an ${label}-refinement link must be a Goal or a Task (iStar 2.0 Guide, Table 1)`, "source");
|
|
1012
|
+
if (!is(graph, target, "Task", "Goal")) return fail$1("invalid-target", `the target of an ${label}-refinement link must be a Goal or a Task (iStar 2.0 Guide, Table 1)`, "target");
|
|
963
1013
|
if (source.id === target.id) return fail$1("self-link", `you cannot make an ${label}-refinement link from an element onto itself`);
|
|
964
1014
|
if (isDependum(source) || isDependum(target)) return fail$1("dependum-endpoint", `you cannot make an ${label}-refinement link with a dependum (iStar 2.0 Guide, Page 14)`);
|
|
965
1015
|
if (source.parent !== target.parent) return fail$1("different-actors", `the source and target of an ${label}-refinement link must pertain to the same actor (iStar 2.0 Guide, Page 14)`);
|
|
966
1016
|
if (graph.isThereLinkBetween(source, target)) return fail$1("duplicate-link", "there can only be one refinement link between the same two elements");
|
|
967
|
-
if (graph.isSourceOfType(target, "istar.DependencyLink")) return fail$1("depender-target", "you cannot refine a Depender Element; that is, an element that is the source of a Dependency (iStar 2.0 Guide, Page 14). Instead, you can try to move the dependency to the sub-element.");
|
|
968
|
-
if (graph.isTargetOfType(target, other)) return fail$1("mixed-refinement", label === "AND" ? "you cannot mix AND-refinements with OR-refinements targeting the same element (iStar 2.0 Guide, Page 10)." : "you cannot mix OR-refinements with AND-refinements targeting the same element (iStar 2.0 Guide, Page 10).");
|
|
1017
|
+
if (graph.isSourceOfType(target, "istar.DependencyLink")) return fail$1("depender-target", "you cannot refine a Depender Element; that is, an element that is the source of a Dependency (iStar 2.0 Guide, Page 14). Instead, you can try to move the dependency to the sub-element.", "target");
|
|
1018
|
+
if (graph.isTargetOfType(target, other)) return fail$1("mixed-refinement", label === "AND" ? "you cannot mix AND-refinements with OR-refinements targeting the same element (iStar 2.0 Guide, Page 10)." : "you cannot mix OR-refinements with AND-refinements targeting the same element (iStar 2.0 Guide, Page 10).", "target");
|
|
969
1019
|
return OK;
|
|
970
1020
|
};
|
|
971
1021
|
}
|
|
972
1022
|
const neededByLink = (graph, source, target) => {
|
|
973
|
-
if (!is(graph, source, "Resource")) return fail$1("invalid-source", "the source of a Needed-By link must be a Resource (iStar 2.0 Guide, Table 1)");
|
|
974
|
-
if (!is(graph, target, "Task")) return fail$1("invalid-target", "the target of a Needed-By link must be a Task (iStar 2.0 Guide, Table 1)");
|
|
1023
|
+
if (!is(graph, source, "Resource")) return fail$1("invalid-source", "the source of a Needed-By link must be a Resource (iStar 2.0 Guide, Table 1)", "source");
|
|
1024
|
+
if (!is(graph, target, "Task")) return fail$1("invalid-target", "the target of a Needed-By link must be a Task (iStar 2.0 Guide, Table 1)", "target");
|
|
975
1025
|
if (source.id === target.id) return fail$1("self-link", "you cannot make a Needed-By link from an element onto itself");
|
|
976
1026
|
if (isDependum(source) || isDependum(target)) return fail$1("dependum-endpoint", "you cannot make a Needed-By link with a dependum (iStar 2.0 Guide, Page 14)");
|
|
977
1027
|
if (source.parent !== target.parent) return fail$1("different-actors", "the source and target of a Needed-By link must pertain to the same actor (iStar 2.0 Guide, Page 14)");
|
|
@@ -979,19 +1029,19 @@ const neededByLink = (graph, source, target) => {
|
|
|
979
1029
|
return OK;
|
|
980
1030
|
};
|
|
981
1031
|
const contributionLink = (graph, source, target) => {
|
|
982
|
-
if (!is(graph, source, "Goal", "Quality", "Task", "Resource")) return fail$1("invalid-source", "the source of a Contribution link must be a Goal, a Quality, a Task or a Resource (iStar 2.0 Guide, Table 1)");
|
|
983
|
-
if (!is(graph, target, "Quality")) return fail$1("invalid-target", "the target of a Contribution link must be a Quality (iStar 2.0 Guide, Table 1)");
|
|
1032
|
+
if (!is(graph, source, "Goal", "Quality", "Task", "Resource")) return fail$1("invalid-source", "the source of a Contribution link must be a Goal, a Quality, a Task or a Resource (iStar 2.0 Guide, Table 1)", "source");
|
|
1033
|
+
if (!is(graph, target, "Quality")) return fail$1("invalid-target", "the target of a Contribution link must be a Quality (iStar 2.0 Guide, Table 1)", "target");
|
|
984
1034
|
if (source.id === target.id) return fail$1("self-link", "you cannot make a Contribution link from an element onto itself (iStar 2.0 Guide, Page 15)");
|
|
985
1035
|
if (isDependum(source) || isDependum(target)) return fail$1("dependum-endpoint", "you cannot make a Contribution link with a dependum (iStar 2.0 Guide, Page 14)");
|
|
986
1036
|
if (source.parent !== target.parent) return fail$1("different-actors", "the source and target of a Contribution link must pertain to the same actor (iStar 2.0 Guide, Page 14)");
|
|
987
1037
|
if (graph.isThereLinkBetween(source, target, "istar.ContributionLink")) return fail$1("duplicate-link", "there can only be one Contribution link between the same two elements");
|
|
988
1038
|
if (graph.isThereLinkBetween(source, target, "istar.QualificationLink")) return fail$1("contribution-and-qualification", "you cannot have Contribution and Qualification links between the same two elements (iStar 2.0 Guide, Page 15)");
|
|
989
|
-
if (graph.isSourceOfType(target, "istar.DependencyLink")) return fail$1("depender-target", "you cannot contribute to a Depender Element; that is, an element that is the source of a Dependency (iStar 2.0 Guide, Page 14). Instead, you can try to move the dependency to the sub-quality.");
|
|
1039
|
+
if (graph.isSourceOfType(target, "istar.DependencyLink")) return fail$1("depender-target", "you cannot contribute to a Depender Element; that is, an element that is the source of a Dependency (iStar 2.0 Guide, Page 14). Instead, you can try to move the dependency to the sub-quality.", "target");
|
|
990
1040
|
return OK;
|
|
991
1041
|
};
|
|
992
1042
|
const qualificationLink = (graph, source, target) => {
|
|
993
|
-
if (!is(graph, source, "Quality")) return fail$1("invalid-source", "the source of a Qualification link must be a Quality (iStar 2.0 Guide, Table 1)");
|
|
994
|
-
if (!is(graph, target, "Goal", "Task", "Resource")) return fail$1("invalid-target", "the target of a Qualification link must be a Goal, a Task or a Resource (iStar 2.0 Guide, Table 1)");
|
|
1043
|
+
if (!is(graph, source, "Quality")) return fail$1("invalid-source", "the source of a Qualification link must be a Quality (iStar 2.0 Guide, Table 1)", "source");
|
|
1044
|
+
if (!is(graph, target, "Goal", "Task", "Resource")) return fail$1("invalid-target", "the target of a Qualification link must be a Goal, a Task or a Resource (iStar 2.0 Guide, Table 1)", "target");
|
|
995
1045
|
if (source.id === target.id) return fail$1("self-link", "you cannot make a Qualification link from an element onto itself");
|
|
996
1046
|
if (isDependum(source) || isDependum(target)) return fail$1("dependum-endpoint", "you cannot make a Qualification link with a dependum (iStar 2.0 Guide, Page 14)");
|
|
997
1047
|
if (source.parent !== target.parent) return fail$1("different-actors", "the source and target of a Qualification link must pertain to the same actor (iStar 2.0 Guide, Page 14)");
|
|
@@ -1031,12 +1081,12 @@ function matchesSelector(graph, element, selector) {
|
|
|
1031
1081
|
function declarativeCheck(graph, definition, rules, rulesKind, source, target) {
|
|
1032
1082
|
const name = definition.label;
|
|
1033
1083
|
const article = /^[aeiou]/i.test(name) ? "an" : "a";
|
|
1034
|
-
if (!rules.sources.some((selector) => matchesSelector(graph, source, selector))) return fail$1("invalid-source", `the source of ${article} ${name} link must be ${describe(graph, rules.sources)}
|
|
1084
|
+
if (!rules.sources.some((selector) => matchesSelector(graph, source, selector))) return fail$1("invalid-source", `the source of ${article} ${name} link must be ${describe(graph, rules.sources)}`, "source");
|
|
1035
1085
|
if (!rules.targets.some((selector) => matchesSelector(graph, target, selector))) {
|
|
1036
1086
|
const targetKind = isLinkRef(target) ? void 0 : graph.metamodel.elements.get(target.kind);
|
|
1037
1087
|
const hubOf = targetKind?.role === "hub" && targetKind.of !== void 0 ? graph.metamodel.links.get(targetKind.of) : void 0;
|
|
1038
|
-
if (hubOf) return fail$1("hub-target", `you cannot make ${article} ${name} link towards the hub of ${/^[aeiou]/i.test(hubOf.label) ? "an" : "a"} ${hubOf.label} link
|
|
1039
|
-
return fail$1("invalid-target", `the target of ${article} ${name} link must be ${describe(graph, rules.targets)}
|
|
1088
|
+
if (hubOf) return fail$1("hub-target", `you cannot make ${article} ${name} link towards the hub of ${/^[aeiou]/i.test(hubOf.label) ? "an" : "a"} ${hubOf.label} link`, "target");
|
|
1089
|
+
return fail$1("invalid-target", `the target of ${article} ${name} link must be ${describe(graph, rules.targets)}`, "target");
|
|
1040
1090
|
}
|
|
1041
1091
|
if (!rules.allowSelf && source.id === target.id) return fail$1("self-link", `you cannot make ${article} ${name} link from an element onto itself`);
|
|
1042
1092
|
if (definition.category === "node" && !isLinkRef(target)) {
|
|
@@ -1791,6 +1841,20 @@ function withFileMetamodel(model, block) {
|
|
|
1791
1841
|
//#region src/operations.ts
|
|
1792
1842
|
var ModelOperationError = class extends Error {
|
|
1793
1843
|
name = "ModelOperationError";
|
|
1844
|
+
/** The element or link the error is about, when the operation named one. */
|
|
1845
|
+
elementId;
|
|
1846
|
+
constructor(message, elementId) {
|
|
1847
|
+
super(message);
|
|
1848
|
+
this.elementId = elementId;
|
|
1849
|
+
}
|
|
1850
|
+
/** As a `GoalDiagnostic`, anchored on `elementId` (empty string if the error names none). */
|
|
1851
|
+
toDiagnostic() {
|
|
1852
|
+
return {
|
|
1853
|
+
elementId: this.elementId ?? "",
|
|
1854
|
+
severity: "error",
|
|
1855
|
+
message: this.message
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1794
1858
|
};
|
|
1795
1859
|
const defaultIdGenerator = () => {
|
|
1796
1860
|
const c = globalThis.crypto;
|
|
@@ -1820,12 +1884,12 @@ function derive(from, changes) {
|
|
|
1820
1884
|
}
|
|
1821
1885
|
function requireElement(model, id) {
|
|
1822
1886
|
const element = model.elements.get(id);
|
|
1823
|
-
if (!element) throw new ModelOperationError(`unknown element "${id}"
|
|
1887
|
+
if (!element) throw new ModelOperationError(`unknown element "${id}"`, id);
|
|
1824
1888
|
return element;
|
|
1825
1889
|
}
|
|
1826
1890
|
function requireLink(model, id) {
|
|
1827
1891
|
const link = model.links.get(id);
|
|
1828
|
-
if (!link) throw new ModelOperationError(`unknown link "${id}"
|
|
1892
|
+
if (!link) throw new ModelOperationError(`unknown link "${id}"`, id);
|
|
1829
1893
|
return link;
|
|
1830
1894
|
}
|
|
1831
1895
|
function withoutUndefined(value) {
|
|
@@ -1839,10 +1903,10 @@ function addElement(model, input, ctx = {}) {
|
|
|
1839
1903
|
const definition = metamodel.elements.get(input.kind);
|
|
1840
1904
|
if (!definition) throw new ModelOperationError(`unknown element kind "${input.kind}" in ${metamodel.name}`);
|
|
1841
1905
|
const id = input.id ?? (ctx.createId ?? defaultIdGenerator)();
|
|
1842
|
-
if (m.elements.has(id) || m.links.has(id)) throw new ModelOperationError(`id "${id}" is already in use
|
|
1906
|
+
if (m.elements.has(id) || m.links.has(id)) throw new ModelOperationError(`id "${id}" is already in use`, id);
|
|
1843
1907
|
if (input.parent !== void 0) {
|
|
1844
1908
|
if (definition.category === "actor") throw new ModelOperationError("actors cannot be nested");
|
|
1845
|
-
if (!isActorIn$1(m, requireElement(m, input.parent).kind)) throw new ModelOperationError(`parent "${input.parent}" is not an actor
|
|
1909
|
+
if (!isActorIn$1(m, requireElement(m, input.parent).kind)) throw new ModelOperationError(`parent "${input.parent}" is not an actor`, input.parent);
|
|
1846
1910
|
}
|
|
1847
1911
|
const element = withoutUndefined({
|
|
1848
1912
|
id,
|
|
@@ -1911,10 +1975,10 @@ function moveElement(model, id, x, y) {
|
|
|
1911
1975
|
function nestElement(model, id, parent) {
|
|
1912
1976
|
const m = any(model);
|
|
1913
1977
|
const current = requireElement(m, id);
|
|
1914
|
-
if (isActorIn$1(m, current.kind)) throw new ModelOperationError("actors cannot be nested");
|
|
1915
|
-
if (current.isDependum) throw new ModelOperationError("a dependum cannot be nested");
|
|
1978
|
+
if (isActorIn$1(m, current.kind)) throw new ModelOperationError("actors cannot be nested", id);
|
|
1979
|
+
if (current.isDependum) throw new ModelOperationError("a dependum cannot be nested", id);
|
|
1916
1980
|
if ((current.parent ?? null) === parent) return model;
|
|
1917
|
-
if (parent !== null && !isActorIn$1(m, requireElement(m, parent).kind)) throw new ModelOperationError(`parent "${parent}" is not an actor
|
|
1981
|
+
if (parent !== null && !isActorIn$1(m, requireElement(m, parent).kind)) throw new ModelOperationError(`parent "${parent}" is not an actor`, parent);
|
|
1918
1982
|
const elements = new Map(m.elements);
|
|
1919
1983
|
elements.delete(id);
|
|
1920
1984
|
const { parent: _old, ...rest } = current;
|
|
@@ -1992,12 +2056,27 @@ function removeCascade(model, elementIds, linkIds) {
|
|
|
1992
2056
|
});
|
|
1993
2057
|
}
|
|
1994
2058
|
/**
|
|
2059
|
+
* Maps a failing `connect` result to a `GoalDiagnostic`, anchored on the end named by the
|
|
2060
|
+
* `LinkCheck`'s `end` field (`target` when a check doesn't single out one end, such as
|
|
2061
|
+
* `self-link` or `actor-types-differ`). `undefined` for a successful result.
|
|
2062
|
+
*/
|
|
2063
|
+
function connectResultToDiagnostic(result, endpoints) {
|
|
2064
|
+
if (result.ok) return void 0;
|
|
2065
|
+
return {
|
|
2066
|
+
elementId: (result.end ?? "target") === "source" ? endpoints.source : endpoints.target,
|
|
2067
|
+
severity: "error",
|
|
2068
|
+
message: result.reason,
|
|
2069
|
+
code: result.code
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
/**
|
|
1995
2073
|
* Adds a link between two elements if the iStar constraints allow it.
|
|
1996
2074
|
*
|
|
1997
2075
|
* With an n-ary kind (`arity: 'n'`), a `target` that is one of the kind's hubs gets a new leg
|
|
1998
|
-
* from `source` (see `addLeg`).
|
|
1999
|
-
* target
|
|
2000
|
-
*
|
|
2076
|
+
* from `source` (see `addLeg`), taking `input.id` or `input.legId` (default a fresh id) as the
|
|
2077
|
+
* leg's id. Any other target gets a new hub, centred between source and target, with a leg from
|
|
2078
|
+
* `source` and an end to `target`. `input.id` and `customProperties` then go to the hub, `label`
|
|
2079
|
+
* and `name` to the end, and `legId`/`endId` (default fresh ids) to the leg and end.
|
|
2001
2080
|
*/
|
|
2002
2081
|
function connect(model, input, options = {}) {
|
|
2003
2082
|
const m = any(model);
|
|
@@ -2026,7 +2105,7 @@ function connect(model, input, options = {}) {
|
|
|
2026
2105
|
if (!m.links.has(target)) requireElement(m, target);
|
|
2027
2106
|
}
|
|
2028
2107
|
const id = input.id ?? (options.createId ?? defaultIdGenerator)();
|
|
2029
|
-
if (m.elements.has(id) || m.links.has(id)) throw new ModelOperationError(`id "${id}" is already in use
|
|
2108
|
+
if (m.elements.has(id) || m.links.has(id)) throw new ModelOperationError(`id "${id}" is already in use`, id);
|
|
2030
2109
|
const link = withoutUndefined({
|
|
2031
2110
|
id,
|
|
2032
2111
|
kind,
|
|
@@ -2047,7 +2126,13 @@ function connectHub(m, definition, input, options) {
|
|
|
2047
2126
|
const metamodel = metaOf(m);
|
|
2048
2127
|
const parts = definition.parts;
|
|
2049
2128
|
const { source, target } = input;
|
|
2050
|
-
if (m.elements.get(target)?.kind === parts.hub)
|
|
2129
|
+
if (m.elements.get(target)?.kind === parts.hub) {
|
|
2130
|
+
const legId = input.id ?? input.legId;
|
|
2131
|
+
return addLeg(typed(m), target, source, {
|
|
2132
|
+
...options,
|
|
2133
|
+
...legId === void 0 ? {} : { id: legId }
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2051
2136
|
if (!options.force) {
|
|
2052
2137
|
const check = canLink(m, source, target, definition.kind);
|
|
2053
2138
|
if (!check.ok) return check;
|
|
@@ -2056,8 +2141,8 @@ function connectHub(m, definition, input, options) {
|
|
|
2056
2141
|
const to = requireElement(m, target);
|
|
2057
2142
|
const createId = options.createId ?? defaultIdGenerator;
|
|
2058
2143
|
const hubId = input.id ?? createId();
|
|
2059
|
-
const legId = createId();
|
|
2060
|
-
const endId = createId();
|
|
2144
|
+
const legId = input.legId ?? createId();
|
|
2145
|
+
const endId = input.endId ?? createId();
|
|
2061
2146
|
for (const [i, id] of [
|
|
2062
2147
|
hubId,
|
|
2063
2148
|
legId,
|
|
@@ -2066,7 +2151,7 @@ function connectHub(m, definition, input, options) {
|
|
|
2066
2151
|
hubId,
|
|
2067
2152
|
legId,
|
|
2068
2153
|
endId
|
|
2069
|
-
].indexOf(id) !== i) throw new ModelOperationError(`id "${id}" is already in use
|
|
2154
|
+
].indexOf(id) !== i) throw new ModelOperationError(`id "${id}" is already in use`, id);
|
|
2070
2155
|
const position = defaultHubPosition(m, from, to, metamodel.elements.get(parts.hub)?.size ?? {
|
|
2071
2156
|
width: 20,
|
|
2072
2157
|
height: 20
|
|
@@ -2117,14 +2202,14 @@ function addLeg(model, hubId, source, options = {}) {
|
|
|
2117
2202
|
const metamodel = metaOf(m);
|
|
2118
2203
|
const hub = requireElement(m, hubId);
|
|
2119
2204
|
const base = metamodel.links.get(metamodel.elements.get(hub.kind)?.of ?? "");
|
|
2120
|
-
if (metamodel.elements.get(hub.kind)?.role !== "hub" || !base?.parts) throw new ModelOperationError(`"${hubId}" is not the hub of an n-ary link
|
|
2205
|
+
if (metamodel.elements.get(hub.kind)?.role !== "hub" || !base?.parts) throw new ModelOperationError(`"${hubId}" is not the hub of an n-ary link`, hubId);
|
|
2121
2206
|
const kind = base.parts.leg;
|
|
2122
2207
|
if (!options.force) {
|
|
2123
2208
|
const check = canLink(m, source, hubId, kind);
|
|
2124
2209
|
if (!check.ok) return check;
|
|
2125
2210
|
} else requireElement(m, source);
|
|
2126
2211
|
const id = options.id ?? (options.createId ?? defaultIdGenerator)();
|
|
2127
|
-
if (m.elements.has(id) || m.links.has(id)) throw new ModelOperationError(`id "${id}" is already in use
|
|
2212
|
+
if (m.elements.has(id) || m.links.has(id)) throw new ModelOperationError(`id "${id}" is already in use`, id);
|
|
2128
2213
|
const leg = {
|
|
2129
2214
|
id,
|
|
2130
2215
|
kind,
|
|
@@ -2147,7 +2232,7 @@ function addLeg(model, hubId, source, options = {}) {
|
|
|
2147
2232
|
function removeLeg(model, legId) {
|
|
2148
2233
|
const m = any(model);
|
|
2149
2234
|
const leg = requireLink(m, legId);
|
|
2150
|
-
if (metaOf(m).links.get(leg.kind)?.role !== "leg") throw new ModelOperationError(`"${legId}" is not a leg of an n-ary link
|
|
2235
|
+
if (metaOf(m).links.get(leg.kind)?.role !== "leg") throw new ModelOperationError(`"${legId}" is not a leg of an n-ary link`, legId);
|
|
2151
2236
|
return disconnect(model, legId);
|
|
2152
2237
|
}
|
|
2153
2238
|
/** Adds depender → dependum → dependee. */
|
|
@@ -2191,7 +2276,7 @@ function addDependency(model, input, options = {}) {
|
|
|
2191
2276
|
source: dependum.id,
|
|
2192
2277
|
target: dependee.id
|
|
2193
2278
|
};
|
|
2194
|
-
for (const id of [id1, id2]) if (added.model.elements.has(id) || added.model.links.has(id) || id1 === id2) throw new ModelOperationError(`id "${id}" is already in use
|
|
2279
|
+
for (const id of [id1, id2]) if (added.model.elements.has(id) || added.model.links.has(id) || id1 === id2) throw new ModelOperationError(`id "${id}" is already in use`, id);
|
|
2195
2280
|
return {
|
|
2196
2281
|
ok: true,
|
|
2197
2282
|
model: typed(derive(m, {
|
|
@@ -2973,11 +3058,108 @@ function fromNodeIdDiagnostics(diagnostics) {
|
|
|
2973
3058
|
return result;
|
|
2974
3059
|
}
|
|
2975
3060
|
//#endregion
|
|
3061
|
+
//#region src/palette.ts
|
|
3062
|
+
/**
|
|
3063
|
+
* The data part of each kind's palette entry: label, grouping, ordering, and the presets a
|
|
3064
|
+
* toolbar entry creates with (a link's `value`, a dependency's `dependum`, an element's
|
|
3065
|
+
* `properties`). React-free so a host can answer "what can I add here" (GLSP's
|
|
3066
|
+
* `RequestContextActions`) without the react package. Icons, titles and components are
|
|
3067
|
+
* presentation and stay in `packages/react/src/registry`, which derives its default entries
|
|
3068
|
+
* from `DEFAULT_PALETTE`.
|
|
3069
|
+
*/
|
|
3070
|
+
function defaultElementEntry(kind, index) {
|
|
3071
|
+
const actor = isActorKind(kind);
|
|
3072
|
+
return {
|
|
3073
|
+
label: shortKindName(kind),
|
|
3074
|
+
order: actor ? index : 30 + index,
|
|
3075
|
+
section: actor ? "actors" : "elements",
|
|
3076
|
+
...actor ? { group: "actors" } : {}
|
|
3077
|
+
};
|
|
3078
|
+
}
|
|
3079
|
+
const CONTRIBUTION_ENTRY_LABELS = {
|
|
3080
|
+
make: "Make (++)",
|
|
3081
|
+
help: "Help (+)",
|
|
3082
|
+
hurt: "Hurt (-)",
|
|
3083
|
+
break: "Break (--)"
|
|
3084
|
+
};
|
|
3085
|
+
const LINK_ENTRIES = {
|
|
3086
|
+
"istar.IsALink": [{
|
|
3087
|
+
label: "Is A",
|
|
3088
|
+
order: 10,
|
|
3089
|
+
group: "actor-links",
|
|
3090
|
+
section: "actors"
|
|
3091
|
+
}],
|
|
3092
|
+
"istar.ParticipatesInLink": [{
|
|
3093
|
+
label: "Participates-In",
|
|
3094
|
+
order: 11,
|
|
3095
|
+
group: "actor-links",
|
|
3096
|
+
section: "actors"
|
|
3097
|
+
}],
|
|
3098
|
+
"istar.DependencyLink": NODE_KINDS.map((dependum, i) => ({
|
|
3099
|
+
label: `${shortKindName(dependum)} dependency`,
|
|
3100
|
+
dependum,
|
|
3101
|
+
order: 20 + i,
|
|
3102
|
+
group: "dependencies",
|
|
3103
|
+
section: "actors"
|
|
3104
|
+
})),
|
|
3105
|
+
"istar.AndRefinementLink": [{
|
|
3106
|
+
label: "And",
|
|
3107
|
+
order: 40,
|
|
3108
|
+
section: "elements"
|
|
3109
|
+
}],
|
|
3110
|
+
"istar.OrRefinementLink": [{
|
|
3111
|
+
label: "Or",
|
|
3112
|
+
order: 41,
|
|
3113
|
+
section: "elements"
|
|
3114
|
+
}],
|
|
3115
|
+
"istar.NeededByLink": [{
|
|
3116
|
+
label: "Needed-By",
|
|
3117
|
+
order: 42,
|
|
3118
|
+
section: "elements"
|
|
3119
|
+
}],
|
|
3120
|
+
"istar.QualificationLink": [{
|
|
3121
|
+
label: "Qualification",
|
|
3122
|
+
order: 43,
|
|
3123
|
+
section: "elements"
|
|
3124
|
+
}],
|
|
3125
|
+
"istar.ContributionLink": CONTRIBUTION_LABELS.map((value, i) => ({
|
|
3126
|
+
label: CONTRIBUTION_ENTRY_LABELS[value],
|
|
3127
|
+
value,
|
|
3128
|
+
order: 50 + i,
|
|
3129
|
+
group: "contributions",
|
|
3130
|
+
section: "elements"
|
|
3131
|
+
}))
|
|
3132
|
+
};
|
|
3133
|
+
/** piStar's dropdown buttons. */
|
|
3134
|
+
const DEFAULT_PALETTE_GROUPS = {
|
|
3135
|
+
actors: { label: "Actor" },
|
|
3136
|
+
"actor-links": { label: "Actor links" },
|
|
3137
|
+
dependencies: { label: "Dependency" },
|
|
3138
|
+
contributions: { label: "Contribution" }
|
|
3139
|
+
};
|
|
3140
|
+
function buildDefaultPalette() {
|
|
3141
|
+
const elements = {};
|
|
3142
|
+
ELEMENT_KINDS.forEach((kind, i) => {
|
|
3143
|
+
elements[kind] = defaultElementEntry(kind, i);
|
|
3144
|
+
});
|
|
3145
|
+
const links = {};
|
|
3146
|
+
for (const kind of LINK_KINDS) links[kind] = LINK_ENTRIES[kind];
|
|
3147
|
+
return {
|
|
3148
|
+
elements,
|
|
3149
|
+
links,
|
|
3150
|
+
groups: DEFAULT_PALETTE_GROUPS
|
|
3151
|
+
};
|
|
3152
|
+
}
|
|
3153
|
+
/** The palette data of iStar 2.0, as piStar's toolbar presents it. */
|
|
3154
|
+
const DEFAULT_PALETTE = buildDefaultPalette();
|
|
3155
|
+
//#endregion
|
|
2976
3156
|
exports.ACTOR_KINDS = ACTOR_KINDS;
|
|
2977
3157
|
exports.ACTOR_LINK_KINDS = ACTOR_LINK_KINDS;
|
|
3158
|
+
exports.ACTOR_PADDING = ACTOR_PADDING;
|
|
2978
3159
|
exports.CONTRIBUTION_LABELS = CONTRIBUTION_LABELS;
|
|
2979
3160
|
exports.DEFAULT_DIAGRAM = DEFAULT_DIAGRAM;
|
|
2980
3161
|
exports.DEFAULT_ELEMENT_SIZE = DEFAULT_ELEMENT_SIZE;
|
|
3162
|
+
exports.DEFAULT_PALETTE = DEFAULT_PALETTE;
|
|
2981
3163
|
exports.DEPENDENCY_LINK_KINDS = DEPENDENCY_LINK_KINDS;
|
|
2982
3164
|
exports.DIAGNOSTIC_SEVERITIES = DIAGNOSTIC_SEVERITIES;
|
|
2983
3165
|
exports.DIAGRAM_LAYOUT_KEY = DIAGRAM_LAYOUT_KEY;
|
|
@@ -3002,6 +3184,7 @@ exports.NODE_LINK_KINDS = NODE_LINK_KINDS;
|
|
|
3002
3184
|
exports.PISTAR_TOOL = PISTAR_TOOL;
|
|
3003
3185
|
exports.PistarParseError = PistarParseError;
|
|
3004
3186
|
exports.PistarWriteError = PistarWriteError;
|
|
3187
|
+
exports.actorBounds = actorBounds;
|
|
3005
3188
|
exports.addDependency = addDependency;
|
|
3006
3189
|
exports.addElement = addElement;
|
|
3007
3190
|
exports.addLeg = addLeg;
|
|
@@ -3010,6 +3193,7 @@ exports.canLink = canLink;
|
|
|
3010
3193
|
exports.childrenOf = childrenOf;
|
|
3011
3194
|
exports.compareSeverity = compareSeverity;
|
|
3012
3195
|
exports.connect = connect;
|
|
3196
|
+
exports.connectResultToDiagnostic = connectResultToDiagnostic;
|
|
3013
3197
|
exports.createDiagnosticsStore = createDiagnosticsStore;
|
|
3014
3198
|
exports.createEmptyModel = createEmptyModel;
|
|
3015
3199
|
exports.createModelStore = createModelStore;
|