@istar-ts/core 0.11.0 → 0.13.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/dist/index.d.cts CHANGED
@@ -69,7 +69,7 @@ export declare function isContributionLabel(value: string): value is Contributio
69
69
  export declare function shortKindName(kind: ElementKind | LinkKind): string;
70
70
  //#endregion
71
71
  //#region src/constraints.d.ts
72
- export type ConstraintCode = "unknown-element" | "unknown-link-kind" | "invalid-source" | "invalid-target" | "actor-types-differ" | "self-link" | "duplicate-link" | "dependum-endpoint" | "same-actor" | "different-actors" | "refined-depender" | "contributed-depender" | "depender-target" | "mixed-refinement" | "contribution-and-qualification";
72
+ export type ConstraintCode = "unknown-element" | "unknown-link-kind" | "invalid-source" | "invalid-target" | "actor-types-differ" | "self-link" | "duplicate-link" | "dependum-endpoint" | "same-actor" | "different-actors" | "refined-depender" | "contributed-depender" | "depender-target" | "mixed-refinement" | "contribution-and-qualification" | "hub-target" | "incomplete-hub";
73
73
  export type LinkCheck = {
74
74
  ok: true;
75
75
  } | {
@@ -77,6 +77,11 @@ export type LinkCheck = {
77
77
  reason: string;
78
78
  code: ConstraintCode;
79
79
  };
80
+ /**
81
+ * True for a link, as opposed to an element: the target of a link kind whose rules target links
82
+ * (see `LinkRules`) may be either.
83
+ */
84
+ export declare function isLinkRef<EK extends string, LK extends string>(value: IstarElement<EK> | IstarLink<LK>): value is IstarLink<LK>;
80
85
  export interface CanLinkOptions<EK extends string = ElementKind, LK extends string = LinkKind> {
81
86
  /** Default: the model's metamodel, or iStar 2.0. */
82
87
  readonly metamodel?: Metamodel<EK, LK>;
@@ -89,8 +94,13 @@ export interface CanLinkOptions<EK extends string = ElementKind, LK extends stri
89
94
  *
90
95
  * Extended kinds (see `extendMetamodel`) are checked with the rules of the kind they behave
91
96
  * like, or with their declarative rules, then with their extra predicate.
97
+ *
98
+ * `target` may be a link (or a link id) for kinds whose rules target links (`LinkRules`).
99
+ *
100
+ * For an n-ary kind (`arity: 'n'`), a `target` that is one of its hubs is checked as a new leg
101
+ * of that hub; any other target, as a new link from `source` to `target`.
92
102
  */
93
- export declare function canLink<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, source: string | IstarElement<EK>, target: string | IstarElement<EK>, kind: LK, options?: CanLinkOptions<EK, LK>): LinkCheck;
103
+ export declare function canLink<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, source: string | IstarElement<EK>, target: string | IstarElement<EK> | IstarLink<LK>, kind: LK, options?: CanLinkOptions<EK, LK>): LinkCheck;
94
104
  /**
95
105
  * What an extension's `check` predicate receives: the model, the endpoints, and the graph
96
106
  * queries the iStar 2.0 rules use.
@@ -101,9 +111,17 @@ export interface LinkRuleContext {
101
111
  /** The link kind being added. */
102
112
  readonly kind: string;
103
113
  readonly source: IstarElement<string>;
104
- readonly target: IstarElement<string>;
105
- /** A link (optionally of `kind`, or a kind behaving like it) joins `a` and `b`, either way. */
106
- isThereLinkBetween(a: IstarElement<string>, b: IstarElement<string>, kind?: string): boolean;
114
+ /** An element, or a link for kinds whose rules target links (narrow with `isLinkRef`). */
115
+ readonly target: IstarElement<string> | IstarLink<string>;
116
+ /**
117
+ * A link (optionally of `kind`, or a kind behaving like it) joins `a` and `b`, either way. Both
118
+ * may be elements or links.
119
+ */
120
+ isThereLinkBetween(a: {
121
+ readonly id: string;
122
+ }, b: {
123
+ readonly id: string;
124
+ }, kind?: string): boolean;
107
125
  /** `element` is the source of a link of `kind` (or a kind behaving like it). */
108
126
  isSourceOfType(element: IstarElement<string>, kind: string): boolean;
109
127
  /** `element` is the target of a link of `kind` (or a kind behaving like it). */
@@ -120,7 +138,9 @@ export interface ModelIssue<LK extends string = LinkKind> {
120
138
  /**
121
139
  * Re-validates every link of a model, the way piStar does when loading a file: dependencies
122
140
  * first (each checked before its own links are added), then the remaining links in file
123
- * order, each against the graph built so far. Invalid links are reported, not removed.
141
+ * order, each against the graph built so far, and last the links that target links. Invalid
142
+ * links are reported, not removed. A hub without an end or legs is reported with code
143
+ * `incomplete-hub`, the hub's id as `linkId` and the n-ary kind as `kind`.
124
144
  */
125
145
  export declare function validateModel<EK extends string, LK extends string>(model: IstarModel<EK, LK>, options: CanLinkOptions<EK, LK>): ModelIssue<LK>[];
126
146
  export declare function validateModel<EK extends string, LK extends string>(model: IstarModel<EK, LK>): ModelIssue<LK>[];
@@ -135,6 +155,10 @@ export type ElementCategory = "actor" | "node";
135
155
  export interface KindShape {
136
156
  readonly path: string;
137
157
  readonly viewBox?: string;
158
+ /** SVG `stroke-dasharray` of the outline, e.g. `'8,4'` for a dashed shape. */
159
+ readonly dash?: string;
160
+ /** Outline width, when it differs from the renderer's default. */
161
+ readonly strokeWidth?: number;
138
162
  }
139
163
  export interface KindTextBox {
140
164
  readonly top: number;
@@ -167,15 +191,29 @@ export interface ElementKindDefinition<K extends string = string> {
167
191
  /** Presentation hints (drawn by `@istar-ts/react`). */
168
192
  readonly shape?: KindShape;
169
193
  readonly textBox?: KindTextBox;
194
+ /** `hub` for the kind generated for an n-ary link kind's hubs (see `LinkKindDeclaration.arity`). */
195
+ readonly role?: "hub";
196
+ /** For a hub kind: the n-ary link kind it was generated for. */
197
+ readonly of?: string;
198
+ }
199
+ /** The kinds generated for an n-ary link kind: its hub element kind, leg and end link kinds. */
200
+ export interface HubLinkParts {
201
+ readonly hub: string;
202
+ readonly leg: string;
203
+ readonly end: string;
170
204
  }
171
205
  /**
172
206
  * Declarative link rules, as piStar-ext's "Create a new Construct" dialog collects them:
173
207
  * which kinds may be the source and target. Entries are kind names (`istar.Goal`,
174
208
  * `rationalAgents.Plan`) or categories (`node`, `actor`, `*`). A kind name also matches kinds
175
209
  * that behave like it, so `istar.Task` accepts a Planning that behaves like a Task.
210
+ *
211
+ * A link may also target another link (NFR's Argument link points at a Contribution): `targets`
212
+ * then holds `link` (any link) or link kind names. Sources are always elements.
176
213
  */
177
214
  export interface LinkRules {
178
215
  readonly sources: readonly string[];
216
+ /** Element selectors as for `sources`, plus `link` and link kind names. */
179
217
  readonly targets: readonly string[];
180
218
  /**
181
219
  * Source and target must belong to the same actor (iStar 2.0 Guide, page 14). Default true
@@ -208,13 +246,44 @@ export interface LinkKindDefinition<K extends string = string> {
208
246
  readonly extension?: string;
209
247
  /** Presentation hint (drawn by `@istar-ts/react`). */
210
248
  readonly line?: KindLine;
249
+ /** `'n'` for an n-ary link kind (several sources joined at a hub); absent means 1. */
250
+ readonly arity?: 1 | "n";
251
+ /** For an n-ary kind: the kinds generated for it. */
252
+ readonly parts?: HubLinkParts;
253
+ /** For a generated kind: `leg` (a source → the hub) or `end` (the hub → the target). */
254
+ readonly role?: "leg" | "end";
255
+ /** For a generated kind: the n-ary link kind it was generated for. */
256
+ readonly of?: string;
257
+ }
258
+ /**
259
+ * How a root metamodel's files look, for models that don't come from a file (`createEmptyModel`)
260
+ * and for writing them (`toPistar`). Files that were read keep their own layout.
261
+ */
262
+ export interface MetamodelFileHints {
263
+ /** The `tool` a new model records. Default: piStar's `pistar.2.1.0`. */
264
+ readonly tool?: string;
265
+ /** The `istar` version a new model records; only iStar 2.0 sets it (`'2.0'`). */
266
+ readonly istar?: string;
267
+ /** Extra top-level keys of a new model (its `extra`), e.g. `{ metamodelVersion: '1.0' }`. */
268
+ readonly extra?: Readonly<Record<string, unknown>>;
269
+ /** Top-level key order for models without a source layout. Default: piStar's. */
270
+ readonly keys?: readonly string[];
211
271
  }
212
272
  /** A metamodel: the element and link kinds a model may use, and their rules. */
213
273
  export interface Metamodel<EK extends string = ElementKind, LK extends string = LinkKind> {
214
- /** `istar-2.0`, followed by the names of the extensions applied, e.g. `istar-2.0+rationalAgents`. */
274
+ /**
275
+ * The root's name (`istar-2.0`), followed by the names of the extensions applied, e.g.
276
+ * `istar-2.0+rationalAgents`.
277
+ */
215
278
  readonly name: string;
279
+ /** The root metamodel's name, e.g. `istar-2.0`; extensions keep it. */
280
+ readonly framework: string;
281
+ /** The root's namespace (`istar`). Extensions may not declare kinds in it. */
282
+ readonly prefix: string;
283
+ /** How the root's files look (see `MetamodelFileHints`); extensions keep it. */
284
+ readonly file: MetamodelFileHints;
216
285
  readonly extensions: readonly string[];
217
- /** In declaration order: iStar 2.0 kinds first, then each extension's. */
286
+ /** In declaration order: the root's kinds first, then each extension's. */
218
287
  readonly elements: ReadonlyMap<EK, ElementKindDefinition<EK>>;
219
288
  readonly links: ReadonlyMap<LK, LinkKindDefinition<LK>>;
220
289
  /** `type` in piStar files → kind. */
@@ -226,8 +295,6 @@ export type ElementKindOf<M> = M extends Metamodel<infer EK, string> ? EK : neve
226
295
  export type LinkKindOf<M> = M extends Metamodel<string, infer LK> ? LK : never;
227
296
  /** A metamodel of any dialect, for code that handles every kind generically. */
228
297
  export type AnyMetamodel = Metamodel<string, string>;
229
- /** iStar 2.0, as the piStar tool implements it. The default metamodel everywhere. */
230
- export declare const ISTAR_2_0: Metamodel;
231
298
  export interface ElementKindDeclaration<K extends string = string> {
232
299
  /** Namespaced kind name, e.g. `rationalAgents.Planning`. */
233
300
  readonly kind: K;
@@ -237,7 +304,10 @@ export interface ElementKindDeclaration<K extends string = string> {
237
304
  readonly category?: ElementCategory;
238
305
  /** An existing element kind whose link rules this kind follows, e.g. `istar.Task`. */
239
306
  readonly behavesLike?: string;
240
- /** Containment rules for nodes. Default: those of `behavesLike`, else piStar-ext's (inner, dependum). */
307
+ /**
308
+ * Containment rules for nodes. Default: those of `behavesLike`, else piStar-ext's (inner,
309
+ * dependum) in an extension and DSM3's (inner, dependum, on paper) in a root.
310
+ */
241
311
  readonly info?: Partial<NodeKindInfo>;
242
312
  /** Default: that of `behavesLike`, else piStar-ext's 90×55 for nodes and 200×120 for actors. */
243
313
  readonly size?: Size;
@@ -257,7 +327,10 @@ export interface LinkKindDeclaration<K extends string = string> {
257
327
  readonly category?: LinkCategory;
258
328
  /** An existing link kind whose rules this kind takes on, e.g. `istar.OrRefinementLink`. */
259
329
  readonly behavesLike?: string;
260
- /** Declarative rules; give these or `behavesLike`, not both. */
330
+ /**
331
+ * Declarative rules; give these or `behavesLike`, not both. A root's link kind may give
332
+ * neither when the library has built-in rules for it (iStar 2.0's links).
333
+ */
261
334
  readonly rules?: LinkRules;
262
335
  /** An extra predicate, checked after the other rules pass. */
263
336
  readonly check?: (context: LinkRuleContext) => LinkCheck;
@@ -266,6 +339,28 @@ export interface LinkKindDeclaration<K extends string = string> {
266
339
  readonly pistarType?: string;
267
340
  /** Presentation hint, kept as data for renderers (`@istar-ts/react` draws it). */
268
341
  readonly line?: KindLine;
342
+ /**
343
+ * `'n'` for a link with several sources, like KAOS's AND-refinement: in a model it is a hub
344
+ * element joined to each source by a leg link and to the target by an end link, as DSM3-goals
345
+ * draws and saves it. Declaring it generates three kinds, named after this one (`parts`):
346
+ * `<kind>Hub`, `<kind>Start` and `<kind>End`, written as `<pistarType>Hub` etc. Needs `rules`
347
+ * and the `node` category.
348
+ */
349
+ readonly arity?: "n";
350
+ /** Names of the generated kinds, when the defaults don't suit. */
351
+ readonly parts?: Partial<HubLinkParts>;
352
+ /** The hub's size. Default 20×20, as in DSM3-goals. */
353
+ readonly hubSize?: Size;
354
+ }
355
+ /** A whole modelling language, turned into a `Metamodel` by `defineMetamodel`. */
356
+ export interface RootMetamodelDeclaration<EK extends string = string, LK extends string = string> {
357
+ /** Identifies the language; the metamodel's `name` and `framework`, e.g. `istar-2.0`. */
358
+ readonly name: string;
359
+ /** The language's namespace, e.g. `istar`. Extensions may not declare kinds in it. */
360
+ readonly prefix: string;
361
+ readonly elements: readonly ElementKindDeclaration<EK>[];
362
+ readonly links: readonly LinkKindDeclaration<LK>[];
363
+ readonly file?: MetamodelFileHints;
269
364
  }
270
365
  /** A named set of new kinds, applied to a metamodel with `extendMetamodel`. */
271
366
  export interface MetamodelExtension<NEK extends string = string, NLK extends string = string> {
@@ -282,11 +377,27 @@ export declare class MetamodelError extends Error {
282
377
  * type the result). Identity at runtime.
283
378
  */
284
379
  export declare function defineMetamodelExtension<const NEK extends string = never, const NLK extends string = never>(extension: MetamodelExtension<NEK, NLK>): MetamodelExtension<NEK, NLK>;
380
+ /** The `targets` selector for any link (see `LinkRules`). */
381
+ export declare const LINK_SELECTOR = "link";
285
382
  /**
286
- * Returns a new metamodel with the extension's kinds added. `base` is not modified.
383
+ * Builds a root metamodel: a modelling language of its own, such as iStar 2.0, KAOS or NFR.
384
+ * Kinds are namespaced (any namespace; usually `prefix`), declared in order: `behavesLike` and
385
+ * link rules refer to kinds declared before them. Extensions of the result may not use `prefix`.
287
386
  *
288
- * Throws `MetamodelError` when a kind name isn't namespaced, uses the reserved `istar.`
289
- * prefix, collides with an existing kind or piStar type, or refers to an unknown kind.
387
+ * Throws `MetamodelError` on the same declaration errors as `extendMetamodel`.
388
+ */
389
+ export declare function defineMetamodel<const EK extends string, const LK extends string>(declaration: RootMetamodelDeclaration<EK, LK>): Metamodel<EK, LK>;
390
+ /** iStar 2.0's declaration, built from the constants in `metamodel.ts`. */
391
+ export declare const ISTAR_2_0_DECLARATION: RootMetamodelDeclaration<ElementKind, LinkKind>;
392
+ /** iStar 2.0, as the piStar tool implements it. The default metamodel everywhere. */
393
+ export declare const ISTAR_2_0: Metamodel;
394
+ /**
395
+ * Returns a new metamodel with the extension's kinds added. `base` is not modified. The result
396
+ * keeps `base`'s `framework`, `prefix` and `file`.
397
+ *
398
+ * Throws `MetamodelError` when a kind name isn't namespaced, uses the root's reserved prefix
399
+ * (`istar.` for iStar 2.0), collides with an existing kind or piStar type, or refers to an
400
+ * unknown kind.
290
401
  */
291
402
  export declare function extendMetamodel<EK extends string, LK extends string, const NEK extends string = never, const NLK extends string = never>(base: Metamodel<EK, LK>, extension: MetamodelExtension<NEK, NLK>): Metamodel<EK | NEK, LK | NLK>;
292
403
  export declare function elementKindDefinition(metamodel: AnyMetamodel, kind: string): ElementKindDefinition | undefined;
@@ -302,6 +413,10 @@ export declare function isNodeKindIn(metamodel: AnyMetamodel, kind: string): boo
302
413
  export declare function effectiveElementKind(metamodel: AnyMetamodel, kind: string): string;
303
414
  /** Like `effectiveElementKind`, for link kinds. */
304
415
  export declare function effectiveLinkKind(metamodel: AnyMetamodel, kind: string): string;
416
+ /** True when link `kind` is `ancestor` or (transitively) behaves like it. */
417
+ export declare function linkBehavesLike(metamodel: AnyMetamodel, kind: string, ancestor: string): boolean;
418
+ /** True when a link kind's rules let it target links (`link` or a link kind in `targets`). */
419
+ export declare function targetsLinks(metamodel: AnyMetamodel, kind: string): boolean;
305
420
  /** True when `kind` is `ancestor` or (transitively) behaves like it. */
306
421
  export declare function elementBehavesLike(metamodel: AnyMetamodel, kind: string, ancestor: string): boolean;
307
422
  //#endregion
@@ -401,6 +516,13 @@ export interface IstarModel<EK extends string = ElementKind, LK extends string =
401
516
  /** Tool identifier written by piStar 2.1.0, whose file format this library emulates. */
402
517
  export declare const PISTAR_TOOL = "pistar.2.1.0";
403
518
  export declare const ISTAR_VERSION = "2.0";
519
+ /**
520
+ * DSM3-goals' file conventions, shared by its languages (KAOS, NFR; see `@istar-ts/dsm3-kaos`
521
+ * and `@istar-ts/dsm3-nfr`): the `tool` its editors write, and the order of their top-level keys
522
+ * (`fileManager.js`). Use them as a root's `file.tool` and `file.keys`.
523
+ */
524
+ export declare const DSM3_TOOL = "dsm3-goals";
525
+ export declare const DSM3_FILE_KEYS: readonly string[];
404
526
  export declare const DEFAULT_DIAGRAM: Diagram;
405
527
  /** A model of any dialect, for code that handles every kind generically. */
406
528
  export type AnyIstarModel = IstarModel<string, string>;
@@ -441,13 +563,38 @@ export declare function isActorIn(metamodel: AnyMetamodel): (element: IstarEleme
441
563
  export declare function isNodeIn(metamodel: AnyMetamodel): (element: IstarElement<string> | undefined) => boolean;
442
564
  /** Inner elements of an actor, in model order. */
443
565
  export declare function childrenOf<EK extends string, LK extends string>(model: IstarModel<EK, LK>, actorId: string): IstarElement<EK>[];
444
- /** Links that have the element as source or target. */
566
+ /**
567
+ * Links that have the element as source or target. Given a link id, the links that target that
568
+ * link (see `LinkRules`).
569
+ */
445
570
  export declare function linksOf<EK extends string, LK extends string>(model: IstarModel<EK, LK>, elementId: string): IstarLink<LK>[];
446
571
  /** For a dependum, the two halves of its dependency: depender → dependum → dependee. */
447
572
  export declare function dependencyLinksOf<EK extends string, LK extends string>(model: IstarModel<EK, LK>, dependumId: string): {
448
573
  inbound?: IstarLink<LK>;
449
574
  outbound?: IstarLink<LK>;
450
575
  };
576
+ /** What a link points at: an element, or a link for kinds whose rules target links. */
577
+ export declare function linkTargetOf<EK extends string, LK extends string>(model: IstarModel<EK, LK>, link: IstarLink<LK>): IstarElement<EK> | IstarLink<LK> | undefined;
578
+ /** An n-ary link as it lives in a model: its hub element, its legs (in link order), its end. */
579
+ export interface HubLink<EK extends string = ElementKind, LK extends string = LinkKind> {
580
+ readonly hub: IstarElement<EK>;
581
+ readonly legs: readonly IstarLink<LK>[];
582
+ /** Absent only in an incomplete model (`validateModel` reports it). */
583
+ readonly end?: IstarLink<LK>;
584
+ }
585
+ /** True when `element` is the hub of an n-ary link. */
586
+ export declare function isHubElement(metamodel: AnyMetamodel, element: IstarElement<string> | undefined): boolean;
587
+ /** The n-ary link whose hub is `hubId`; `undefined` when it isn't a hub. */
588
+ export declare function hubLinkOf<EK extends string, LK extends string>(model: IstarModel<EK, LK>, hubId: string): HubLink<EK, LK> | undefined;
589
+ /** The hub a leg or end link belongs to. */
590
+ export declare function hubOfLink<EK extends string, LK extends string>(model: IstarModel<EK, LK>, linkId: string): IstarElement<EK> | undefined;
591
+ /** The centre of an element's box: its display size, else its kind's default size. */
592
+ export declare function elementCenter<EK extends string, LK extends string>(model: IstarModel<EK, LK>, element: IstarElement<EK>): Point;
593
+ /**
594
+ * Where a new hub goes: centred between the source and the target, as DSM3-goals places it.
595
+ * Returns its top-left corner for a hub of `size`.
596
+ */
597
+ export declare function defaultHubPosition<EK extends string, LK extends string>(model: IstarModel<EK, LK>, source: IstarElement<EK>, target: IstarElement<EK>, size: Size): Point;
451
598
  //#endregion
452
599
  //#region src/file-metamodel.d.ts
453
600
  /** The top-level key of the block in piStar JSON. */
@@ -508,6 +655,30 @@ export interface PistarLinkJson {
508
655
  label?: string;
509
656
  [key: string]: unknown;
510
657
  }
658
+ /** The hub of an n-ary link, in a hub entry: an element without `text`. */
659
+ export interface PistarHubJson {
660
+ id: string;
661
+ type: string;
662
+ x: number;
663
+ y: number;
664
+ customProperties?: Record<string, string>;
665
+ [key: string]: unknown;
666
+ }
667
+ /**
668
+ * An n-ary link (see `LinkKindDeclaration.arity`) in `links`, as DSM3-goals writes it: no id of
669
+ * its own, its hub, its legs (`starts`) and its end.
670
+ */
671
+ export interface PistarHubLinkJson {
672
+ hub: PistarHubJson;
673
+ starts: PistarLinkJson[];
674
+ end?: PistarLinkJson;
675
+ [key: string]: unknown;
676
+ }
677
+ /**
678
+ * Key of a hub element's `extra` that holds its entry's unknown keys (those beside `hub`,
679
+ * `starts` and `end`), so they are written back on the entry.
680
+ */
681
+ export declare const HUB_ENTRY_KEY = "__hubEntry";
511
682
  export interface PistarDiagramJson {
512
683
  width?: number;
513
684
  height?: number;
@@ -520,7 +691,7 @@ export interface PistarFile {
520
691
  actors: PistarActorJson[];
521
692
  orphans?: PistarElementJson[];
522
693
  dependencies?: PistarDependencyJson[];
523
- links?: PistarLinkJson[];
694
+ links?: (PistarLinkJson | PistarHubLinkJson)[];
524
695
  display?: Record<string, Record<string, unknown>>;
525
696
  tool?: string;
526
697
  istar?: string;
@@ -579,6 +750,18 @@ export declare function parsePistar<EK extends string, LK extends string>(input:
579
750
  }): IstarModel<EK, LK>;
580
751
  export declare function parsePistar(input: string | PistarFile | Record<string, unknown>, options: ParsePistarOptions): IstarModel;
581
752
  export declare function parsePistar(input: string | PistarFile | Record<string, unknown>): IstarModel;
753
+ /**
754
+ * The namespace of the first `type` in a file (`kaos` for `kaos.Softgoal`), looking at actors and
755
+ * their nodes, orphans, dependencies, then links (hub entries included). `undefined` when no
756
+ * element or link has a namespaced type. Throws `PistarParseError` for a string that isn't JSON.
757
+ */
758
+ export declare function detectPrefix(json: string | Record<string, unknown>): string | undefined;
759
+ /**
760
+ * Which of `roots` a file is written in: the one whose `prefix` is the file's (`detectPrefix`);
761
+ * for a file without types, the only one whose `file.tool` is the file's `tool`; otherwise
762
+ * `undefined`. Pass the result to `parsePistar(json, { metamodel })`.
763
+ */
764
+ export declare function detectFramework(json: string | Record<string, unknown>, roots: readonly AnyMetamodel[]): AnyMetamodel | undefined;
582
765
  export interface ToPistarOptions {
583
766
  /** Default: the model's metamodel, or iStar 2.0. Decides each kind's `type` and category. */
584
767
  metamodel?: AnyMetamodel;
@@ -607,7 +790,7 @@ export declare function fileMetamodelOf<EK extends string, LK extends string>(mo
607
790
  */
608
791
  export declare function withFileMetamodel<EK extends string, LK extends string>(model: IstarModel<EK, LK>, block: FileMetamodel | null): IstarModel<string, string>;
609
792
  declare namespace operations_d_exports {
610
- export { AddDependencyResult, ConnectOptions, ConnectResult, ElementPatch, IdGenerator, LinkPatch, ModelOperationError, NewDependency, NewElement, NewLink, OperationContext, addDependency, addElement, connect, defaultIdGenerator, dependencyEnds, disconnect, moveElement, nestElement, removeElement, removeElements, updateDiagram, updateElement, updateLink };
793
+ export { AddDependencyResult, ConnectOptions, ConnectResult, ElementPatch, IdGenerator, LinkPatch, ModelOperationError, NewDependency, NewElement, NewLink, OperationContext, addDependency, addElement, addLeg, connect, defaultIdGenerator, dependencyEnds, disconnect, moveElement, nestElement, removeElement, removeElements, removeLeg, updateDiagram, updateElement, updateLink };
611
794
  }
612
795
  export declare class ModelOperationError extends Error {
613
796
  override name: string;
@@ -657,7 +840,8 @@ export declare function moveElement<EK extends string = ElementKind, LK extends
657
840
  export declare function nestElement<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, id: string, parent: string | null): IstarModel<EK, LK>;
658
841
  /**
659
842
  * Removes elements and everything that depends on them: inner elements of removed actors,
660
- * links touching removed elements, and the rest of any dependency that loses a part.
843
+ * links touching removed elements (and links targeting removed links), and the rest of any
844
+ * dependency that loses a part.
661
845
  */
662
846
  export declare function removeElements<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, ids: Iterable<string>): IstarModel<EK, LK>;
663
847
  export declare function removeElement<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, id: string): IstarModel<EK, LK>;
@@ -665,6 +849,7 @@ export interface NewLink<K extends string = LinkKind> {
665
849
  /** Any link kind except dependency kinds (use `addDependency` for those). */
666
850
  readonly kind: Exclude<K, DependencyLinkKind>;
667
851
  readonly source: string;
852
+ /** An element id, or a link id for kinds whose rules target links. */
668
853
  readonly target: string;
669
854
  readonly id?: string;
670
855
  /** Contribution value: make, help, hurt or break. */
@@ -684,13 +869,35 @@ export interface ConnectOptions extends OperationContext {
684
869
  export type ConnectResult<EK extends string = ElementKind, LK extends string = LinkKind> = {
685
870
  ok: true;
686
871
  model: IstarModel<EK, LK>;
872
+ /** The new link; for an n-ary kind, the new leg or (for a new hub) the end. */
687
873
  link: IstarLink<LK>;
688
874
  reversed: boolean;
875
+ /** For an n-ary kind: the hub link the new link belongs to, as it now is. */
876
+ hub?: HubLink<EK, LK>;
689
877
  } | Extract<LinkCheck, {
690
878
  ok: false;
691
879
  }>;
692
- /** Adds a link between two elements if the iStar constraints allow it. */
880
+ /**
881
+ * Adds a link between two elements if the iStar constraints allow it.
882
+ *
883
+ * With an n-ary kind (`arity: 'n'`), a `target` that is one of the kind's hubs gets a new leg
884
+ * from `source` (see `addLeg`). Any other target gets a new hub, centred between source and
885
+ * target, with a leg from `source` and an end to `target`. `input.id` and `customProperties`
886
+ * then go to the hub, and `label` and `name` to the end.
887
+ */
693
888
  export declare function connect<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, input: NewLink<LK>, options?: ConnectOptions): ConnectResult<EK, LK>;
889
+ /**
890
+ * Adds a leg from `source` to the hub `hubId` of an n-ary link (one more source for it), if the
891
+ * kind's rules allow it. `options.id` is the leg's id.
892
+ */
893
+ export declare function addLeg<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, hubId: string, source: string, options?: ConnectOptions & {
894
+ readonly id?: string;
895
+ }): ConnectResult<EK, LK>;
896
+ /**
897
+ * Removes one leg of an n-ary link. Removing its last leg removes the whole link (hub and end),
898
+ * as `disconnect` does.
899
+ */
900
+ export declare function removeLeg<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, legId: string): IstarModel<EK, LK>;
694
901
  export interface NewDependency<EK extends string = ElementKind, LK extends string = LinkKind> {
695
902
  /** The depender: an actor or one of its inner elements. */
696
903
  readonly depender: string;
@@ -720,7 +927,10 @@ export type AddDependencyResult<EK extends string = ElementKind, LK extends stri
720
927
  }>;
721
928
  /** Adds depender → dependum → dependee. */
722
929
  export declare function addDependency<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, input: NewDependency<EK, LK>, options?: Omit<ConnectOptions, "tryReversed">): AddDependencyResult<EK, LK>;
723
- /** Removes a link. Removing either half of a dependency removes the whole dependency. */
930
+ /**
931
+ * Removes a link, and the links that target it. Removing either half of a dependency removes
932
+ * the whole dependency.
933
+ */
724
934
  export declare function disconnect<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, id: string): IstarModel<EK, LK>;
725
935
  export interface LinkPatch {
726
936
  /** `null` removes it. */
@@ -763,6 +973,10 @@ export type ModelChange = {
763
973
  } | {
764
974
  type: "connect";
765
975
  id: string;
976
+ hubId?: string;
977
+ } | {
978
+ type: "removeLeg";
979
+ id: string;
766
980
  } | {
767
981
  type: "addDependency";
768
982
  dependumId: string;
@@ -802,7 +1016,7 @@ export interface ModelStore<EK extends string = ElementKind, LK extends string =
802
1016
  getModel(): IstarModel<EK, LK>;
803
1017
  /** Returns an unsubscribe function. */
804
1018
  subscribe(listener: ModelListener<EK, LK>): () => void;
805
- canLink(source: string | IstarElement<EK>, target: string | IstarElement<EK>, kind: LK): LinkCheck;
1019
+ canLink(source: string | IstarElement<EK>, target: string | IstarElement<EK> | IstarLink<LK>, kind: LK): LinkCheck;
806
1020
  addElement(input: NewElement<EK>): IstarElement<EK>;
807
1021
  updateElement(id: string, patch: ElementPatch): void;
808
1022
  moveElement(id: string, x: number, y: number): void;
@@ -812,6 +1026,12 @@ export interface ModelStore<EK extends string = ElementKind, LK extends string =
812
1026
  setCollapsed(actorId: string, collapsed: boolean): void;
813
1027
  connect(input: NewLink<LK>, options?: Omit<ConnectOptions, "createId">): ConnectResult<EK, LK>;
814
1028
  addDependency(input: NewDependency<EK, LK>, options?: Omit<ConnectOptions, "createId" | "tryReversed">): AddDependencyResult<EK, LK>;
1029
+ /** Adds a leg from `source` to the hub of an n-ary link (see `ops.addLeg`). */
1030
+ addLeg(hubId: string, source: string, options?: Omit<ConnectOptions, "createId" | "tryReversed"> & {
1031
+ readonly id?: string;
1032
+ }): ConnectResult<EK, LK>;
1033
+ /** Removes a leg; the last one takes its hub and end with it. */
1034
+ removeLeg(id: string): void;
815
1035
  updateLink(id: string, patch: LinkPatch): void;
816
1036
  disconnect(id: string): void;
817
1037
  updateDiagram(patch: Partial<Diagram>): void;
@@ -908,4 +1128,137 @@ export declare function defineProperties<const S extends PropertyShape, const Ki
908
1128
  }): PropertySchema<S, Kind>;
909
1129
  export declare function validateModelProperties<EK extends string, LK extends string>(model: IstarModel<EK, LK>, schemas: readonly PropertySchema<PropertyShape, string>[]): ModelPropertyIssue[];
910
1130
  //#endregion
1131
+ //#region src/diagnostics.d.ts
1132
+ /**
1133
+ * The Goal Model Diagnostics Protocol: findings about a model's elements and links, published by
1134
+ * any analyser (a language server, a validator, a simulation…) and anchored by element id rather
1135
+ * than by text position. Data only: diagnostics are never part of the model and are never
1136
+ * written to disk.
1137
+ *
1138
+ * See goal-controller issue #24 (option D). Producers publish per source; consumers see the
1139
+ * union of every source, deduplicated by `(elementId, key, message)`.
1140
+ */
1141
+ export type DiagnosticSeverity = "error" | "warning" | "info" | "hint";
1142
+ /** Most severe first. */
1143
+ export declare const DIAGNOSTIC_SEVERITIES: readonly DiagnosticSeverity[];
1144
+ /** A zero-based position in the producer's document (as in LSP). */
1145
+ export interface DiagnosticPosition {
1146
+ readonly line: number;
1147
+ readonly character: number;
1148
+ }
1149
+ export interface DiagnosticRange {
1150
+ readonly start: DiagnosticPosition;
1151
+ readonly end: DiagnosticPosition;
1152
+ }
1153
+ export interface GoalDiagnostic {
1154
+ /** Id of the element or link the diagnostic is about. */
1155
+ readonly elementId: string;
1156
+ /** The custom property it is about; absent for the element as a whole. */
1157
+ readonly key?: string;
1158
+ readonly severity: DiagnosticSeverity;
1159
+ readonly message: string;
1160
+ /** Who published it (e.g. `mutrose`, `goal-language`). */
1161
+ readonly source?: string;
1162
+ /** Where it was found in the producer's document, if it has one. */
1163
+ readonly range?: DiagnosticRange;
1164
+ readonly code?: string;
1165
+ /** Producer data, passed through untouched. */
1166
+ readonly data?: unknown;
1167
+ }
1168
+ /** Negative when `a` is more severe than `b`. */
1169
+ export declare function compareSeverity(a: DiagnosticSeverity, b: DiagnosticSeverity): number;
1170
+ /** The most severe severity in a list (what a badge shows), or `undefined` if it is empty. */
1171
+ export declare function worstDiagnosticSeverity(diagnostics: readonly {
1172
+ readonly severity: DiagnosticSeverity;
1173
+ }[] | undefined): DiagnosticSeverity | undefined;
1174
+ /**
1175
+ * The union of several lists, in order, without duplicates: diagnostics with the same
1176
+ * `(elementId, key, message)` count once, keeping the first one's position and the most severe
1177
+ * severity among them.
1178
+ */
1179
+ export declare function mergeDiagnostics(lists: Iterable<readonly GoalDiagnostic[] | undefined>): GoalDiagnostic[];
1180
+ /** Diagnostics by element (or link) id, each list in the input order. */
1181
+ export declare function groupDiagnostics(diagnostics: readonly GoalDiagnostic[] | undefined): ReadonlyMap<string, readonly GoalDiagnostic[]>;
1182
+ /**
1183
+ * Holds the diagnostics of several sources. Each `publish` replaces that source's set, so a
1184
+ * producer re-publishes everything it currently reports, as an LSP server does.
1185
+ */
1186
+ export interface DiagnosticsStore {
1187
+ /**
1188
+ * Replaces `source`'s diagnostics (an empty list clears them). Entries without a `source` get
1189
+ * this one.
1190
+ */
1191
+ publish(source: string, diagnostics: readonly GoalDiagnostic[]): void;
1192
+ /** Removes one source's diagnostics, or every source's. */
1193
+ clear(source?: string): void;
1194
+ /** Sources with diagnostics, in the order they first published. */
1195
+ sources(): readonly string[];
1196
+ /** One source's diagnostics. */
1197
+ get(source: string): readonly GoalDiagnostic[];
1198
+ /**
1199
+ * Every source's diagnostics, merged (see `mergeDiagnostics`). The same array until the next
1200
+ * change, so it can back `useSyncExternalStore`.
1201
+ */
1202
+ getAll(): readonly GoalDiagnostic[];
1203
+ subscribe(listener: () => void): () => void;
1204
+ }
1205
+ export declare function createDiagnosticsStore(): DiagnosticsStore;
1206
+ /**
1207
+ * The parts of an LSP `Diagnostic` the adapter reads (structurally typed, so no LSP library is
1208
+ * needed). The element is taken from `data.elementId`, or MutRoSe's `data.nodeId`; the property
1209
+ * from `data.key`.
1210
+ */
1211
+ export interface LspDiagnosticLike {
1212
+ readonly range?: DiagnosticRange;
1213
+ /** LSP numbering: 1 error, 2 warning, 3 information, 4 hint. */
1214
+ readonly severity?: number;
1215
+ readonly message: string | {
1216
+ readonly value: string;
1217
+ };
1218
+ readonly source?: string;
1219
+ readonly code?: string | number | {
1220
+ readonly value: string | number;
1221
+ };
1222
+ readonly data?: unknown;
1223
+ }
1224
+ export interface FromLspOptions<D extends LspDiagnosticLike = LspDiagnosticLike> {
1225
+ /**
1226
+ * The element of a diagnostic whose `data` names none, e.g. by looking its range up in the
1227
+ * document. Return `undefined` to drop it.
1228
+ */
1229
+ readonly elementIdFor?: (diagnostic: D) => string | undefined;
1230
+ /**
1231
+ * `'vscode'` for `vscode.Diagnostic` objects, whose severity counts from 0 (error) to 3
1232
+ * (hint). Default `'lsp'`, the protocol's 1 to 4.
1233
+ */
1234
+ readonly severityNumbering?: "lsp" | "vscode";
1235
+ }
1236
+ /**
1237
+ * One LSP diagnostic anchored by `data.elementId` (or `data.nodeId`), or `undefined` when it
1238
+ * names no element. For range-anchored diagnostics use `fromLspDiagnostics` with `elementIdFor`.
1239
+ */
1240
+ export declare function fromLspDiagnostic(diagnostic: LspDiagnosticLike): GoalDiagnostic | undefined;
1241
+ /** LSP diagnostics as goal diagnostics; those that can't be anchored to an element are dropped. */
1242
+ export declare function fromLspDiagnostics<D extends LspDiagnosticLike>(diagnostics: readonly D[], options?: FromLspOptions<D>): GoalDiagnostic[];
1243
+ /**
1244
+ * The shape MutRoSe's VS Code extension posts to its webview: `{ nodeId, severity, message }`
1245
+ * (plus `range` and `source`). `nodeId` is missing when no node could be found.
1246
+ */
1247
+ export interface NodeIdDiagnostic {
1248
+ readonly nodeId?: string;
1249
+ readonly key?: string;
1250
+ readonly severity: DiagnosticSeverity | string;
1251
+ readonly message: string;
1252
+ readonly source?: string;
1253
+ readonly range?: DiagnosticRange;
1254
+ readonly code?: string;
1255
+ }
1256
+ /** One `{ nodeId, severity, message }` diagnostic, or `undefined` without a `nodeId`. */
1257
+ export declare function fromNodeIdDiagnostic(diagnostic: NodeIdDiagnostic): GoalDiagnostic | undefined;
1258
+ /**
1259
+ * `{ nodeId, severity, message }` diagnostics (MutRoSe) as goal diagnostics, dropping those
1260
+ * without a node: `store.publish('mutrose', fromNodeIdDiagnostics(message.diagnostics))`.
1261
+ */
1262
+ export declare function fromNodeIdDiagnostics(diagnostics: readonly NodeIdDiagnostic[]): GoalDiagnostic[];
1263
+ //#endregion
911
1264
  //# sourceMappingURL=index.d.cts.map