@istar-ts/core 0.10.1 → 0.11.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 +53 -0
- package/dist/index.cjs +251 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.js +245 -60
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -267,7 +267,9 @@ function extendMetamodel(base, extension) {
|
|
|
267
267
|
} } : {},
|
|
268
268
|
...parent ? { behavesLike: parent.kind } : {},
|
|
269
269
|
pistarType: type,
|
|
270
|
-
extension: extension.name
|
|
270
|
+
extension: extension.name,
|
|
271
|
+
...declaration.shape ? { shape: declaration.shape } : {},
|
|
272
|
+
...declaration.textBox ? { textBox: declaration.textBox } : {}
|
|
271
273
|
};
|
|
272
274
|
elements.set(kind, definition);
|
|
273
275
|
elementsByType.set(type, kind);
|
|
@@ -300,7 +302,8 @@ function extendMetamodel(base, extension) {
|
|
|
300
302
|
...declaration.rules ? { rules: declaration.rules } : {},
|
|
301
303
|
...declaration.check ? { check: declaration.check } : {},
|
|
302
304
|
pistarType: type,
|
|
303
|
-
extension: extension.name
|
|
305
|
+
extension: extension.name,
|
|
306
|
+
...declaration.line ? { line: declaration.line } : {}
|
|
304
307
|
};
|
|
305
308
|
links.set(kind, definition);
|
|
306
309
|
linksByType.set(type, kind);
|
|
@@ -391,10 +394,27 @@ function withMetamodel(model, metamodel) {
|
|
|
391
394
|
else metamodels.set(model, metamodel);
|
|
392
395
|
return model;
|
|
393
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* For models whose file declares part of their metamodel (see `withFileMetamodel`): the
|
|
399
|
+
* metamodel the host supplied, without the file's kinds.
|
|
400
|
+
*/
|
|
401
|
+
const hostMetamodels = /* @__PURE__ */ new WeakMap();
|
|
402
|
+
/** @internal Records the host part of a model's metamodel (used by serialization). */
|
|
403
|
+
function setHostMetamodel(model, metamodel) {
|
|
404
|
+
if (metamodel) hostMetamodels.set(model, metamodel);
|
|
405
|
+
else hostMetamodels.delete(model);
|
|
406
|
+
}
|
|
407
|
+
/** @internal The host part of a model's metamodel, if the file declares the rest. */
|
|
408
|
+
function hostMetamodelOf(model) {
|
|
409
|
+
return hostMetamodels.get(model);
|
|
410
|
+
}
|
|
394
411
|
/** Gives `to` the metamodel of `from` (used for every model an operation derives). */
|
|
395
412
|
function inheritMetamodel(from, to) {
|
|
413
|
+
if (from === to) return to;
|
|
396
414
|
const metamodel = metamodels.get(from);
|
|
397
|
-
if (metamodel
|
|
415
|
+
if (metamodel) metamodels.set(to, metamodel);
|
|
416
|
+
const host = hostMetamodels.get(from);
|
|
417
|
+
if (host) hostMetamodels.set(to, host);
|
|
398
418
|
return to;
|
|
399
419
|
}
|
|
400
420
|
function createEmptyModel(diagram = DEFAULT_DIAGRAM, options = {}) {
|
|
@@ -466,7 +486,7 @@ function dependencyLinksOf(model, dependumId) {
|
|
|
466
486
|
//#endregion
|
|
467
487
|
//#region src/constraints.ts
|
|
468
488
|
const OK = { ok: true };
|
|
469
|
-
function fail(code, reason) {
|
|
489
|
+
function fail$1(code, reason) {
|
|
470
490
|
return {
|
|
471
491
|
ok: false,
|
|
472
492
|
code,
|
|
@@ -487,10 +507,10 @@ function canLink(model, source, target, kind, options = {}) {
|
|
|
487
507
|
const metamodel = options.metamodel ?? metamodelOf(model);
|
|
488
508
|
const s = resolve(any, source);
|
|
489
509
|
const t = resolve(any, target);
|
|
490
|
-
if (!s) return fail("unknown-element", `unknown source element "${refId(source)}"`);
|
|
491
|
-
if (!t) return fail("unknown-element", `unknown target element "${refId(target)}"`);
|
|
510
|
+
if (!s) return fail$1("unknown-element", `unknown source element "${refId(source)}"`);
|
|
511
|
+
if (!t) return fail$1("unknown-element", `unknown target element "${refId(target)}"`);
|
|
492
512
|
const definition = metamodel.links.get(kind);
|
|
493
|
-
if (!definition) return fail("unknown-link-kind", `unknown link kind "${String(kind)}"`);
|
|
513
|
+
if (!definition) return fail$1("unknown-link-kind", `unknown link kind "${String(kind)}"`);
|
|
494
514
|
return checkWith(new Graph(any, metamodel), definition, s, t);
|
|
495
515
|
}
|
|
496
516
|
/** Runs the rule a link kind resolves to, then every extra predicate along its chain. */
|
|
@@ -502,7 +522,7 @@ function checkWith(graph, definition, source, target) {
|
|
|
502
522
|
let result;
|
|
503
523
|
if (root.rules) result = declarativeCheck(graph, definition, root.rules, root.kind, source, target);
|
|
504
524
|
else if (builtIn) result = builtIn(graph, source, target);
|
|
505
|
-
else result = fail("unknown-link-kind", `link kind "${definition.kind}" has no rules`);
|
|
525
|
+
else result = fail$1("unknown-link-kind", `link kind "${definition.kind}" has no rules`);
|
|
506
526
|
if (!result.ok) return result;
|
|
507
527
|
const context = graph.context(definition.kind, source, target);
|
|
508
528
|
for (const step of chain.toReversed()) {
|
|
@@ -586,18 +606,18 @@ const is = (graph, element, ...shortNames) => {
|
|
|
586
606
|
};
|
|
587
607
|
const isDependum = (element) => element.isDependum === true;
|
|
588
608
|
const isALink = (graph, source, target) => {
|
|
589
|
-
if (!is(graph, source, "Actor", "Role")) return fail("invalid-source", "the source of Is-A links must be a Role or a general Actor (iStar 2.0 Guide, Page 6).");
|
|
590
|
-
if (source.kind !== target.kind) return fail("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).");
|
|
591
|
-
if (!is(graph, target, "Actor", "Role")) return fail("invalid-target", "the target of Is-A links must be a Role or a general Actor (iStar 2.0 Guide, Page 6).");
|
|
592
|
-
if (source.id === target.id) return fail("self-link", "you cannot make Is-A links from an actor onto itself.");
|
|
593
|
-
if (graph.isThereLinkBetween(source, target)) return fail("duplicate-link", "there can only be one Actor link between the same two actors (iStar 2.0 Guide, Page 14).");
|
|
609
|
+
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).");
|
|
610
|
+
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).");
|
|
611
|
+
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).");
|
|
612
|
+
if (source.id === target.id) return fail$1("self-link", "you cannot make Is-A links from an actor onto itself.");
|
|
613
|
+
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).");
|
|
594
614
|
return OK;
|
|
595
615
|
};
|
|
596
616
|
const participatesInLink = (graph, source, target) => {
|
|
597
|
-
if (!graph.isActor(source)) return fail("invalid-source", "the source of a Participates-In link must be some kind of actor (iStar 2.0 Guide, Page 6)");
|
|
598
|
-
if (!graph.isActor(target)) return fail("invalid-target", "the target of a Participates-In link must be some kind of actor (iStar 2.0 Guide, Page 6)");
|
|
599
|
-
if (source.id === target.id) return fail("self-link", "you cannot make a Participates-In link from an actor onto itself.");
|
|
600
|
-
if (graph.isThereLinkBetween(source, target)) return fail("duplicate-link", "there can only be one Actor link between the same two actors (iStar 2.0 Guide, Page 14).");
|
|
617
|
+
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)");
|
|
618
|
+
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)");
|
|
619
|
+
if (source.id === target.id) return fail$1("self-link", "you cannot make a Participates-In link from an actor onto itself.");
|
|
620
|
+
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).");
|
|
601
621
|
return OK;
|
|
602
622
|
};
|
|
603
623
|
/** The actor an element belongs to: itself for actors, its parent for inner elements. */
|
|
@@ -605,56 +625,56 @@ function owningActorId(graph, element) {
|
|
|
605
625
|
return graph.isActor(element) ? element.id : element.parent;
|
|
606
626
|
}
|
|
607
627
|
const dependencyLink = (graph, source, target) => {
|
|
608
|
-
if (source.id === target.id) return fail("self-link", "a Dependency link cannot link an element onto itself");
|
|
609
|
-
if (isDependum(source)) return fail("dependum-endpoint", "a Dependency link cannot start from a dependum");
|
|
610
|
-
if (isDependum(target)) return fail("dependum-endpoint", "a Dependency link cannot end in a dependum");
|
|
611
|
-
if (owningActorId(graph, source) === owningActorId(graph, target)) return fail("same-actor", "a Dependency link must involve two different actors (iStar 2.0 Guide, Page 14)");
|
|
612
|
-
if (graph.isTargetOfType(source, "istar.OrRefinementLink") || graph.isTargetOfType(source, "istar.AndRefinementLink")) return fail("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.");
|
|
613
|
-
if (graph.isTargetOfType(source, "istar.ContributionLink")) return fail("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.");
|
|
628
|
+
if (source.id === target.id) return fail$1("self-link", "a Dependency link cannot link an element onto itself");
|
|
629
|
+
if (isDependum(source)) return fail$1("dependum-endpoint", "a Dependency link cannot start from a dependum");
|
|
630
|
+
if (isDependum(target)) return fail$1("dependum-endpoint", "a Dependency link cannot end in a dependum");
|
|
631
|
+
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)");
|
|
632
|
+
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.");
|
|
633
|
+
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.");
|
|
614
634
|
return OK;
|
|
615
635
|
};
|
|
616
636
|
function refinementLink(label) {
|
|
617
637
|
const other = label === "AND" ? "istar.OrRefinementLink" : "istar.AndRefinementLink";
|
|
618
638
|
return (graph, source, target) => {
|
|
619
|
-
if (!is(graph, source, "Task", "Goal")) return fail("invalid-source", `the source of an ${label}-refinement link must be a Goal or a Task (iStar 2.0 Guide, Table 1)`);
|
|
620
|
-
if (!is(graph, target, "Task", "Goal")) return fail("invalid-target", `the target of an ${label}-refinement link must be a Goal or a Task (iStar 2.0 Guide, Table 1)`);
|
|
621
|
-
if (source.id === target.id) return fail("self-link", `you cannot make an ${label}-refinement link from an element onto itself`);
|
|
622
|
-
if (isDependum(source) || isDependum(target)) return fail("dependum-endpoint", `you cannot make an ${label}-refinement link with a dependum (iStar 2.0 Guide, Page 14)`);
|
|
623
|
-
if (source.parent !== target.parent) return fail("different-actors", `the source and target of an ${label}-refinement link must pertain to the same actor (iStar 2.0 Guide, Page 14)`);
|
|
624
|
-
if (graph.isThereLinkBetween(source, target)) return fail("duplicate-link", "there can only be one refinement link between the same two elements");
|
|
625
|
-
if (graph.isSourceOfType(target, "istar.DependencyLink")) return fail("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.");
|
|
626
|
-
if (graph.isTargetOfType(target, other)) return fail("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).");
|
|
639
|
+
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)`);
|
|
640
|
+
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)`);
|
|
641
|
+
if (source.id === target.id) return fail$1("self-link", `you cannot make an ${label}-refinement link from an element onto itself`);
|
|
642
|
+
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)`);
|
|
643
|
+
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)`);
|
|
644
|
+
if (graph.isThereLinkBetween(source, target)) return fail$1("duplicate-link", "there can only be one refinement link between the same two elements");
|
|
645
|
+
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.");
|
|
646
|
+
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).");
|
|
627
647
|
return OK;
|
|
628
648
|
};
|
|
629
649
|
}
|
|
630
650
|
const neededByLink = (graph, source, target) => {
|
|
631
|
-
if (!is(graph, source, "Resource")) return fail("invalid-source", "the source of a Needed-By link must be a Resource (iStar 2.0 Guide, Table 1)");
|
|
632
|
-
if (!is(graph, target, "Task")) return fail("invalid-target", "the target of a Needed-By link must be a Task (iStar 2.0 Guide, Table 1)");
|
|
633
|
-
if (source.id === target.id) return fail("self-link", "you cannot make a Needed-By link from an element onto itself");
|
|
634
|
-
if (isDependum(source) || isDependum(target)) return fail("dependum-endpoint", "you cannot make a Needed-By link with a dependum (iStar 2.0 Guide, Page 14)");
|
|
635
|
-
if (source.parent !== target.parent) return fail("different-actors", "the source and target of a Needed-By link must pertain to the same actor (iStar 2.0 Guide, Page 14)");
|
|
636
|
-
if (graph.isThereLinkBetween(source, target)) return fail("duplicate-link", "there can only be one Needed-By link between the same two elements");
|
|
651
|
+
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)");
|
|
652
|
+
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)");
|
|
653
|
+
if (source.id === target.id) return fail$1("self-link", "you cannot make a Needed-By link from an element onto itself");
|
|
654
|
+
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)");
|
|
655
|
+
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)");
|
|
656
|
+
if (graph.isThereLinkBetween(source, target)) return fail$1("duplicate-link", "there can only be one Needed-By link between the same two elements");
|
|
637
657
|
return OK;
|
|
638
658
|
};
|
|
639
659
|
const contributionLink = (graph, source, target) => {
|
|
640
|
-
if (!is(graph, source, "Goal", "Quality", "Task", "Resource")) return fail("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)");
|
|
641
|
-
if (!is(graph, target, "Quality")) return fail("invalid-target", "the target of a Contribution link must be a Quality (iStar 2.0 Guide, Table 1)");
|
|
642
|
-
if (source.id === target.id) return fail("self-link", "you cannot make a Contribution link from an element onto itself (iStar 2.0 Guide, Page 15)");
|
|
643
|
-
if (isDependum(source) || isDependum(target)) return fail("dependum-endpoint", "you cannot make a Contribution link with a dependum (iStar 2.0 Guide, Page 14)");
|
|
644
|
-
if (source.parent !== target.parent) return fail("different-actors", "the source and target of a Contribution link must pertain to the same actor (iStar 2.0 Guide, Page 14)");
|
|
645
|
-
if (graph.isThereLinkBetween(source, target, "istar.ContributionLink")) return fail("duplicate-link", "there can only be one Contribution link between the same two elements");
|
|
646
|
-
if (graph.isThereLinkBetween(source, target, "istar.QualificationLink")) return fail("contribution-and-qualification", "you cannot have Contribution and Qualification links between the same two elements (iStar 2.0 Guide, Page 15)");
|
|
647
|
-
if (graph.isSourceOfType(target, "istar.DependencyLink")) return fail("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.");
|
|
660
|
+
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)");
|
|
661
|
+
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)");
|
|
662
|
+
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)");
|
|
663
|
+
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)");
|
|
664
|
+
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)");
|
|
665
|
+
if (graph.isThereLinkBetween(source, target, "istar.ContributionLink")) return fail$1("duplicate-link", "there can only be one Contribution link between the same two elements");
|
|
666
|
+
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)");
|
|
667
|
+
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.");
|
|
648
668
|
return OK;
|
|
649
669
|
};
|
|
650
670
|
const qualificationLink = (graph, source, target) => {
|
|
651
|
-
if (!is(graph, source, "Quality")) return fail("invalid-source", "the source of a Qualification link must be a Quality (iStar 2.0 Guide, Table 1)");
|
|
652
|
-
if (!is(graph, target, "Goal", "Task", "Resource")) return fail("invalid-target", "the target of a Qualification link must be a Goal, a Task or a Resource (iStar 2.0 Guide, Table 1)");
|
|
653
|
-
if (source.id === target.id) return fail("self-link", "you cannot make a Qualification link from an element onto itself");
|
|
654
|
-
if (isDependum(source) || isDependum(target)) return fail("dependum-endpoint", "you cannot make a Qualification link with a dependum (iStar 2.0 Guide, Page 14)");
|
|
655
|
-
if (source.parent !== target.parent) return fail("different-actors", "the source and target of a Qualification link must pertain to the same actor (iStar 2.0 Guide, Page 14)");
|
|
656
|
-
if (graph.isThereLinkBetween(source, target, "istar.QualificationLink")) return fail("duplicate-link", "there can only be one Qualification link between the same two elements");
|
|
657
|
-
if (graph.isThereLinkBetween(source, target, "istar.ContributionLink")) return fail("contribution-and-qualification", "you cannot have Qualification and Contribution links between the same two elements (iStar 2.0 Guide, Page 15)");
|
|
671
|
+
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)");
|
|
672
|
+
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)");
|
|
673
|
+
if (source.id === target.id) return fail$1("self-link", "you cannot make a Qualification link from an element onto itself");
|
|
674
|
+
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)");
|
|
675
|
+
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)");
|
|
676
|
+
if (graph.isThereLinkBetween(source, target, "istar.QualificationLink")) return fail$1("duplicate-link", "there can only be one Qualification link between the same two elements");
|
|
677
|
+
if (graph.isThereLinkBetween(source, target, "istar.ContributionLink")) return fail$1("contribution-and-qualification", "you cannot have Qualification and Contribution links between the same two elements (iStar 2.0 Guide, Page 15)");
|
|
658
678
|
return OK;
|
|
659
679
|
};
|
|
660
680
|
/** The words used for a list of kind selectors in messages: "a Goal or a Task". */
|
|
@@ -682,15 +702,15 @@ function matchesSelector(graph, element, selector) {
|
|
|
682
702
|
function declarativeCheck(graph, definition, rules, rulesKind, source, target) {
|
|
683
703
|
const name = definition.label;
|
|
684
704
|
const article = /^[aeiou]/i.test(name) ? "an" : "a";
|
|
685
|
-
if (!rules.sources.some((selector) => matchesSelector(graph, source, selector))) return fail("invalid-source", `the source of ${article} ${name} link must be ${describe(graph, rules.sources)}`);
|
|
686
|
-
if (!rules.targets.some((selector) => matchesSelector(graph, target, selector))) return fail("invalid-target", `the target of ${article} ${name} link must be ${describe(graph, rules.targets)}`);
|
|
687
|
-
if (!rules.allowSelf && source.id === target.id) return fail("self-link", `you cannot make ${article} ${name} link from an element onto itself`);
|
|
705
|
+
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)}`);
|
|
706
|
+
if (!rules.targets.some((selector) => matchesSelector(graph, target, selector))) return fail$1("invalid-target", `the target of ${article} ${name} link must be ${describe(graph, rules.targets)}`);
|
|
707
|
+
if (!rules.allowSelf && source.id === target.id) return fail$1("self-link", `you cannot make ${article} ${name} link from an element onto itself`);
|
|
688
708
|
if (definition.category === "node") {
|
|
689
|
-
if (!rules.allowDependum && (isDependum(source) || isDependum(target))) return fail("dependum-endpoint", `you cannot make ${article} ${name} link with a dependum`);
|
|
690
|
-
if (rules.sameActor !== false && source.parent !== target.parent) return fail("different-actors", `the source and target of ${article} ${name} link must pertain to the same actor (iStar 2.0 Guide, Page 14)`);
|
|
709
|
+
if (!rules.allowDependum && (isDependum(source) || isDependum(target))) return fail$1("dependum-endpoint", `you cannot make ${article} ${name} link with a dependum`);
|
|
710
|
+
if (rules.sameActor !== false && source.parent !== target.parent) return fail$1("different-actors", `the source and target of ${article} ${name} link must pertain to the same actor (iStar 2.0 Guide, Page 14)`);
|
|
691
711
|
}
|
|
692
712
|
const unique = rules.unique ?? "kind";
|
|
693
|
-
if (unique === "any" && graph.isThereLinkBetween(source, target) || unique === "kind" && graph.isThereLinkOfKindBetween(source, target, definition.kind) || unique === "kind" && rulesKind !== definition.kind && graph.isThereLinkOfKindBetween(source, target, rulesKind)) return fail("duplicate-link", `there can only be one ${name} link between the same two elements`);
|
|
713
|
+
if (unique === "any" && graph.isThereLinkBetween(source, target) || unique === "kind" && graph.isThereLinkOfKindBetween(source, target, definition.kind) || unique === "kind" && rulesKind !== definition.kind && graph.isThereLinkOfKindBetween(source, target, rulesKind)) return fail$1("duplicate-link", `there can only be one ${name} link between the same two elements`);
|
|
694
714
|
return OK;
|
|
695
715
|
}
|
|
696
716
|
const CHECKS = {
|
|
@@ -749,6 +769,126 @@ function validateModel(model, options) {
|
|
|
749
769
|
return issues;
|
|
750
770
|
}
|
|
751
771
|
//#endregion
|
|
772
|
+
//#region src/file-metamodel.ts
|
|
773
|
+
/** The top-level key of the block in piStar JSON. */
|
|
774
|
+
const FILE_METAMODEL_KEY = "metamodel";
|
|
775
|
+
function isObject(value) {
|
|
776
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
777
|
+
}
|
|
778
|
+
function fail(path, message) {
|
|
779
|
+
throw new MetamodelError(`${path} ${message}`);
|
|
780
|
+
}
|
|
781
|
+
function optionalString(entry, key, path) {
|
|
782
|
+
if (entry[key] !== void 0 && typeof entry[key] !== "string") fail(`${path}.${key}`, "must be a string");
|
|
783
|
+
}
|
|
784
|
+
function optionalBoolean(entry, key, path) {
|
|
785
|
+
if (entry[key] !== void 0 && typeof entry[key] !== "boolean") fail(`${path}.${key}`, "must be true or false");
|
|
786
|
+
}
|
|
787
|
+
function checkSize(value, path) {
|
|
788
|
+
if (value === void 0) return;
|
|
789
|
+
if (!isObject(value)) fail(path, "must be an object { width, height }");
|
|
790
|
+
for (const key of ["width", "height"]) {
|
|
791
|
+
const n = value[key];
|
|
792
|
+
if (typeof n !== "number" || !Number.isFinite(n) || n <= 0) fail(`${path}.${key}`, "must be a positive number");
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
function checkShape(value, path) {
|
|
796
|
+
if (value === void 0) return;
|
|
797
|
+
if (!isObject(value)) fail(path, "must be an object { path, viewBox? }");
|
|
798
|
+
if (typeof value.path !== "string" || value.path.trim() === "") fail(`${path}.path`, "must be SVG path data");
|
|
799
|
+
optionalString(value, "viewBox", path);
|
|
800
|
+
}
|
|
801
|
+
function checkTextBox(value, path) {
|
|
802
|
+
if (value === void 0) return;
|
|
803
|
+
if (!isObject(value)) fail(path, "must be an object { top, right, bottom, left }");
|
|
804
|
+
for (const key of [
|
|
805
|
+
"top",
|
|
806
|
+
"right",
|
|
807
|
+
"bottom",
|
|
808
|
+
"left"
|
|
809
|
+
]) {
|
|
810
|
+
const n = value[key];
|
|
811
|
+
if (typeof n !== "number" || n < 0 || n >= 1) fail(`${path}.${key}`, "must be a fraction from 0 (inclusive) to 1 (exclusive)");
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
function checkLine(value, path) {
|
|
815
|
+
if (value === void 0) return;
|
|
816
|
+
if (!isObject(value)) fail(path, "must be an object { dash?, marker?, markerFilled? }");
|
|
817
|
+
optionalString(value, "dash", path);
|
|
818
|
+
if (value.marker !== void 0 && value.marker !== false && typeof value.marker !== "string") fail(`${path}.marker`, "must be SVG path data or false");
|
|
819
|
+
optionalBoolean(value, "markerFilled", path);
|
|
820
|
+
}
|
|
821
|
+
function checkStringList(value, path) {
|
|
822
|
+
if (!Array.isArray(value) || value.length === 0 || value.some((v) => typeof v !== "string")) fail(path, "must be a non-empty list of kind names or categories");
|
|
823
|
+
}
|
|
824
|
+
function checkRules(value, path) {
|
|
825
|
+
if (!isObject(value)) fail(path, "must be an object { sources, targets, … }");
|
|
826
|
+
checkStringList(value.sources, `${path}.sources`);
|
|
827
|
+
checkStringList(value.targets, `${path}.targets`);
|
|
828
|
+
for (const key of [
|
|
829
|
+
"sameActor",
|
|
830
|
+
"allowDependum",
|
|
831
|
+
"allowSelf"
|
|
832
|
+
]) optionalBoolean(value, key, path);
|
|
833
|
+
if (value.unique !== void 0 && ![
|
|
834
|
+
"kind",
|
|
835
|
+
"any",
|
|
836
|
+
false
|
|
837
|
+
].includes(value.unique)) fail(`${path}.unique`, "must be \"kind\", \"any\" or false");
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Checks that `value` is a well-formed `"metamodel"` block and returns it typed. Throws
|
|
841
|
+
* `MetamodelError` naming the offending path (`metamodel.elements[1].kind must be a string`).
|
|
842
|
+
* Whether its kinds fit a given metamodel (namespacing, collisions, `behavesLike` targets) is
|
|
843
|
+
* checked when it is applied, by `extendMetamodel`.
|
|
844
|
+
*/
|
|
845
|
+
function validateFileMetamodel(value, path = FILE_METAMODEL_KEY) {
|
|
846
|
+
if (!isObject(value)) fail(path, "must be an object { name, elements?, links? }");
|
|
847
|
+
if (typeof value.name !== "string" || value.name === "") fail(`${path}.name`, "must be a non-empty string");
|
|
848
|
+
for (const [listKey, kind] of [["elements", "element"], ["links", "link"]]) {
|
|
849
|
+
const list = value[listKey];
|
|
850
|
+
if (list === void 0) continue;
|
|
851
|
+
if (!Array.isArray(list)) fail(`${path}.${listKey}`, "must be a list");
|
|
852
|
+
list.forEach((entry, i) => {
|
|
853
|
+
const at = `${path}.${listKey}[${i}]`;
|
|
854
|
+
if (!isObject(entry)) fail(at, `must be an object describing a ${kind} kind`);
|
|
855
|
+
if (typeof entry.kind !== "string" || entry.kind === "") fail(`${at}.kind`, "must be a string");
|
|
856
|
+
optionalString(entry, "label", at);
|
|
857
|
+
optionalString(entry, "behavesLike", at);
|
|
858
|
+
optionalString(entry, "pistarType", at);
|
|
859
|
+
if (kind === "element") {
|
|
860
|
+
if (entry.category !== void 0 && entry.category !== "node" && entry.category !== "actor") fail(`${at}.category`, "must be \"node\" or \"actor\"");
|
|
861
|
+
checkSize(entry.size, `${at}.size`);
|
|
862
|
+
if (entry.info !== void 0) {
|
|
863
|
+
if (!isObject(entry.info)) fail(`${at}.info`, "must be an object");
|
|
864
|
+
for (const key of [
|
|
865
|
+
"canBeInnerElement",
|
|
866
|
+
"canBeDependum",
|
|
867
|
+
"canBeOnPaper"
|
|
868
|
+
]) optionalBoolean(entry.info, key, `${at}.info`);
|
|
869
|
+
}
|
|
870
|
+
checkShape(entry.shape, `${at}.shape`);
|
|
871
|
+
checkTextBox(entry.textBox, `${at}.textBox`);
|
|
872
|
+
} else {
|
|
873
|
+
if (entry.category !== void 0 && ![
|
|
874
|
+
"node",
|
|
875
|
+
"actor",
|
|
876
|
+
"dependency"
|
|
877
|
+
].includes(entry.category)) fail(`${at}.category`, "must be \"node\", \"actor\" or \"dependency\"");
|
|
878
|
+
if (entry.rules !== void 0) checkRules(entry.rules, `${at}.rules`);
|
|
879
|
+
if ("check" in entry) fail(`${at}.check`, "cannot be stored in a file (it is code)");
|
|
880
|
+
if (entry.info !== void 0 && !isObject(entry.info)) fail(`${at}.info`, "must be an object");
|
|
881
|
+
checkLine(entry.line, `${at}.line`);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
return value;
|
|
886
|
+
}
|
|
887
|
+
/** The block as an extension `extendMetamodel` applies (it ignores the extra keys). */
|
|
888
|
+
function fileMetamodelExtension(block) {
|
|
889
|
+
return block;
|
|
890
|
+
}
|
|
891
|
+
//#endregion
|
|
752
892
|
//#region src/serialization.ts
|
|
753
893
|
var PistarParseError = class extends Error {
|
|
754
894
|
name = "PistarParseError";
|
|
@@ -826,7 +966,7 @@ function asArray(value, where) {
|
|
|
826
966
|
return value;
|
|
827
967
|
}
|
|
828
968
|
function parsePistar(input, options = {}) {
|
|
829
|
-
|
|
969
|
+
let metamodel = options.metamodel ?? ISTAR_2_0;
|
|
830
970
|
const categoryOf = (kind) => metamodel.elements.get(kind)?.category;
|
|
831
971
|
let json;
|
|
832
972
|
if (typeof input === "string") try {
|
|
@@ -836,6 +976,12 @@ function parsePistar(input, options = {}) {
|
|
|
836
976
|
}
|
|
837
977
|
else json = structuredClone(input);
|
|
838
978
|
if (!isRecord(json)) throw new PistarParseError("a piStar model must be a JSON object");
|
|
979
|
+
let host;
|
|
980
|
+
if (options.fileMetamodel && json["metamodel"] !== void 0) {
|
|
981
|
+
const block = validateFileMetamodel(json[FILE_METAMODEL_KEY]);
|
|
982
|
+
host = metamodel;
|
|
983
|
+
metamodel = extendMetamodel(metamodel, fileMetamodelExtension(block));
|
|
984
|
+
}
|
|
839
985
|
const display = isRecord(json.display) ? json.display : {};
|
|
840
986
|
const usedDisplay = /* @__PURE__ */ new Set();
|
|
841
987
|
const elements = /* @__PURE__ */ new Map();
|
|
@@ -936,6 +1082,7 @@ function parsePistar(input, options = {}) {
|
|
|
936
1082
|
keys: Object.keys(json),
|
|
937
1083
|
display: Object.keys(display)
|
|
938
1084
|
});
|
|
1085
|
+
setHostMetamodel(model, host);
|
|
939
1086
|
return withMetamodel(model, metamodel);
|
|
940
1087
|
}
|
|
941
1088
|
function optional(key, value) {
|
|
@@ -1062,6 +1209,44 @@ function toPistarObject(input, options = {}) {
|
|
|
1062
1209
|
function toPistar(model, options) {
|
|
1063
1210
|
return JSON.stringify(toPistarObject(model, options), null, 2);
|
|
1064
1211
|
}
|
|
1212
|
+
/**
|
|
1213
|
+
* The constructs the model's file declares in its `"metamodel"` block, when they were applied
|
|
1214
|
+
* (read with `fileMetamodel: true`, or set with `withFileMetamodel`); `undefined` otherwise.
|
|
1215
|
+
* Kinds the host supplied are never part of it.
|
|
1216
|
+
*/
|
|
1217
|
+
function fileMetamodelOf(model) {
|
|
1218
|
+
if (!hostMetamodelOf(model)) return void 0;
|
|
1219
|
+
return model.extra?.[FILE_METAMODEL_KEY];
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* A copy of `model` whose file declares `block` (`null` removes the block): its metamodel is the
|
|
1223
|
+
* host's extended by `block`, and `toPistar` writes the block. This is how a tool's "Add new"
|
|
1224
|
+
* construct joins a model. Throws `MetamodelError` if the block is malformed, collides with the
|
|
1225
|
+
* host's kinds, or leaves out a kind the model still uses.
|
|
1226
|
+
*/
|
|
1227
|
+
function withFileMetamodel(model, block) {
|
|
1228
|
+
const any = model;
|
|
1229
|
+
const host = hostMetamodelOf(any) ?? metamodelOf(any);
|
|
1230
|
+
let next = host;
|
|
1231
|
+
if (block) {
|
|
1232
|
+
validateFileMetamodel(block);
|
|
1233
|
+
next = extendMetamodel(host, fileMetamodelExtension(block));
|
|
1234
|
+
}
|
|
1235
|
+
for (const element of any.elements.values()) if (!next.elements.has(element.kind)) throw new MetamodelError(`element "${element.id}" uses kind "${element.kind}", which the new metamodel lacks`);
|
|
1236
|
+
for (const link of any.links.values()) if (!next.links.has(link.kind)) throw new MetamodelError(`link "${link.id}" uses kind "${link.kind}", which the new metamodel lacks`);
|
|
1237
|
+
const { [FILE_METAMODEL_KEY]: _previous, ...others } = any.extra ?? {};
|
|
1238
|
+
const extra = block ? {
|
|
1239
|
+
...any.extra,
|
|
1240
|
+
[FILE_METAMODEL_KEY]: structuredClone(block)
|
|
1241
|
+
} : others;
|
|
1242
|
+
const { extra: _extra, ...rest } = any;
|
|
1243
|
+
const result = inheritSourceLayout(any, {
|
|
1244
|
+
...rest,
|
|
1245
|
+
...Object.keys(extra).length > 0 ? { extra } : {}
|
|
1246
|
+
});
|
|
1247
|
+
setHostMetamodel(result, block ? host : void 0);
|
|
1248
|
+
return withMetamodel(result, next);
|
|
1249
|
+
}
|
|
1065
1250
|
//#endregion
|
|
1066
1251
|
//#region src/operations.ts
|
|
1067
1252
|
var ModelOperationError = class extends Error {
|
|
@@ -1841,6 +2026,6 @@ function validateModelProperties(model, schemas) {
|
|
|
1841
2026
|
return issues;
|
|
1842
2027
|
}
|
|
1843
2028
|
//#endregion
|
|
1844
|
-
export { ACTOR_KINDS, ACTOR_LINK_KINDS, CONTRIBUTION_LABELS, DEFAULT_DIAGRAM, DEFAULT_ELEMENT_SIZE, DEPENDENCY_LINK_KINDS, ELEMENT_KINDS, ISTAR_2_0, ISTAR_PREFIX, ISTAR_VERSION, LINK_KINDS, LINK_KIND_INFO, METAMODEL_VERSION, MetamodelError, ModelOperationError, NODE_KINDS, NODE_KIND_INFO, NODE_LINK_KINDS, PISTAR_TOOL, PistarParseError, PistarWriteError, addDependency, addElement, boolean, canLink, childrenOf, connect, createEmptyModel, createModelStore, defaultIdGenerator, defineMetamodelExtension, defineProperties, dependencyEnds, dependencyLinksOf, disconnect, effectiveElementKind, effectiveLinkKind, elementBehavesLike, elementKindDefinition, enumOf, extendMetamodel, getSourceLayout, inheritMetamodel, inheritSourceLayout, isActor, isActorIn, isActorKind, isActorKindIn, isActorLinkKind, isContributionLabel, isDependencyLinkKind, isElementKind, isLinkKind, isNode, isNodeIn, isNodeKind, isNodeKindIn, isNodeLinkKind, linkKindDefinition, linksOf, metamodelOf, moveElement, nestElement, number, parsePistar, prop, removeElement, removeElements, shortKindName, string, toPistar, toPistarObject, updateDiagram, updateElement, updateLink, validateModel, validateModelProperties, withMetamodel };
|
|
2029
|
+
export { ACTOR_KINDS, ACTOR_LINK_KINDS, CONTRIBUTION_LABELS, DEFAULT_DIAGRAM, DEFAULT_ELEMENT_SIZE, DEPENDENCY_LINK_KINDS, ELEMENT_KINDS, FILE_METAMODEL_KEY, ISTAR_2_0, ISTAR_PREFIX, ISTAR_VERSION, LINK_KINDS, LINK_KIND_INFO, METAMODEL_VERSION, MetamodelError, ModelOperationError, NODE_KINDS, NODE_KIND_INFO, NODE_LINK_KINDS, PISTAR_TOOL, PistarParseError, PistarWriteError, addDependency, addElement, boolean, canLink, childrenOf, connect, createEmptyModel, createModelStore, defaultIdGenerator, defineMetamodelExtension, defineProperties, dependencyEnds, dependencyLinksOf, disconnect, effectiveElementKind, effectiveLinkKind, elementBehavesLike, elementKindDefinition, enumOf, extendMetamodel, fileMetamodelExtension, fileMetamodelOf, getSourceLayout, hostMetamodelOf, inheritMetamodel, inheritSourceLayout, isActor, isActorIn, isActorKind, isActorKindIn, isActorLinkKind, isContributionLabel, isDependencyLinkKind, isElementKind, isLinkKind, isNode, isNodeIn, isNodeKind, isNodeKindIn, isNodeLinkKind, linkKindDefinition, linksOf, metamodelOf, moveElement, nestElement, number, parsePistar, prop, removeElement, removeElements, setHostMetamodel, shortKindName, string, toPistar, toPistarObject, updateDiagram, updateElement, updateLink, validateFileMetamodel, validateModel, validateModelProperties, withFileMetamodel, withMetamodel };
|
|
1845
2030
|
|
|
1846
2031
|
//# sourceMappingURL=index.js.map
|