@istar-ts/core 0.7.0 → 0.8.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
@@ -68,6 +68,212 @@ export declare function isContributionLabel(value: string): value is Contributio
68
68
  /** `'istar.Goal'` → `'Goal'` */
69
69
  export declare function shortKindName(kind: ElementKind | LinkKind): string;
70
70
  //#endregion
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";
73
+ export type LinkCheck = {
74
+ ok: true;
75
+ } | {
76
+ ok: false;
77
+ reason: string;
78
+ code: ConstraintCode;
79
+ };
80
+ export interface CanLinkOptions<EK extends string = ElementKind, LK extends string = LinkKind> {
81
+ /** Default: the model's metamodel, or iStar 2.0. */
82
+ readonly metamodel?: Metamodel<EK, LK>;
83
+ }
84
+ /**
85
+ * Checks whether a link of `kind` may be added from `source` to `target` in `model`.
86
+ *
87
+ * For dependency links, `source` is the depender and `target` the dependee (an actor or one of
88
+ * its inner elements); the dependum sits between them and is not part of the check.
89
+ *
90
+ * Extended kinds (see `extendMetamodel`) are checked with the rules of the kind they behave
91
+ * like, or with their declarative rules, then with their extra predicate.
92
+ */
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;
94
+ /**
95
+ * What an extension's `check` predicate receives: the model, the endpoints, and the graph
96
+ * queries the iStar 2.0 rules use.
97
+ */
98
+ export interface LinkRuleContext {
99
+ readonly model: AnyIstarModel;
100
+ readonly metamodel: AnyMetamodel;
101
+ /** The link kind being added. */
102
+ readonly kind: string;
103
+ 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;
107
+ /** `element` is the source of a link of `kind` (or a kind behaving like it). */
108
+ isSourceOfType(element: IstarElement<string>, kind: string): boolean;
109
+ /** `element` is the target of a link of `kind` (or a kind behaving like it). */
110
+ isTargetOfType(element: IstarElement<string>, kind: string): boolean;
111
+ }
112
+ export interface ModelIssue<LK extends string = LinkKind> {
113
+ readonly linkId: string;
114
+ readonly kind: LK;
115
+ readonly source: string;
116
+ readonly target: string;
117
+ readonly code: ConstraintCode;
118
+ readonly message: string;
119
+ }
120
+ /**
121
+ * Re-validates every link of a model, the way piStar does when loading a file: dependencies
122
+ * 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.
124
+ */
125
+ export declare function validateModel<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, options?: CanLinkOptions<EK, LK>): ModelIssue<LK>[];
126
+ //#endregion
127
+ //#region src/metamodels.d.ts
128
+ export type ElementCategory = "actor" | "node";
129
+ export type LinkCategory = "actor" | "node" | "dependency";
130
+ /** A resolved element kind of a metamodel. */
131
+ export interface ElementKindDefinition<K extends string = string> {
132
+ readonly kind: K;
133
+ readonly category: ElementCategory;
134
+ /** Human-readable name, e.g. "Goal" or "Planning". */
135
+ readonly label: string;
136
+ /** Default size; for actors the initial boundary size. */
137
+ readonly size: Size;
138
+ /** Containment rules (nodes only; actors have none). */
139
+ readonly info?: NodeKindInfo;
140
+ /** The kind whose link rules this kind follows (built-in kinds have none). */
141
+ readonly behavesLike?: string;
142
+ /** The `type` written in piStar files. Equal to `kind` unless declared otherwise. */
143
+ readonly pistarType: string;
144
+ /** Name of the extension that declared it; absent for iStar 2.0 kinds. */
145
+ readonly extension?: string;
146
+ }
147
+ /**
148
+ * Declarative link rules, as piStar-ext's "Create a new Construct" dialog collects them:
149
+ * which kinds may be the source and target. Entries are kind names (`istar.Goal`,
150
+ * `rationalAgents.Plan`) or categories (`node`, `actor`, `*`). A kind name also matches kinds
151
+ * that behave like it, so `istar.Task` accepts a Planning that behaves like a Task.
152
+ */
153
+ export interface LinkRules {
154
+ readonly sources: readonly string[];
155
+ readonly targets: readonly string[];
156
+ /**
157
+ * Source and target must belong to the same actor (iStar 2.0 Guide, page 14). Default true
158
+ * for node links, ignored for actor links.
159
+ */
160
+ readonly sameActor?: boolean;
161
+ /** Allow a dependum as source or target. Default false. */
162
+ readonly allowDependum?: boolean;
163
+ /** Allow a link from an element onto itself. Default false. */
164
+ readonly allowSelf?: boolean;
165
+ /**
166
+ * At most one link `'kind'` of this kind, or `'any'` link at all, between the same two
167
+ * elements (in either direction); `false` for no limit. Default `'kind'`.
168
+ */
169
+ readonly unique?: "kind" | "any" | false;
170
+ }
171
+ /** A resolved link kind of a metamodel. */
172
+ export interface LinkKindDefinition<K extends string = string> {
173
+ readonly kind: K;
174
+ readonly category: LinkCategory;
175
+ readonly label: string;
176
+ readonly info: LinkKindInfo;
177
+ /** The link kind whose rules (and meaning, for other rules) this kind takes on. */
178
+ readonly behavesLike?: string;
179
+ /** Declarative rules, for link kinds that don't behave like another kind. */
180
+ readonly rules?: LinkRules;
181
+ /** An extra predicate, checked after the inherited or declarative rules pass. */
182
+ readonly check?: (context: LinkRuleContext) => LinkCheck;
183
+ readonly pistarType: string;
184
+ readonly extension?: string;
185
+ }
186
+ /** A metamodel: the element and link kinds a model may use, and their rules. */
187
+ export interface Metamodel<EK extends string = ElementKind, LK extends string = LinkKind> {
188
+ /** `istar-2.0`, followed by the names of the extensions applied, e.g. `istar-2.0+rationalAgents`. */
189
+ readonly name: string;
190
+ readonly extensions: readonly string[];
191
+ /** In declaration order: iStar 2.0 kinds first, then each extension's. */
192
+ readonly elements: ReadonlyMap<EK, ElementKindDefinition<EK>>;
193
+ readonly links: ReadonlyMap<LK, LinkKindDefinition<LK>>;
194
+ /** `type` in piStar files → kind. */
195
+ readonly elementsByPistarType: ReadonlyMap<string, EK>;
196
+ readonly linksByPistarType: ReadonlyMap<string, LK>;
197
+ }
198
+ /** The kinds of a metamodel, as type-level unions. */
199
+ export type ElementKindOf<M> = M extends Metamodel<infer EK, string> ? EK : never;
200
+ export type LinkKindOf<M> = M extends Metamodel<string, infer LK> ? LK : never;
201
+ /** A metamodel of any dialect, for code that handles every kind generically. */
202
+ export type AnyMetamodel = Metamodel<string, string>;
203
+ /** iStar 2.0, as the piStar tool implements it. The default metamodel everywhere. */
204
+ export declare const ISTAR_2_0: Metamodel;
205
+ export interface ElementKindDeclaration<K extends string = string> {
206
+ /** Namespaced kind name, e.g. `rationalAgents.Planning`. */
207
+ readonly kind: K;
208
+ /** Default: the part after the namespace ("Planning"). */
209
+ readonly label?: string;
210
+ /** Required unless `behavesLike` is given (then it is the same category). */
211
+ readonly category?: ElementCategory;
212
+ /** An existing element kind whose link rules this kind follows, e.g. `istar.Task`. */
213
+ readonly behavesLike?: string;
214
+ /** Containment rules for nodes. Default: those of `behavesLike`, else piStar-ext's (inner, dependum). */
215
+ readonly info?: Partial<NodeKindInfo>;
216
+ /** Default: that of `behavesLike`, else piStar-ext's 90×55 for nodes and 200×120 for actors. */
217
+ readonly size?: Size;
218
+ /**
219
+ * The `type` used in piStar files, when it differs from `kind`. piStar-ext saves its new
220
+ * constructs as `istar.<Name>`, so `pistarType: 'istar.Planning'` reads its files.
221
+ */
222
+ readonly pistarType?: string;
223
+ }
224
+ export interface LinkKindDeclaration<K extends string = string> {
225
+ readonly kind: K;
226
+ readonly label?: string;
227
+ /** Default: that of `behavesLike`, else `node`. */
228
+ readonly category?: LinkCategory;
229
+ /** An existing link kind whose rules this kind takes on, e.g. `istar.OrRefinementLink`. */
230
+ readonly behavesLike?: string;
231
+ /** Declarative rules; give these or `behavesLike`, not both. */
232
+ readonly rules?: LinkRules;
233
+ /** An extra predicate, checked after the other rules pass. */
234
+ readonly check?: (context: LinkRuleContext) => LinkCheck;
235
+ /** Default: that of `behavesLike`, else none (no fixed label, no changeable value). */
236
+ readonly info?: LinkKindInfo;
237
+ readonly pistarType?: string;
238
+ }
239
+ /** A named set of new kinds, applied to a metamodel with `extendMetamodel`. */
240
+ export interface MetamodelExtension<NEK extends string = string, NLK extends string = string> {
241
+ /** Identifies the extension, e.g. `rationalAgents`. */
242
+ readonly name: string;
243
+ readonly elements?: readonly ElementKindDeclaration<NEK>[];
244
+ readonly links?: readonly LinkKindDeclaration<NLK>[];
245
+ }
246
+ export declare class MetamodelError extends Error {
247
+ override name: string;
248
+ }
249
+ /**
250
+ * Declares an extension, keeping its kind names as literal types (so `extendMetamodel` can
251
+ * type the result). Identity at runtime.
252
+ */
253
+ export declare function defineMetamodelExtension<const NEK extends string = never, const NLK extends string = never>(extension: MetamodelExtension<NEK, NLK>): MetamodelExtension<NEK, NLK>;
254
+ /**
255
+ * Returns a new metamodel with the extension's kinds added. `base` is not modified.
256
+ *
257
+ * Throws `MetamodelError` when a kind name isn't namespaced, uses the reserved `istar.`
258
+ * prefix, collides with an existing kind or piStar type, or refers to an unknown kind.
259
+ */
260
+ 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>;
261
+ export declare function elementKindDefinition(metamodel: AnyMetamodel, kind: string): ElementKindDefinition | undefined;
262
+ export declare function linkKindDefinition(metamodel: AnyMetamodel, kind: string): LinkKindDefinition | undefined;
263
+ /** True for actor-category kinds (Actor, Agent, Role and extensions behaving like them). */
264
+ export declare function isActorKindIn(metamodel: AnyMetamodel, kind: string): boolean;
265
+ /** True for node-category kinds (intentional elements, built-in or extended). */
266
+ export declare function isNodeKindIn(metamodel: AnyMetamodel, kind: string): boolean;
267
+ /**
268
+ * The built-in (or rule-carrying) kind an element kind ultimately behaves like: `istar.Task`
269
+ * for a Planning that behaves like a Task; the kind itself when it behaves like nothing.
270
+ */
271
+ export declare function effectiveElementKind(metamodel: AnyMetamodel, kind: string): string;
272
+ /** Like `effectiveElementKind`, for link kinds. */
273
+ export declare function effectiveLinkKind(metamodel: AnyMetamodel, kind: string): string;
274
+ /** True when `kind` is `ancestor` or (transitively) behaves like it. */
275
+ export declare function elementBehavesLike(metamodel: AnyMetamodel, kind: string, ancestor: string): boolean;
276
+ //#endregion
71
277
  //#region src/model.d.ts
72
278
  /**
73
279
  * piStar stores every custom property value as a string (e.g. `"initialValue": "false"`).
@@ -94,7 +300,11 @@ export interface LinkDisplay {
94
300
  readonly vertices?: readonly Point[];
95
301
  readonly [key: string]: unknown;
96
302
  }
97
- export interface IstarElement<K extends ElementKind = ElementKind> {
303
+ /**
304
+ * An element of the model. `K` is the kind union: iStar 2.0's by default, wider for models of an
305
+ * extended metamodel (see `extendMetamodel`).
306
+ */
307
+ export interface IstarElement<K extends string = ElementKind> {
98
308
  readonly id: string;
99
309
  readonly kind: K;
100
310
  /** The element's label; `text` in the save file. */
@@ -120,7 +330,7 @@ export interface IstarElement<K extends ElementKind = ElementKind> {
120
330
  }
121
331
  export type IstarActor = IstarElement<ActorKind>;
122
332
  export type IstarNode = IstarElement<NodeKind>;
123
- export interface IstarLink<K extends LinkKind = LinkKind> {
333
+ export interface IstarLink<K extends string = LinkKind> {
124
334
  readonly id: string;
125
335
  readonly kind: K;
126
336
  readonly source: string;
@@ -142,10 +352,12 @@ export interface Diagram {
142
352
  /**
143
353
  * An immutable iStar model. Maps preserve insertion order, which is the order elements and
144
354
  * links are written back to disk.
355
+ *
356
+ * `EK`/`LK` are the element and link kind unions; they default to iStar 2.0's.
145
357
  */
146
- export interface IstarModel {
147
- readonly elements: ReadonlyMap<string, IstarElement>;
148
- readonly links: ReadonlyMap<string, IstarLink>;
358
+ export interface IstarModel<EK extends string = ElementKind, LK extends string = LinkKind> {
359
+ readonly elements: ReadonlyMap<string, IstarElement<EK>>;
360
+ readonly links: ReadonlyMap<string, IstarLink<LK>>;
149
361
  readonly diagram?: Diagram;
150
362
  readonly tool?: string;
151
363
  readonly istar?: string;
@@ -159,51 +371,39 @@ export interface IstarModel {
159
371
  export declare const PISTAR_TOOL = "pistar.2.1.0";
160
372
  export declare const ISTAR_VERSION = "2.0";
161
373
  export declare const DEFAULT_DIAGRAM: Diagram;
374
+ /** A model of any dialect, for code that handles every kind generically. */
375
+ export type AnyIstarModel = IstarModel<string, string>;
376
+ /**
377
+ * Associates `metamodel` with `model` and returns the same model, typed for it. Use when a
378
+ * model was built without the library's operations (e.g. with an object spread), which loses
379
+ * the association.
380
+ */
381
+ export declare function withMetamodel<EK extends string, LK extends string>(model: IstarModel<string, string>, metamodel: Metamodel<EK, LK>): IstarModel<EK, LK>;
382
+ /** Gives `to` the metamodel of `from` (used for every model an operation derives). */
383
+ export declare function inheritMetamodel<M extends IstarModel<string, string>>(from: IstarModel<string, string>, to: M): M;
384
+ export declare function createEmptyModel<EK extends string, LK extends string>(diagram: Diagram | undefined, options: {
385
+ metamodel: Metamodel<EK, LK>;
386
+ }): IstarModel<EK, LK>;
162
387
  export declare function createEmptyModel(diagram?: Diagram): IstarModel;
163
- export declare function isActor(element: IstarElement | undefined): element is IstarActor;
164
- export declare function isNode(element: IstarElement | undefined): element is IstarNode;
388
+ /**
389
+ * The metamodel a model was read or created with (see `parsePistar`, `createEmptyModel`), or
390
+ * iStar 2.0. Constraints, operations and serialization use it unless given another one.
391
+ */
392
+ export declare function metamodelOf<EK extends string, LK extends string>(model: IstarModel<EK, LK>): Metamodel<EK, LK>;
393
+ /** True for actor-category elements; pass the metamodel for extended actor kinds. */
394
+ export declare function isActor(element: IstarElement<string> | undefined, metamodel?: AnyMetamodel): element is IstarActor;
395
+ /** True for node-category elements; pass the metamodel for extended node kinds. */
396
+ export declare function isNode(element: IstarElement<string> | undefined, metamodel?: AnyMetamodel): element is IstarNode;
165
397
  /** Inner elements of an actor, in model order. */
166
- export declare function childrenOf(model: IstarModel, actorId: string): IstarElement[];
398
+ export declare function childrenOf<EK extends string, LK extends string>(model: IstarModel<EK, LK>, actorId: string): IstarElement<EK>[];
167
399
  /** Links that have the element as source or target. */
168
- export declare function linksOf(model: IstarModel, elementId: string): IstarLink[];
400
+ export declare function linksOf<EK extends string, LK extends string>(model: IstarModel<EK, LK>, elementId: string): IstarLink<LK>[];
169
401
  /** For a dependum, the two halves of its dependency: depender → dependum → dependee. */
170
- export declare function dependencyLinksOf(model: IstarModel, dependumId: string): {
171
- inbound?: IstarLink;
172
- outbound?: IstarLink;
402
+ export declare function dependencyLinksOf<EK extends string, LK extends string>(model: IstarModel<EK, LK>, dependumId: string): {
403
+ inbound?: IstarLink<LK>;
404
+ outbound?: IstarLink<LK>;
173
405
  };
174
406
  //#endregion
175
- //#region src/constraints.d.ts
176
- 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";
177
- export type LinkCheck = {
178
- ok: true;
179
- } | {
180
- ok: false;
181
- reason: string;
182
- code: ConstraintCode;
183
- };
184
- type ElementRef = string | IstarElement;
185
- /**
186
- * Checks whether a link of `kind` may be added from `source` to `target` in `model`.
187
- *
188
- * For `istar.DependencyLink`, `source` is the depender and `target` the dependee (an actor or
189
- * one of its inner elements); the dependum sits between them and is not part of the check.
190
- */
191
- export declare function canLink(model: IstarModel, source: ElementRef, target: ElementRef, kind: LinkKind): LinkCheck;
192
- export interface ModelIssue {
193
- readonly linkId: string;
194
- readonly kind: LinkKind;
195
- readonly source: string;
196
- readonly target: string;
197
- readonly code: ConstraintCode;
198
- readonly message: string;
199
- }
200
- /**
201
- * Re-validates every link of a model, the way piStar does when loading a file: dependencies
202
- * first (each checked before its own links are added), then the remaining links in file
203
- * order, each against the graph built so far. Invalid links are reported, not removed.
204
- */
205
- export declare function validateModel(model: IstarModel): ModelIssue[];
206
- //#endregion
207
407
  //#region src/serialization.d.ts
208
408
  export interface PistarElementJson {
209
409
  id: string;
@@ -254,6 +454,10 @@ export interface PistarFile {
254
454
  export declare class PistarParseError extends Error {
255
455
  override name: string;
256
456
  }
457
+ /** Thrown by `toPistar` for a kind its metamodel doesn't know (rather than writing it wrong). */
458
+ export declare class PistarWriteError extends Error {
459
+ override name: string;
460
+ }
257
461
  /**
258
462
  * Parse-time bookkeeping that lets `toPistar` reproduce the original key order. It has no
259
463
  * meaning for the model itself and may be dropped.
@@ -265,20 +469,33 @@ export interface SourceLayout {
265
469
  readonly display: readonly string[];
266
470
  }
267
471
  /** The key order recorded when `model` was parsed, if it came from `parsePistar`. */
268
- export declare function getSourceLayout(model: IstarModel): SourceLayout | undefined;
472
+ export declare function getSourceLayout<EK extends string, LK extends string>(model: IstarModel<EK, LK>): SourceLayout | undefined;
269
473
  /**
270
474
  * Carries the recorded key order over to a model derived from `from` (the store does this for
271
475
  * every edit, so saving an edited file keeps a stable diff).
272
476
  */
273
- export declare function inheritSourceLayout<M extends IstarModel>(from: IstarModel, to: M): M;
477
+ export declare function inheritSourceLayout<M extends IstarModel<string, string>>(from: IstarModel<string, string>, to: M): M;
274
478
  /**
275
479
  * Parses a piStar save file (JSON text or an already-parsed object). The input is not mutated.
276
480
  *
277
481
  * Unlike the piStar tool, elements with `x` or `y` equal to 0 are kept: upstream's
278
482
  * `if (element.x && element.y)` silently drops them, which looks unintended.
279
483
  */
484
+ export interface ParsePistarOptions<EK extends string = ElementKind, LK extends string = LinkKind> {
485
+ /**
486
+ * The metamodel whose kinds the file may use. Default iStar 2.0: any other `type` throws,
487
+ * as in the piStar tool. The parsed model remembers it (see `metamodelOf`).
488
+ */
489
+ readonly metamodel?: Metamodel<EK, LK>;
490
+ }
491
+ export declare function parsePistar<EK extends string, LK extends string>(input: string | PistarFile | Record<string, unknown>, options: ParsePistarOptions<EK, LK> & {
492
+ readonly metamodel: Metamodel<EK, LK>;
493
+ }): IstarModel<EK, LK>;
494
+ export declare function parsePistar(input: string | PistarFile | Record<string, unknown>, options: ParsePistarOptions): IstarModel;
280
495
  export declare function parsePistar(input: string | PistarFile | Record<string, unknown>): IstarModel;
281
496
  export interface ToPistarOptions {
497
+ /** Default: the model's metamodel, or iStar 2.0. Decides each kind's `type` and category. */
498
+ metamodel?: AnyMetamodel;
282
499
  /**
283
500
  * Value for `saveDate`. Defaults to the model's own `saveDate` (so a load/save round trip is
284
501
  * lossless), or the current time for models that never had one. Pass `new Date()` when
@@ -287,9 +504,9 @@ export interface ToPistarOptions {
287
504
  saveDate?: string | Date;
288
505
  }
289
506
  /** Builds the save-file object for a model. `toPistar` is this plus `JSON.stringify`. */
290
- export declare function toPistarObject(model: IstarModel, options?: ToPistarOptions): PistarFile;
507
+ export declare function toPistarObject<EK extends string, LK extends string>(input: IstarModel<EK, LK>, options?: ToPistarOptions): PistarFile;
291
508
  /** Serializes a model to piStar's JSON text (2-space indentation, no trailing newline). */
292
- export declare function toPistar(model: IstarModel, options?: ToPistarOptions): string;
509
+ export declare function toPistar<EK extends string, LK extends string>(model: IstarModel<EK, LK>, options?: ToPistarOptions): string;
293
510
  declare namespace operations_d_exports {
294
511
  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 };
295
512
  }
@@ -302,21 +519,21 @@ export declare const defaultIdGenerator: IdGenerator;
302
519
  export interface OperationContext {
303
520
  readonly createId?: IdGenerator;
304
521
  }
305
- export interface NewElement {
306
- readonly kind: ElementKind;
522
+ export interface NewElement<K extends string = ElementKind> {
523
+ readonly kind: K;
307
524
  readonly x: number;
308
525
  readonly y: number;
309
526
  readonly id?: string;
310
- /** Defaults to the kind's short name ("Goal"), like piStar. */
527
+ /** Defaults to the kind's label ("Goal"), like piStar. */
311
528
  readonly name?: string;
312
529
  /** Actor to place the element in. Required in practice for iStar nodes (see NodeKindInfo). */
313
530
  readonly parent?: string;
314
531
  readonly customProperties?: CustomProperties;
315
532
  readonly display?: ElementDisplay;
316
533
  }
317
- export declare function addElement(model: IstarModel, input: NewElement, ctx?: OperationContext): {
318
- model: IstarModel;
319
- element: IstarElement;
534
+ export declare function addElement<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, input: NewElement<EK>, ctx?: OperationContext): {
535
+ model: IstarModel<EK, LK>;
536
+ element: IstarElement<EK>;
320
537
  };
321
538
  export interface ElementPatch {
322
539
  readonly name?: string;
@@ -327,26 +544,27 @@ export interface ElementPatch {
327
544
  readonly [key: string]: unknown;
328
545
  } | null;
329
546
  }
330
- export declare function updateElement(model: IstarModel, id: string, patch: ElementPatch): IstarModel;
547
+ export declare function updateElement<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, id: string, patch: ElementPatch): IstarModel<EK, LK>;
331
548
  /**
332
549
  * Moves an element to an absolute position. Moving an actor shifts its inner elements by the
333
550
  * same offset.
334
551
  */
335
- export declare function moveElement(model: IstarModel, id: string, x: number, y: number): IstarModel;
552
+ export declare function moveElement<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, id: string, x: number, y: number): IstarModel<EK, LK>;
336
553
  /**
337
554
  * Moves an inner element into another actor (or out of any actor with `null`). The element
338
555
  * becomes the actor's last child. Links that become invalid are kept; `validateModel` reports
339
556
  * them.
340
557
  */
341
- export declare function nestElement(model: IstarModel, id: string, parent: string | null): IstarModel;
558
+ export declare function nestElement<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, id: string, parent: string | null): IstarModel<EK, LK>;
342
559
  /**
343
560
  * Removes elements and everything that depends on them: inner elements of removed actors,
344
561
  * links touching removed elements, and the rest of any dependency that loses a part.
345
562
  */
346
- export declare function removeElements(model: IstarModel, ids: Iterable<string>): IstarModel;
347
- export declare function removeElement(model: IstarModel, id: string): IstarModel;
348
- export interface NewLink {
349
- readonly kind: Exclude<LinkKind, "istar.DependencyLink">;
563
+ export declare function removeElements<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, ids: Iterable<string>): IstarModel<EK, LK>;
564
+ export declare function removeElement<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, id: string): IstarModel<EK, LK>;
565
+ export interface NewLink<K extends string = LinkKind> {
566
+ /** Any link kind except dependency kinds (use `addDependency` for those). */
567
+ readonly kind: Exclude<K, DependencyLinkKind>;
350
568
  readonly source: string;
351
569
  readonly target: string;
352
570
  readonly id?: string;
@@ -364,23 +582,24 @@ export interface ConnectOptions extends OperationContext {
364
582
  */
365
583
  readonly tryReversed?: boolean;
366
584
  }
367
- export type ConnectResult = {
585
+ export type ConnectResult<EK extends string = ElementKind, LK extends string = LinkKind> = {
368
586
  ok: true;
369
- model: IstarModel;
370
- link: IstarLink;
587
+ model: IstarModel<EK, LK>;
588
+ link: IstarLink<LK>;
371
589
  reversed: boolean;
372
590
  } | Extract<LinkCheck, {
373
591
  ok: false;
374
592
  }>;
375
593
  /** Adds a link between two elements if the iStar constraints allow it. */
376
- export declare function connect(model: IstarModel, input: NewLink, options?: ConnectOptions): ConnectResult;
377
- export interface NewDependency {
594
+ export declare function connect<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, input: NewLink<LK>, options?: ConnectOptions): ConnectResult<EK, LK>;
595
+ export interface NewDependency<EK extends string = ElementKind, LK extends string = LinkKind> {
378
596
  /** The depender: an actor or one of its inner elements. */
379
597
  readonly depender: string;
380
598
  /** The dependee: an actor or one of its inner elements. */
381
599
  readonly dependee: string;
382
600
  readonly dependum: {
383
- readonly kind: NodeKind;
601
+ /** A node kind (built-in or extended) that can be a dependum. */
602
+ readonly kind: Exclude<EK, ActorKind>;
384
603
  readonly name?: string;
385
604
  readonly id?: string;
386
605
  /** Defaults to the midpoint between depender and dependee, as in piStar. */
@@ -388,20 +607,22 @@ export interface NewDependency {
388
607
  readonly y?: number;
389
608
  readonly customProperties?: CustomProperties;
390
609
  };
610
+ /** A dependency link kind. Default `istar.DependencyLink`. */
611
+ readonly linkKind?: LK;
391
612
  readonly linkIds?: readonly [string, string];
392
613
  }
393
- export type AddDependencyResult = {
614
+ export type AddDependencyResult<EK extends string = ElementKind, LK extends string = LinkKind> = {
394
615
  ok: true;
395
- model: IstarModel;
396
- dependum: IstarElement;
397
- links: readonly [IstarLink, IstarLink];
616
+ model: IstarModel<EK, LK>;
617
+ dependum: IstarElement<EK>;
618
+ links: readonly [IstarLink<LK>, IstarLink<LK>];
398
619
  } | Extract<LinkCheck, {
399
620
  ok: false;
400
621
  }>;
401
622
  /** Adds depender → dependum → dependee. */
402
- export declare function addDependency(model: IstarModel, input: NewDependency, options?: Omit<ConnectOptions, "tryReversed">): AddDependencyResult;
623
+ 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>;
403
624
  /** Removes a link. Removing either half of a dependency removes the whole dependency. */
404
- export declare function disconnect(model: IstarModel, id: string): IstarModel;
625
+ export declare function disconnect<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, id: string): IstarModel<EK, LK>;
405
626
  export interface LinkPatch {
406
627
  /** `null` removes it. */
407
628
  readonly label?: string | null;
@@ -411,10 +632,10 @@ export interface LinkPatch {
411
632
  readonly [key: string]: unknown;
412
633
  } | null;
413
634
  }
414
- export declare function updateLink(model: IstarModel, id: string, patch: LinkPatch): IstarModel;
415
- export declare function updateDiagram(model: IstarModel, patch: Partial<Diagram>): IstarModel;
635
+ export declare function updateLink<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, id: string, patch: LinkPatch): IstarModel<EK, LK>;
636
+ export declare function updateDiagram<EK extends string = ElementKind, LK extends string = LinkKind>(model: IstarModel<EK, LK>, patch: Partial<Diagram>): IstarModel<EK, LK>;
416
637
  /** Where dependency depender/dependee currently point (see `dependencyLinksOf`). */
417
- export declare function dependencyEnds(model: IstarModel, dependumId: string): {
638
+ export declare function dependencyEnds<EK extends string, LK extends string>(model: IstarModel<EK, LK>, dependumId: string): {
418
639
  depender?: string;
419
640
  dependee?: string;
420
641
  };
@@ -460,40 +681,45 @@ export type ModelChange = {
460
681
  } | {
461
682
  type: "replace";
462
683
  };
463
- export interface ModelChangeEvent {
464
- readonly model: IstarModel;
465
- readonly previous: IstarModel;
684
+ export interface ModelChangeEvent<EK extends string = ElementKind, LK extends string = LinkKind> {
685
+ readonly model: IstarModel<EK, LK>;
686
+ readonly previous: IstarModel<EK, LK>;
466
687
  /** `undo`/`redo`/`load` carry no changes; a transaction carries all of its changes. */
467
688
  readonly changes: readonly ModelChange[];
468
689
  readonly source: "edit" | "undo" | "redo" | "load";
469
690
  }
470
- export type ModelListener = (event: ModelChangeEvent) => void;
471
- export interface ModelStoreOptions {
691
+ export type ModelListener<EK extends string = ElementKind, LK extends string = LinkKind> = (event: ModelChangeEvent<EK, LK>) => void;
692
+ export interface ModelStoreOptions<EK extends string = ElementKind, LK extends string = LinkKind> {
693
+ /**
694
+ * Metamodel for the default empty model, when no initial model is given. A given initial
695
+ * model keeps its own (see `metamodelOf`).
696
+ */
697
+ readonly metamodel?: Metamodel<EK, LK>;
472
698
  readonly createId?: IdGenerator;
473
699
  /** Maximum number of undo steps kept. Default 100. */
474
700
  readonly historyLimit?: number;
475
701
  }
476
- export interface ModelStore {
477
- getModel(): IstarModel;
702
+ export interface ModelStore<EK extends string = ElementKind, LK extends string = LinkKind> {
703
+ getModel(): IstarModel<EK, LK>;
478
704
  /** Returns an unsubscribe function. */
479
- subscribe(listener: ModelListener): () => void;
480
- canLink(source: string | IstarElement, target: string | IstarElement, kind: LinkKind): LinkCheck;
481
- addElement(input: NewElement): IstarElement;
705
+ subscribe(listener: ModelListener<EK, LK>): () => void;
706
+ canLink(source: string | IstarElement<EK>, target: string | IstarElement<EK>, kind: LK): LinkCheck;
707
+ addElement(input: NewElement<EK>): IstarElement<EK>;
482
708
  updateElement(id: string, patch: ElementPatch): void;
483
709
  moveElement(id: string, x: number, y: number): void;
484
710
  nestElement(id: string, parent: string | null): void;
485
711
  removeElement(id: string): void;
486
712
  removeElements(ids: Iterable<string>): void;
487
713
  setCollapsed(actorId: string, collapsed: boolean): void;
488
- connect(input: NewLink, options?: Omit<ConnectOptions, "createId">): ConnectResult;
489
- addDependency(input: NewDependency, options?: Omit<ConnectOptions, "createId" | "tryReversed">): AddDependencyResult;
714
+ connect(input: NewLink<LK>, options?: Omit<ConnectOptions, "createId">): ConnectResult<EK, LK>;
715
+ addDependency(input: NewDependency<EK, LK>, options?: Omit<ConnectOptions, "createId" | "tryReversed">): AddDependencyResult<EK, LK>;
490
716
  updateLink(id: string, patch: LinkPatch): void;
491
717
  disconnect(id: string): void;
492
718
  updateDiagram(patch: Partial<Diagram>): void;
493
719
  /** Replaces the model as one undoable edit. */
494
- replace(model: IstarModel): void;
720
+ replace(model: IstarModel<EK, LK>): void;
495
721
  /** Replaces the model and clears history (like opening a file in piStar). */
496
- load(model: IstarModel): void;
722
+ load(model: IstarModel<EK, LK>): void;
497
723
  /**
498
724
  * Groups several edits into one undo step and one change event. Nested calls join the
499
725
  * outermost transaction. If `fn` throws, the model is rolled back.
@@ -505,6 +731,10 @@ export interface ModelStore {
505
731
  canRedo(): boolean;
506
732
  clearHistory(): void;
507
733
  }
734
+ export declare function createModelStore<EK extends string, LK extends string>(initial: IstarModel<EK, LK>, options?: ModelStoreOptions<EK, LK>): ModelStore<EK, LK>;
735
+ export declare function createModelStore<EK extends string, LK extends string>(initial: undefined, options: ModelStoreOptions<EK, LK> & {
736
+ readonly metamodel: Metamodel<EK, LK>;
737
+ }): ModelStore<EK, LK>;
508
738
  export declare function createModelStore(initial?: IstarModel, options?: ModelStoreOptions): ModelStore;
509
739
  //#endregion
510
740
  //#region src/properties.d.ts
@@ -551,8 +781,12 @@ export interface PropertyIssue {
551
781
  key: string;
552
782
  message: string;
553
783
  }
554
- export interface PropertySchema<S extends PropertyShape = PropertyShape> {
555
- readonly kind: ElementKind | LinkKind;
784
+ /**
785
+ * Typed custom properties of one element or link kind. `K` is the kind: iStar 2.0's kinds by
786
+ * default; any extension kind (see `extendMetamodel`) is accepted too.
787
+ */
788
+ export interface PropertySchema<S extends PropertyShape = PropertyShape, K extends string = ElementKind | LinkKind> {
789
+ readonly kind: K;
556
790
  readonly shape: S;
557
791
  read(source: CustomProperties | undefined | PropertyOwner): {
558
792
  values: Partial<InferProperties<S>>;
@@ -570,9 +804,9 @@ export interface PropertyOwner {
570
804
  export interface ModelPropertyIssue extends PropertyIssue {
571
805
  id: string;
572
806
  }
573
- export declare function defineProperties<const S extends PropertyShape>(kind: ElementKind | LinkKind, shape: S, options?: {
807
+ export declare function defineProperties<const S extends PropertyShape, const Kind extends string = ElementKind | LinkKind>(kind: Kind, shape: S, options?: {
574
808
  validate?: (values: Partial<InferProperties<S>>) => PropertyIssue[];
575
- }): PropertySchema<S>;
576
- export declare function validateModelProperties(model: IstarModel, schemas: readonly PropertySchema[]): ModelPropertyIssue[];
809
+ }): PropertySchema<S, Kind>;
810
+ export declare function validateModelProperties<EK extends string, LK extends string>(model: IstarModel<EK, LK>, schemas: readonly PropertySchema<PropertyShape, string>[]): ModelPropertyIssue[];
577
811
  //#endregion
578
812
  //# sourceMappingURL=index.d.cts.map