@istar-ts/core 0.7.0 → 0.8.1
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 +80 -0
- package/dist/index.cjs +562 -124
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +341 -95
- package/dist/index.d.ts +341 -95
- package/dist/index.js +546 -125
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -137,6 +137,236 @@ function shortKindName(kind) {
|
|
|
137
137
|
return kind.slice(6);
|
|
138
138
|
}
|
|
139
139
|
//#endregion
|
|
140
|
+
//#region src/metamodels.ts
|
|
141
|
+
function shortName(kind) {
|
|
142
|
+
const dot = kind.indexOf(".");
|
|
143
|
+
return dot === -1 ? kind : kind.slice(dot + 1);
|
|
144
|
+
}
|
|
145
|
+
function buildIstar() {
|
|
146
|
+
const elements = /* @__PURE__ */ new Map();
|
|
147
|
+
for (const kind of ACTOR_KINDS) elements.set(kind, {
|
|
148
|
+
kind,
|
|
149
|
+
category: "actor",
|
|
150
|
+
label: shortName(kind),
|
|
151
|
+
size: DEFAULT_ELEMENT_SIZE[kind],
|
|
152
|
+
pistarType: kind
|
|
153
|
+
});
|
|
154
|
+
for (const kind of NODE_KINDS) elements.set(kind, {
|
|
155
|
+
kind,
|
|
156
|
+
category: "node",
|
|
157
|
+
label: shortName(kind),
|
|
158
|
+
size: DEFAULT_ELEMENT_SIZE[kind],
|
|
159
|
+
info: NODE_KIND_INFO[kind],
|
|
160
|
+
pistarType: kind
|
|
161
|
+
});
|
|
162
|
+
const links = /* @__PURE__ */ new Map();
|
|
163
|
+
const labels = {
|
|
164
|
+
"istar.IsALink": "Is-A",
|
|
165
|
+
"istar.ParticipatesInLink": "Participates-In",
|
|
166
|
+
"istar.DependencyLink": "Dependency",
|
|
167
|
+
"istar.AndRefinementLink": "And-Refinement",
|
|
168
|
+
"istar.OrRefinementLink": "Or-Refinement",
|
|
169
|
+
"istar.NeededByLink": "Needed-By",
|
|
170
|
+
"istar.QualificationLink": "Qualification",
|
|
171
|
+
"istar.ContributionLink": "Contribution"
|
|
172
|
+
};
|
|
173
|
+
const add = (kinds, category) => {
|
|
174
|
+
for (const kind of kinds) links.set(kind, {
|
|
175
|
+
kind,
|
|
176
|
+
category,
|
|
177
|
+
label: labels[kind],
|
|
178
|
+
info: LINK_KIND_INFO[kind],
|
|
179
|
+
pistarType: kind
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
add(ACTOR_LINK_KINDS, "actor");
|
|
183
|
+
add(DEPENDENCY_LINK_KINDS, "dependency");
|
|
184
|
+
add(NODE_LINK_KINDS, "node");
|
|
185
|
+
return {
|
|
186
|
+
name: "istar-2.0",
|
|
187
|
+
extensions: [],
|
|
188
|
+
elements,
|
|
189
|
+
links,
|
|
190
|
+
elementsByPistarType: new Map([...elements.keys()].map((k) => [k, k])),
|
|
191
|
+
linksByPistarType: new Map([...links.keys()].map((k) => [k, k]))
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/** iStar 2.0, as the piStar tool implements it. The default metamodel everywhere. */
|
|
195
|
+
const ISTAR_2_0 = buildIstar();
|
|
196
|
+
var MetamodelError = class extends Error {
|
|
197
|
+
name = "MetamodelError";
|
|
198
|
+
};
|
|
199
|
+
/**
|
|
200
|
+
* Declares an extension, keeping its kind names as literal types (so `extendMetamodel` can
|
|
201
|
+
* type the result). Identity at runtime.
|
|
202
|
+
*/
|
|
203
|
+
function defineMetamodelExtension(extension) {
|
|
204
|
+
return extension;
|
|
205
|
+
}
|
|
206
|
+
const KIND_NAME = /^[A-Za-z_][\w-]*\.[A-Za-z_][\w-]*$/;
|
|
207
|
+
const CATEGORY_SELECTORS = /* @__PURE__ */ new Set([
|
|
208
|
+
"node",
|
|
209
|
+
"actor",
|
|
210
|
+
"*"
|
|
211
|
+
]);
|
|
212
|
+
/**
|
|
213
|
+
* Returns a new metamodel with the extension's kinds added. `base` is not modified.
|
|
214
|
+
*
|
|
215
|
+
* Throws `MetamodelError` when a kind name isn't namespaced, uses the reserved `istar.`
|
|
216
|
+
* prefix, collides with an existing kind or piStar type, or refers to an unknown kind.
|
|
217
|
+
*/
|
|
218
|
+
function extendMetamodel(base, extension) {
|
|
219
|
+
const where = `extension "${extension.name}"`;
|
|
220
|
+
if (!extension.name || typeof extension.name !== "string") throw new MetamodelError("an extension needs a name");
|
|
221
|
+
if (base.extensions.includes(extension.name)) throw new MetamodelError(`${where} is already applied`);
|
|
222
|
+
const elements = new Map(base.elements);
|
|
223
|
+
const links = new Map(base.links);
|
|
224
|
+
const elementsByType = new Map(base.elementsByPistarType);
|
|
225
|
+
const linksByType = new Map(base.linksByPistarType);
|
|
226
|
+
const claimName = (kind, what) => {
|
|
227
|
+
if (typeof kind !== "string" || !KIND_NAME.test(kind)) throw new MetamodelError(`${where}: ${what} kind ${JSON.stringify(kind)} must be namespaced, e.g. "${extension.name}.Name"`);
|
|
228
|
+
if (kind.startsWith(`istar.`)) throw new MetamodelError(`${where}: ${what} kind "${kind}" uses the reserved "${ISTAR_PREFIX}." prefix; name it "${extension.name}.${shortName(kind)}" and set pistarType: "${kind}" to read files that use it`);
|
|
229
|
+
if (elements.has(kind) || links.has(kind)) throw new MetamodelError(`${where}: kind "${kind}" already exists`);
|
|
230
|
+
};
|
|
231
|
+
const claimType = (type, kind) => {
|
|
232
|
+
if (typeof type !== "string" || type === "") throw new MetamodelError(`${where}: kind "${kind}" has an empty pistarType`);
|
|
233
|
+
if (elementsByType.has(type) || linksByType.has(type) || elements.has(type) || links.has(type)) throw new MetamodelError(`${where}: pistarType "${type}" of kind "${kind}" is already used by another kind`);
|
|
234
|
+
};
|
|
235
|
+
for (const declaration of extension.elements ?? []) {
|
|
236
|
+
const { kind } = declaration;
|
|
237
|
+
claimName(kind, "element");
|
|
238
|
+
let parent;
|
|
239
|
+
if (declaration.behavesLike !== void 0) {
|
|
240
|
+
parent = elements.get(declaration.behavesLike);
|
|
241
|
+
if (!parent) throw new MetamodelError(`${where}: "${kind}" behaves like unknown element kind "${declaration.behavesLike}"`);
|
|
242
|
+
}
|
|
243
|
+
const category = declaration.category ?? parent?.category;
|
|
244
|
+
if (category !== "actor" && category !== "node") throw new MetamodelError(`${where}: element kind "${kind}" needs a category ("node" or "actor") or behavesLike`);
|
|
245
|
+
if (parent && parent.category !== category) throw new MetamodelError(`${where}: "${kind}" is a ${category} but behaves like "${parent.kind}", a ${parent.category}`);
|
|
246
|
+
const type = declaration.pistarType ?? kind;
|
|
247
|
+
claimType(type, kind);
|
|
248
|
+
const baseInfo = parent?.info ?? {
|
|
249
|
+
canBeInnerElement: true,
|
|
250
|
+
canBeDependum: true,
|
|
251
|
+
canBeOnPaper: false
|
|
252
|
+
};
|
|
253
|
+
const definition = {
|
|
254
|
+
kind,
|
|
255
|
+
category,
|
|
256
|
+
label: declaration.label ?? shortName(kind),
|
|
257
|
+
size: declaration.size ?? parent?.size ?? (category === "actor" ? {
|
|
258
|
+
width: 200,
|
|
259
|
+
height: 120
|
|
260
|
+
} : {
|
|
261
|
+
width: 90,
|
|
262
|
+
height: 55
|
|
263
|
+
}),
|
|
264
|
+
...category === "node" ? { info: {
|
|
265
|
+
...baseInfo,
|
|
266
|
+
...declaration.info
|
|
267
|
+
} } : {},
|
|
268
|
+
...parent ? { behavesLike: parent.kind } : {},
|
|
269
|
+
pistarType: type,
|
|
270
|
+
extension: extension.name
|
|
271
|
+
};
|
|
272
|
+
elements.set(kind, definition);
|
|
273
|
+
elementsByType.set(type, kind);
|
|
274
|
+
}
|
|
275
|
+
for (const declaration of extension.links ?? []) {
|
|
276
|
+
const { kind } = declaration;
|
|
277
|
+
claimName(kind, "link");
|
|
278
|
+
if (declaration.behavesLike === void 0 === (declaration.rules === void 0)) throw new MetamodelError(`${where}: link kind "${kind}" needs exactly one of behavesLike or rules`);
|
|
279
|
+
let parent;
|
|
280
|
+
if (declaration.behavesLike !== void 0) {
|
|
281
|
+
parent = links.get(declaration.behavesLike);
|
|
282
|
+
if (!parent) throw new MetamodelError(`${where}: "${kind}" behaves like unknown link kind "${declaration.behavesLike}"`);
|
|
283
|
+
}
|
|
284
|
+
const category = declaration.category ?? parent?.category ?? "node";
|
|
285
|
+
if (category !== "node" && category !== "actor" && category !== "dependency") throw new MetamodelError(`${where}: link kind "${kind}" has an unknown category "${category}"`);
|
|
286
|
+
if (parent && parent.category !== category) throw new MetamodelError(`${where}: "${kind}" is a ${category} link but behaves like "${parent.kind}", a ${parent.category} link`);
|
|
287
|
+
if (category === "dependency" && !parent) throw new MetamodelError(`${where}: dependency link kind "${kind}" must behave like a dependency link (its dependum makes declarative rules ambiguous)`);
|
|
288
|
+
if (declaration.rules) for (const [side, selectors] of [["sources", declaration.rules.sources], ["targets", declaration.rules.targets]]) {
|
|
289
|
+
if (!Array.isArray(selectors) || selectors.length === 0) throw new MetamodelError(`${where}: link kind "${kind}" needs at least one of ${side}`);
|
|
290
|
+
for (const selector of selectors) if (!CATEGORY_SELECTORS.has(selector) && !elements.has(selector)) throw new MetamodelError(`${where}: ${side} of "${kind}" names unknown element kind "${selector}"`);
|
|
291
|
+
}
|
|
292
|
+
const type = declaration.pistarType ?? kind;
|
|
293
|
+
claimType(type, kind);
|
|
294
|
+
const definition = {
|
|
295
|
+
kind,
|
|
296
|
+
category,
|
|
297
|
+
label: declaration.label ?? shortName(kind),
|
|
298
|
+
info: declaration.info ?? parent?.info ?? {},
|
|
299
|
+
...parent ? { behavesLike: parent.kind } : {},
|
|
300
|
+
...declaration.rules ? { rules: declaration.rules } : {},
|
|
301
|
+
...declaration.check ? { check: declaration.check } : {},
|
|
302
|
+
pistarType: type,
|
|
303
|
+
extension: extension.name
|
|
304
|
+
};
|
|
305
|
+
links.set(kind, definition);
|
|
306
|
+
linksByType.set(type, kind);
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
name: `${base.name}+${extension.name}`,
|
|
310
|
+
extensions: [...base.extensions, extension.name],
|
|
311
|
+
elements,
|
|
312
|
+
links,
|
|
313
|
+
elementsByPistarType: elementsByType,
|
|
314
|
+
linksByPistarType: linksByType
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function elementKindDefinition(metamodel, kind) {
|
|
318
|
+
return metamodel.elements.get(kind);
|
|
319
|
+
}
|
|
320
|
+
function linkKindDefinition(metamodel, kind) {
|
|
321
|
+
return metamodel.links.get(kind);
|
|
322
|
+
}
|
|
323
|
+
/** True for actor-category kinds (Actor, Agent, Role and extensions behaving like them). */
|
|
324
|
+
function isActorKindIn(metamodel, kind) {
|
|
325
|
+
return metamodel.elements.get(kind)?.category === "actor";
|
|
326
|
+
}
|
|
327
|
+
/** True for node-category kinds (intentional elements, built-in or extended). */
|
|
328
|
+
function isNodeKindIn(metamodel, kind) {
|
|
329
|
+
return metamodel.elements.get(kind)?.category === "node";
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* The built-in (or rule-carrying) kind an element kind ultimately behaves like: `istar.Task`
|
|
333
|
+
* for a Planning that behaves like a Task; the kind itself when it behaves like nothing.
|
|
334
|
+
*/
|
|
335
|
+
function effectiveElementKind(metamodel, kind) {
|
|
336
|
+
let current = metamodel.elements.get(kind);
|
|
337
|
+
const seen = /* @__PURE__ */ new Set();
|
|
338
|
+
while (current?.behavesLike !== void 0 && !seen.has(current.kind)) {
|
|
339
|
+
seen.add(current.kind);
|
|
340
|
+
const next = metamodel.elements.get(current.behavesLike);
|
|
341
|
+
if (!next) break;
|
|
342
|
+
current = next;
|
|
343
|
+
}
|
|
344
|
+
return current?.kind ?? kind;
|
|
345
|
+
}
|
|
346
|
+
/** Like `effectiveElementKind`, for link kinds. */
|
|
347
|
+
function effectiveLinkKind(metamodel, kind) {
|
|
348
|
+
let current = metamodel.links.get(kind);
|
|
349
|
+
const seen = /* @__PURE__ */ new Set();
|
|
350
|
+
while (current?.behavesLike !== void 0 && !seen.has(current.kind)) {
|
|
351
|
+
seen.add(current.kind);
|
|
352
|
+
const next = metamodel.links.get(current.behavesLike);
|
|
353
|
+
if (!next) break;
|
|
354
|
+
current = next;
|
|
355
|
+
}
|
|
356
|
+
return current?.kind ?? kind;
|
|
357
|
+
}
|
|
358
|
+
/** True when `kind` is `ancestor` or (transitively) behaves like it. */
|
|
359
|
+
function elementBehavesLike(metamodel, kind, ancestor) {
|
|
360
|
+
let current = kind;
|
|
361
|
+
const seen = /* @__PURE__ */ new Set();
|
|
362
|
+
while (current !== void 0 && !seen.has(current)) {
|
|
363
|
+
if (current === ancestor) return true;
|
|
364
|
+
seen.add(current);
|
|
365
|
+
current = metamodel.elements.get(current)?.behavesLike;
|
|
366
|
+
}
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
//#endregion
|
|
140
370
|
//#region src/model.ts
|
|
141
371
|
/** Tool identifier written by piStar 2.1.0, whose file format this library emulates. */
|
|
142
372
|
const PISTAR_TOOL = "pistar.2.1.0";
|
|
@@ -145,20 +375,66 @@ const DEFAULT_DIAGRAM = {
|
|
|
145
375
|
width: 2e3,
|
|
146
376
|
height: 1300
|
|
147
377
|
};
|
|
148
|
-
|
|
149
|
-
|
|
378
|
+
/**
|
|
379
|
+
* The metamodel of each model that isn't iStar 2.0. Kept beside the model rather than in it,
|
|
380
|
+
* so a model's shape (and `JSON.stringify`, deep equality, snapshots) is the same as before
|
|
381
|
+
* metamodels existed. Operations and the store carry it to every model they derive.
|
|
382
|
+
*/
|
|
383
|
+
const metamodels = /* @__PURE__ */ new WeakMap();
|
|
384
|
+
/**
|
|
385
|
+
* Associates `metamodel` with `model` and returns the same model, typed for it. Use when a
|
|
386
|
+
* model was built without the library's operations (e.g. with an object spread), which loses
|
|
387
|
+
* the association.
|
|
388
|
+
*/
|
|
389
|
+
function withMetamodel(model, metamodel) {
|
|
390
|
+
if (metamodel === ISTAR_2_0) metamodels.delete(model);
|
|
391
|
+
else metamodels.set(model, metamodel);
|
|
392
|
+
return model;
|
|
393
|
+
}
|
|
394
|
+
/** Gives `to` the metamodel of `from` (used for every model an operation derives). */
|
|
395
|
+
function inheritMetamodel(from, to) {
|
|
396
|
+
const metamodel = metamodels.get(from);
|
|
397
|
+
if (metamodel && from !== to) metamodels.set(to, metamodel);
|
|
398
|
+
return to;
|
|
399
|
+
}
|
|
400
|
+
function createEmptyModel(diagram = DEFAULT_DIAGRAM, options = {}) {
|
|
401
|
+
const model = {
|
|
150
402
|
elements: /* @__PURE__ */ new Map(),
|
|
151
403
|
links: /* @__PURE__ */ new Map(),
|
|
152
404
|
diagram,
|
|
153
405
|
tool: PISTAR_TOOL,
|
|
154
406
|
istar: "2.0"
|
|
155
407
|
};
|
|
408
|
+
return options.metamodel ? withMetamodel(model, options.metamodel) : model;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* The metamodel a model was read or created with (see `parsePistar`, `createEmptyModel`), or
|
|
412
|
+
* iStar 2.0. Constraints, operations and serialization use it unless given another one.
|
|
413
|
+
*/
|
|
414
|
+
function metamodelOf(model) {
|
|
415
|
+
return metamodels.get(model) ?? ISTAR_2_0;
|
|
156
416
|
}
|
|
417
|
+
/**
|
|
418
|
+
* True for iStar 2.0 actors (Actor, Agent, Role). One argument, so it is safe as an array
|
|
419
|
+
* callback (`elements.filter(isActor)`). For extended actor kinds use `isActorIn(metamodel)`.
|
|
420
|
+
*/
|
|
157
421
|
function isActor(element) {
|
|
158
|
-
return element !== void 0 &&
|
|
422
|
+
return element !== void 0 && isActorKindIn(ISTAR_2_0, element.kind);
|
|
159
423
|
}
|
|
424
|
+
/** True for iStar 2.0 intentional elements. For extended kinds use `isNodeIn(metamodel)`. */
|
|
160
425
|
function isNode(element) {
|
|
161
|
-
return element !== void 0 && !
|
|
426
|
+
return element !== void 0 && !isActorKindIn(ISTAR_2_0, element.kind);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* A predicate for actor-category elements of `metamodel`, extended actor kinds included:
|
|
430
|
+
* `elements.filter(isActorIn(metamodelOf(model)))`.
|
|
431
|
+
*/
|
|
432
|
+
function isActorIn(metamodel) {
|
|
433
|
+
return (element) => element !== void 0 && isActorKindIn(metamodel, element.kind);
|
|
434
|
+
}
|
|
435
|
+
/** A predicate for node-category elements of `metamodel`, extended node kinds included. */
|
|
436
|
+
function isNodeIn(metamodel) {
|
|
437
|
+
return (element) => element !== void 0 && metamodel.elements.get(element.kind)?.category === "node";
|
|
162
438
|
}
|
|
163
439
|
/** Inner elements of an actor, in model order. */
|
|
164
440
|
function childrenOf(model, actorId) {
|
|
@@ -174,10 +450,11 @@ function linksOf(model, elementId) {
|
|
|
174
450
|
}
|
|
175
451
|
/** For a dependum, the two halves of its dependency: depender → dependum → dependee. */
|
|
176
452
|
function dependencyLinksOf(model, dependumId) {
|
|
453
|
+
const metamodel = metamodelOf(model);
|
|
177
454
|
let inbound;
|
|
178
455
|
let outbound;
|
|
179
456
|
for (const link of model.links.values()) {
|
|
180
|
-
if (link.kind !== "
|
|
457
|
+
if (metamodel.links.get(link.kind)?.category !== "dependency") continue;
|
|
181
458
|
if (link.target === dependumId && !inbound) inbound = link;
|
|
182
459
|
if (link.source === dependumId && !outbound) outbound = link;
|
|
183
460
|
}
|
|
@@ -199,17 +476,41 @@ function fail(code, reason) {
|
|
|
199
476
|
/**
|
|
200
477
|
* Checks whether a link of `kind` may be added from `source` to `target` in `model`.
|
|
201
478
|
*
|
|
202
|
-
* For
|
|
203
|
-
*
|
|
479
|
+
* For dependency links, `source` is the depender and `target` the dependee (an actor or one of
|
|
480
|
+
* its inner elements); the dependum sits between them and is not part of the check.
|
|
481
|
+
*
|
|
482
|
+
* Extended kinds (see `extendMetamodel`) are checked with the rules of the kind they behave
|
|
483
|
+
* like, or with their declarative rules, then with their extra predicate.
|
|
204
484
|
*/
|
|
205
|
-
function canLink(model, source, target, kind) {
|
|
206
|
-
const
|
|
207
|
-
const
|
|
485
|
+
function canLink(model, source, target, kind, options = {}) {
|
|
486
|
+
const any = model;
|
|
487
|
+
const metamodel = options.metamodel ?? metamodelOf(model);
|
|
488
|
+
const s = resolve(any, source);
|
|
489
|
+
const t = resolve(any, target);
|
|
208
490
|
if (!s) return fail("unknown-element", `unknown source element "${refId(source)}"`);
|
|
209
491
|
if (!t) return fail("unknown-element", `unknown target element "${refId(target)}"`);
|
|
210
|
-
const
|
|
211
|
-
if (!
|
|
212
|
-
return
|
|
492
|
+
const definition = metamodel.links.get(kind);
|
|
493
|
+
if (!definition) return fail("unknown-link-kind", `unknown link kind "${String(kind)}"`);
|
|
494
|
+
return checkWith(new Graph(any, metamodel), definition, s, t);
|
|
495
|
+
}
|
|
496
|
+
/** Runs the rule a link kind resolves to, then every extra predicate along its chain. */
|
|
497
|
+
function checkWith(graph, definition, source, target) {
|
|
498
|
+
const chain = [];
|
|
499
|
+
for (let current = definition; current && !chain.includes(current); current = current.behavesLike ? graph.metamodel.links.get(current.behavesLike) : void 0) chain.push(current);
|
|
500
|
+
const root = chain[chain.length - 1];
|
|
501
|
+
const builtIn = CHECKS[root.kind];
|
|
502
|
+
let result;
|
|
503
|
+
if (root.rules) result = declarativeCheck(graph, definition, root.rules, root.kind, source, target);
|
|
504
|
+
else if (builtIn) result = builtIn(graph, source, target);
|
|
505
|
+
else result = fail("unknown-link-kind", `link kind "${definition.kind}" has no rules`);
|
|
506
|
+
if (!result.ok) return result;
|
|
507
|
+
const context = graph.context(definition.kind, source, target);
|
|
508
|
+
for (const step of chain.toReversed()) {
|
|
509
|
+
if (!step.check) continue;
|
|
510
|
+
const extra = step.check(context);
|
|
511
|
+
if (!extra.ok) return extra;
|
|
512
|
+
}
|
|
513
|
+
return OK;
|
|
213
514
|
}
|
|
214
515
|
function refId(ref) {
|
|
215
516
|
return typeof ref === "string" ? ref : ref.id;
|
|
@@ -217,11 +518,20 @@ function refId(ref) {
|
|
|
217
518
|
function resolve(model, ref) {
|
|
218
519
|
return model.elements.get(refId(ref));
|
|
219
520
|
}
|
|
220
|
-
/**
|
|
521
|
+
/**
|
|
522
|
+
* The graph queries used by upstream constraints (`istar.isThereLinkBetween`, etc.). Link kinds
|
|
523
|
+
* are compared by what they behave like, so an extension link behaving like an OR-refinement
|
|
524
|
+
* counts as one for the iStar 2.0 rules (no mixing with AND, no refining a depender…).
|
|
525
|
+
*/
|
|
221
526
|
var Graph = class {
|
|
222
527
|
model;
|
|
223
|
-
|
|
528
|
+
metamodel;
|
|
529
|
+
constructor(model, metamodel) {
|
|
224
530
|
this.model = model;
|
|
531
|
+
this.metamodel = metamodel;
|
|
532
|
+
}
|
|
533
|
+
matches(link, kind) {
|
|
534
|
+
return link.kind === kind || effectiveLinkKind(this.metamodel, link.kind) === kind;
|
|
225
535
|
}
|
|
226
536
|
/**
|
|
227
537
|
* True when a link (optionally of `kind`) already connects the two elements, in either
|
|
@@ -229,47 +539,76 @@ var Graph = class {
|
|
|
229
539
|
*/
|
|
230
540
|
isThereLinkBetween(a, b, kind) {
|
|
231
541
|
for (const link of this.model.links.values()) {
|
|
232
|
-
if (kind && link
|
|
542
|
+
if (kind && !this.matches(link, kind)) continue;
|
|
543
|
+
if (link.source === a.id && link.target === b.id || link.source === b.id && link.target === a.id) return true;
|
|
544
|
+
}
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
/** Like `isThereLinkBetween`, counting only links of exactly `kind`. */
|
|
548
|
+
isThereLinkOfKindBetween(a, b, kind) {
|
|
549
|
+
for (const link of this.model.links.values()) {
|
|
550
|
+
if (link.kind !== kind) continue;
|
|
233
551
|
if (link.source === a.id && link.target === b.id || link.source === b.id && link.target === a.id) return true;
|
|
234
552
|
}
|
|
235
553
|
return false;
|
|
236
554
|
}
|
|
237
555
|
isSourceOfType(element, kind) {
|
|
238
|
-
for (const link of this.model.links.values()) if (link.
|
|
556
|
+
for (const link of this.model.links.values()) if (link.source === element.id && this.matches(link, kind)) return true;
|
|
239
557
|
return false;
|
|
240
558
|
}
|
|
241
559
|
isTargetOfType(element, kind) {
|
|
242
|
-
for (const link of this.model.links.values()) if (link.
|
|
560
|
+
for (const link of this.model.links.values()) if (link.target === element.id && this.matches(link, kind)) return true;
|
|
243
561
|
return false;
|
|
244
562
|
}
|
|
563
|
+
/** The kind the iStar 2.0 rules see: built-in kinds as they are, extensions as what they behave like. */
|
|
564
|
+
kindOf(element) {
|
|
565
|
+
return effectiveElementKind(this.metamodel, element.kind);
|
|
566
|
+
}
|
|
567
|
+
isActor(element) {
|
|
568
|
+
return this.metamodel.elements.get(element.kind)?.category === "actor";
|
|
569
|
+
}
|
|
570
|
+
context(kind, source, target) {
|
|
571
|
+
return {
|
|
572
|
+
model: this.model,
|
|
573
|
+
metamodel: this.metamodel,
|
|
574
|
+
kind,
|
|
575
|
+
source,
|
|
576
|
+
target,
|
|
577
|
+
isThereLinkBetween: (a, b, k) => this.isThereLinkBetween(a, b, k),
|
|
578
|
+
isSourceOfType: (e, k) => this.isSourceOfType(e, k),
|
|
579
|
+
isTargetOfType: (e, k) => this.isTargetOfType(e, k)
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
const is = (graph, element, ...shortNames) => {
|
|
584
|
+
const kind = graph.kindOf(element);
|
|
585
|
+
return shortNames.some((name) => kind === `istar.${name}`);
|
|
245
586
|
};
|
|
246
|
-
const is = (element, ...shortNames) => shortNames.some((name) => element.kind === `istar.${name}`);
|
|
247
|
-
const isKindOfActor = (element) => isActorKind(element.kind);
|
|
248
587
|
const isDependum = (element) => element.isDependum === true;
|
|
249
588
|
const isALink = (graph, source, target) => {
|
|
250
|
-
if (!is(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).");
|
|
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).");
|
|
251
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).");
|
|
252
|
-
if (!is(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).");
|
|
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).");
|
|
253
592
|
if (source.id === target.id) return fail("self-link", "you cannot make Is-A links from an actor onto itself.");
|
|
254
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).");
|
|
255
594
|
return OK;
|
|
256
595
|
};
|
|
257
596
|
const participatesInLink = (graph, source, target) => {
|
|
258
|
-
if (!
|
|
259
|
-
if (!
|
|
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)");
|
|
260
599
|
if (source.id === target.id) return fail("self-link", "you cannot make a Participates-In link from an actor onto itself.");
|
|
261
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).");
|
|
262
601
|
return OK;
|
|
263
602
|
};
|
|
264
603
|
/** The actor an element belongs to: itself for actors, its parent for inner elements. */
|
|
265
|
-
function owningActorId(element) {
|
|
266
|
-
return
|
|
604
|
+
function owningActorId(graph, element) {
|
|
605
|
+
return graph.isActor(element) ? element.id : element.parent;
|
|
267
606
|
}
|
|
268
607
|
const dependencyLink = (graph, source, target) => {
|
|
269
608
|
if (source.id === target.id) return fail("self-link", "a Dependency link cannot link an element onto itself");
|
|
270
609
|
if (isDependum(source)) return fail("dependum-endpoint", "a Dependency link cannot start from a dependum");
|
|
271
610
|
if (isDependum(target)) return fail("dependum-endpoint", "a Dependency link cannot end in a dependum");
|
|
272
|
-
if (owningActorId(source) === owningActorId(target)) return fail("same-actor", "a Dependency link must involve two different actors (iStar 2.0 Guide, Page 14)");
|
|
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)");
|
|
273
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.");
|
|
274
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.");
|
|
275
614
|
return OK;
|
|
@@ -277,8 +616,8 @@ const dependencyLink = (graph, source, target) => {
|
|
|
277
616
|
function refinementLink(label) {
|
|
278
617
|
const other = label === "AND" ? "istar.OrRefinementLink" : "istar.AndRefinementLink";
|
|
279
618
|
return (graph, source, target) => {
|
|
280
|
-
if (!is(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)`);
|
|
281
|
-
if (!is(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)`);
|
|
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)`);
|
|
282
621
|
if (source.id === target.id) return fail("self-link", `you cannot make an ${label}-refinement link from an element onto itself`);
|
|
283
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)`);
|
|
284
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)`);
|
|
@@ -289,8 +628,8 @@ function refinementLink(label) {
|
|
|
289
628
|
};
|
|
290
629
|
}
|
|
291
630
|
const neededByLink = (graph, source, target) => {
|
|
292
|
-
if (!is(source, "Resource")) return fail("invalid-source", "the source of a Needed-By link must be a Resource (iStar 2.0 Guide, Table 1)");
|
|
293
|
-
if (!is(target, "Task")) return fail("invalid-target", "the target of a Needed-By link must be a Task (iStar 2.0 Guide, Table 1)");
|
|
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)");
|
|
294
633
|
if (source.id === target.id) return fail("self-link", "you cannot make a Needed-By link from an element onto itself");
|
|
295
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)");
|
|
296
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)");
|
|
@@ -298,8 +637,8 @@ const neededByLink = (graph, source, target) => {
|
|
|
298
637
|
return OK;
|
|
299
638
|
};
|
|
300
639
|
const contributionLink = (graph, source, target) => {
|
|
301
|
-
if (!is(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)");
|
|
302
|
-
if (!is(target, "Quality")) return fail("invalid-target", "the target of a Contribution link must be a Quality (iStar 2.0 Guide, Table 1)");
|
|
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)");
|
|
303
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)");
|
|
304
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)");
|
|
305
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)");
|
|
@@ -309,8 +648,8 @@ const contributionLink = (graph, source, target) => {
|
|
|
309
648
|
return OK;
|
|
310
649
|
};
|
|
311
650
|
const qualificationLink = (graph, source, target) => {
|
|
312
|
-
if (!is(source, "Quality")) return fail("invalid-source", "the source of a Qualification link must be a Quality (iStar 2.0 Guide, Table 1)");
|
|
313
|
-
if (!is(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)");
|
|
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)");
|
|
314
653
|
if (source.id === target.id) return fail("self-link", "you cannot make a Qualification link from an element onto itself");
|
|
315
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)");
|
|
316
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)");
|
|
@@ -318,6 +657,42 @@ const qualificationLink = (graph, source, target) => {
|
|
|
318
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)");
|
|
319
658
|
return OK;
|
|
320
659
|
};
|
|
660
|
+
/** The words used for a list of kind selectors in messages: "a Goal or a Task". */
|
|
661
|
+
function describe(graph, selectors) {
|
|
662
|
+
const names = selectors.map((selector) => {
|
|
663
|
+
if (selector === "*") return "any element";
|
|
664
|
+
if (selector === "node") return "an intentional element";
|
|
665
|
+
if (selector === "actor") return "an actor";
|
|
666
|
+
const label = graph.metamodel.elements.get(selector)?.label ?? selector;
|
|
667
|
+
return /^[aeiou]/i.test(label) ? `an ${label}` : `a ${label}`;
|
|
668
|
+
});
|
|
669
|
+
return names.length <= 2 ? names.join(" or ") : `${names.slice(0, -1).join(", ")} or ${names[names.length - 1]}`;
|
|
670
|
+
}
|
|
671
|
+
function matchesSelector(graph, element, selector) {
|
|
672
|
+
if (selector === "*") return true;
|
|
673
|
+
const category = graph.metamodel.elements.get(element.kind)?.category;
|
|
674
|
+
if (selector === "node" || selector === "actor") return category === selector;
|
|
675
|
+
return elementBehavesLike(graph.metamodel, element.kind, selector);
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Declarative rules of an extension link kind, checked in the order of the iStar 2.0 node-link
|
|
679
|
+
* rules (and of piStar-ext's generated `isValid`): source kind, target kind, self link,
|
|
680
|
+
* dependum, same actor, duplicates.
|
|
681
|
+
*/
|
|
682
|
+
function declarativeCheck(graph, definition, rules, rulesKind, source, target) {
|
|
683
|
+
const name = definition.label;
|
|
684
|
+
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`);
|
|
688
|
+
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)`);
|
|
691
|
+
}
|
|
692
|
+
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`);
|
|
694
|
+
return OK;
|
|
695
|
+
}
|
|
321
696
|
const CHECKS = {
|
|
322
697
|
"istar.IsALink": isALink,
|
|
323
698
|
"istar.ParticipatesInLink": participatesInLink,
|
|
@@ -328,42 +703,42 @@ const CHECKS = {
|
|
|
328
703
|
"istar.ContributionLink": contributionLink,
|
|
329
704
|
"istar.QualificationLink": qualificationLink
|
|
330
705
|
};
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
* order, each against the graph built so far. Invalid links are reported, not removed.
|
|
335
|
-
*/
|
|
336
|
-
function validateModel(model) {
|
|
706
|
+
function validateModel(model, options) {
|
|
707
|
+
const metamodel = (typeof options === "object" && options !== null ? options.metamodel : void 0) ?? metamodelOf(model);
|
|
708
|
+
const any = model;
|
|
337
709
|
const issues = [];
|
|
338
710
|
const accepted = /* @__PURE__ */ new Map();
|
|
339
711
|
const view = () => ({
|
|
340
|
-
...
|
|
712
|
+
...any,
|
|
341
713
|
links: accepted
|
|
342
714
|
});
|
|
343
|
-
const
|
|
344
|
-
|
|
715
|
+
const check = (source, target, kind) => canLink(view(), source, target, kind, { metamodel });
|
|
716
|
+
const isDependency = (link) => metamodel.links.get(link.kind)?.category === "dependency";
|
|
717
|
+
const report = (linkId, kind, source, target, result) => {
|
|
718
|
+
if (!result.ok) issues.push({
|
|
345
719
|
linkId,
|
|
346
720
|
kind,
|
|
347
721
|
source,
|
|
348
722
|
target,
|
|
349
|
-
code:
|
|
350
|
-
message:
|
|
723
|
+
code: result.code,
|
|
724
|
+
message: result.reason
|
|
351
725
|
});
|
|
352
726
|
};
|
|
353
|
-
const dependencyLinks = [...
|
|
354
|
-
for (const dependum of
|
|
727
|
+
const dependencyLinks = [...any.links.values()].filter(isDependency);
|
|
728
|
+
for (const dependum of any.elements.values()) {
|
|
355
729
|
if (!dependum.isDependum) continue;
|
|
356
730
|
const inbound = dependencyLinks.find((l) => l.target === dependum.id);
|
|
357
731
|
const outbound = dependencyLinks.find((l) => l.source === dependum.id);
|
|
358
732
|
const depender = inbound?.source ?? dependum.dependency?.source;
|
|
359
733
|
const dependee = outbound?.target ?? dependum.dependency?.target;
|
|
360
|
-
|
|
734
|
+
const kind = inbound?.kind ?? outbound?.kind ?? "istar.DependencyLink";
|
|
735
|
+
if (depender !== void 0 && dependee !== void 0) report(inbound?.id ?? dependum.id, kind, depender, dependee, check(depender, dependee, kind));
|
|
361
736
|
if (inbound) accepted.set(inbound.id, inbound);
|
|
362
737
|
if (outbound) accepted.set(outbound.id, outbound);
|
|
363
738
|
}
|
|
364
|
-
for (const link of
|
|
365
|
-
if (link
|
|
366
|
-
if (
|
|
739
|
+
for (const link of any.links.values()) {
|
|
740
|
+
if (isDependency(link)) continue;
|
|
741
|
+
if (any.elements.has(link.source) && any.elements.has(link.target)) report(link.id, link.kind, link.source, link.target, check(link.source, link.target, link.kind));
|
|
367
742
|
else report(link.id, link.kind, link.source, link.target, {
|
|
368
743
|
ok: false,
|
|
369
744
|
code: "unknown-element",
|
|
@@ -378,6 +753,10 @@ function validateModel(model) {
|
|
|
378
753
|
var PistarParseError = class extends Error {
|
|
379
754
|
name = "PistarParseError";
|
|
380
755
|
};
|
|
756
|
+
/** Thrown by `toPistar` for a kind its metamodel doesn't know (rather than writing it wrong). */
|
|
757
|
+
var PistarWriteError = class extends Error {
|
|
758
|
+
name = "PistarWriteError";
|
|
759
|
+
};
|
|
381
760
|
const layouts = /* @__PURE__ */ new WeakMap();
|
|
382
761
|
/** The key order recorded when `model` was parsed, if it came from `parsePistar`. */
|
|
383
762
|
function getSourceLayout(model) {
|
|
@@ -446,13 +825,9 @@ function asArray(value, where) {
|
|
|
446
825
|
if (!Array.isArray(value)) throw new PistarParseError(`"${where}" must be an array`);
|
|
447
826
|
return value;
|
|
448
827
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
* Unlike the piStar tool, elements with `x` or `y` equal to 0 are kept: upstream's
|
|
453
|
-
* `if (element.x && element.y)` silently drops them, which looks unintended.
|
|
454
|
-
*/
|
|
455
|
-
function parsePistar(input) {
|
|
828
|
+
function parsePistar(input, options = {}) {
|
|
829
|
+
const metamodel = options.metamodel ?? ISTAR_2_0;
|
|
830
|
+
const categoryOf = (kind) => metamodel.elements.get(kind)?.category;
|
|
456
831
|
let json;
|
|
457
832
|
if (typeof input === "string") try {
|
|
458
833
|
json = JSON.parse(input);
|
|
@@ -475,11 +850,12 @@ function parsePistar(input) {
|
|
|
475
850
|
if (!isRecord(raw)) throw new PistarParseError(`${where}: expected an object`);
|
|
476
851
|
const { id, type } = raw;
|
|
477
852
|
if (typeof id !== "string" || id === "") throw new PistarParseError(`${where}: missing "id"`);
|
|
478
|
-
|
|
853
|
+
const kind = typeof type === "string" ? metamodel.elementsByPistarType.get(type) : void 0;
|
|
854
|
+
if (kind === void 0) throw new PistarParseError(`${where} (${id}): unknown element type ${JSON.stringify(type)}`);
|
|
479
855
|
if (elements.has(id) || links.has(id)) throw new PistarParseError(`${where}: duplicated id "${id}"`);
|
|
480
856
|
const element = {
|
|
481
857
|
id,
|
|
482
|
-
kind
|
|
858
|
+
kind,
|
|
483
859
|
name: typeof raw.text === "string" ? raw.text : "",
|
|
484
860
|
x: typeof raw.x === "number" ? raw.x : 0,
|
|
485
861
|
y: typeof raw.y === "number" ? raw.y : 0,
|
|
@@ -493,9 +869,10 @@ function parsePistar(input) {
|
|
|
493
869
|
};
|
|
494
870
|
asArray(json.actors, "actors").forEach((raw, i) => {
|
|
495
871
|
const actor = readElement(raw, `actors[${i}]`, ACTOR_KEYS, {});
|
|
496
|
-
if (
|
|
872
|
+
if (categoryOf(actor.kind) !== "actor") throw new PistarParseError(`actors[${i}] (${actor.id}): ${actor.kind} is not an actor kind`);
|
|
497
873
|
asArray(raw.nodes, `actors[${i}].nodes`).forEach((node, j) => {
|
|
498
|
-
|
|
874
|
+
const child = readElement(node, `actors[${i}].nodes[${j}]`, ELEMENT_KEYS, { parent: actor.id });
|
|
875
|
+
if (categoryOf(child.kind) === "actor") throw new PistarParseError(`actors[${i}].nodes[${j}]: actors cannot be nested`);
|
|
499
876
|
});
|
|
500
877
|
});
|
|
501
878
|
asArray(json.orphans, "orphans").forEach((raw, i) => {
|
|
@@ -516,12 +893,13 @@ function parsePistar(input) {
|
|
|
516
893
|
if (!isRecord(raw)) throw new PistarParseError(`links[${i}]: expected an object`);
|
|
517
894
|
const { id, type, source, target } = raw;
|
|
518
895
|
if (typeof id !== "string" || id === "") throw new PistarParseError(`links[${i}]: missing "id"`);
|
|
519
|
-
|
|
896
|
+
const kind = typeof type === "string" ? metamodel.linksByPistarType.get(type) : void 0;
|
|
897
|
+
if (kind === void 0) throw new PistarParseError(`links[${i}] (${id}): unknown link type ${JSON.stringify(type)}`);
|
|
520
898
|
if (typeof source !== "string" || typeof target !== "string") throw new PistarParseError(`links[${i}] (${id}): missing "source" or "target"`);
|
|
521
899
|
if (elements.has(id) || links.has(id)) throw new PistarParseError(`links[${i}]: duplicated id "${id}"`);
|
|
522
900
|
links.set(id, {
|
|
523
901
|
id,
|
|
524
|
-
kind
|
|
902
|
+
kind,
|
|
525
903
|
source,
|
|
526
904
|
target,
|
|
527
905
|
...optional("name", typeof raw.name === "string" ? raw.name : void 0),
|
|
@@ -558,7 +936,7 @@ function parsePistar(input) {
|
|
|
558
936
|
keys: Object.keys(json),
|
|
559
937
|
display: Object.keys(display)
|
|
560
938
|
});
|
|
561
|
-
return model;
|
|
939
|
+
return withMetamodel(model, metamodel);
|
|
562
940
|
}
|
|
563
941
|
function optional(key, value) {
|
|
564
942
|
return value === void 0 ? {} : { [key]: value };
|
|
@@ -566,18 +944,25 @@ function optional(key, value) {
|
|
|
566
944
|
function formatDate(date) {
|
|
567
945
|
return date.toUTCString();
|
|
568
946
|
}
|
|
569
|
-
function
|
|
947
|
+
function pistarTypeOf(type, kind, metamodel) {
|
|
948
|
+
if (type === void 0) throw new PistarWriteError(`kind "${kind}" is not part of ${metamodel.name}; pass its metamodel (toPistar(model, { metamodel }) or withMetamodel)`);
|
|
949
|
+
return type;
|
|
950
|
+
}
|
|
951
|
+
function elementJson(element, metamodel) {
|
|
570
952
|
return {
|
|
571
953
|
id: element.id,
|
|
572
954
|
text: element.name,
|
|
573
|
-
type: element.kind,
|
|
955
|
+
type: pistarTypeOf(metamodel.elements.get(element.kind)?.pistarType, element.kind, metamodel),
|
|
574
956
|
x: element.x,
|
|
575
957
|
y: element.y,
|
|
576
958
|
...element.customProperties ? { customProperties: { ...element.customProperties } } : {}
|
|
577
959
|
};
|
|
578
960
|
}
|
|
579
961
|
/** Builds the save-file object for a model. `toPistar` is this plus `JSON.stringify`. */
|
|
580
|
-
function toPistarObject(
|
|
962
|
+
function toPistarObject(input, options = {}) {
|
|
963
|
+
const model = input;
|
|
964
|
+
const metamodel = options.metamodel ?? metamodelOf(input);
|
|
965
|
+
const isActorKind = (kind) => metamodel.elements.get(kind)?.category === "actor";
|
|
581
966
|
const actors = [];
|
|
582
967
|
const orphans = [];
|
|
583
968
|
const dependencies = [];
|
|
@@ -586,31 +971,31 @@ function toPistarObject(model, options = {}) {
|
|
|
586
971
|
let list = children.get(element.parent);
|
|
587
972
|
if (!list) children.set(element.parent, list = []);
|
|
588
973
|
list.push({
|
|
589
|
-
...elementJson(element),
|
|
974
|
+
...elementJson(element, metamodel),
|
|
590
975
|
...element.extra
|
|
591
976
|
});
|
|
592
977
|
}
|
|
593
978
|
for (const element of model.elements.values()) if (isActorKind(element.kind)) actors.push({
|
|
594
|
-
...elementJson(element),
|
|
979
|
+
...elementJson(element, metamodel),
|
|
595
980
|
nodes: children.get(element.id) ?? [],
|
|
596
981
|
...element.extra
|
|
597
982
|
});
|
|
598
983
|
else if (element.isDependum) {
|
|
599
984
|
const { inbound, outbound } = dependencyLinksOf(model, element.id);
|
|
600
985
|
dependencies.push({
|
|
601
|
-
...elementJson(element),
|
|
986
|
+
...elementJson(element, metamodel),
|
|
602
987
|
source: inbound?.source ?? element.dependency?.source ?? "",
|
|
603
988
|
target: outbound?.target ?? element.dependency?.target ?? "",
|
|
604
989
|
...element.extra
|
|
605
990
|
});
|
|
606
991
|
} else if (element.parent === void 0 || !model.elements.has(element.parent)) orphans.push({
|
|
607
|
-
...elementJson(element),
|
|
992
|
+
...elementJson(element, metamodel),
|
|
608
993
|
...element.extra
|
|
609
994
|
});
|
|
610
995
|
const links = [];
|
|
611
996
|
for (const link of model.links.values()) links.push({
|
|
612
997
|
id: link.id,
|
|
613
|
-
type: link.kind,
|
|
998
|
+
type: pistarTypeOf(metamodel.links.get(link.kind)?.pistarType, link.kind, metamodel),
|
|
614
999
|
source: link.source,
|
|
615
1000
|
target: link.target,
|
|
616
1001
|
...link.name !== void 0 ? { name: link.name } : {},
|
|
@@ -690,11 +1075,23 @@ const defaultIdGenerator = () => {
|
|
|
690
1075
|
return (ch === "x" ? r : r & 3 | 8).toString(16);
|
|
691
1076
|
});
|
|
692
1077
|
};
|
|
1078
|
+
function any(model) {
|
|
1079
|
+
return model;
|
|
1080
|
+
}
|
|
1081
|
+
function typed(model) {
|
|
1082
|
+
return model;
|
|
1083
|
+
}
|
|
1084
|
+
function metaOf(model) {
|
|
1085
|
+
return metamodelOf(model);
|
|
1086
|
+
}
|
|
1087
|
+
function isActorIn$1(model, kind) {
|
|
1088
|
+
return metaOf(model).elements.get(kind)?.category === "actor";
|
|
1089
|
+
}
|
|
693
1090
|
function derive(from, changes) {
|
|
694
|
-
return inheritSourceLayout(from, {
|
|
1091
|
+
return inheritMetamodel(from, inheritSourceLayout(from, {
|
|
695
1092
|
...from,
|
|
696
1093
|
...changes
|
|
697
|
-
});
|
|
1094
|
+
}));
|
|
698
1095
|
}
|
|
699
1096
|
function requireElement(model, id) {
|
|
700
1097
|
const element = model.elements.get(id);
|
|
@@ -712,16 +1109,20 @@ function withoutUndefined(value) {
|
|
|
712
1109
|
return out;
|
|
713
1110
|
}
|
|
714
1111
|
function addElement(model, input, ctx = {}) {
|
|
1112
|
+
const m = any(model);
|
|
1113
|
+
const metamodel = metaOf(m);
|
|
1114
|
+
const definition = metamodel.elements.get(input.kind);
|
|
1115
|
+
if (!definition) throw new ModelOperationError(`unknown element kind "${input.kind}" in ${metamodel.name}`);
|
|
715
1116
|
const id = input.id ?? (ctx.createId ?? defaultIdGenerator)();
|
|
716
|
-
if (
|
|
1117
|
+
if (m.elements.has(id) || m.links.has(id)) throw new ModelOperationError(`id "${id}" is already in use`);
|
|
717
1118
|
if (input.parent !== void 0) {
|
|
718
|
-
if (
|
|
719
|
-
if (!
|
|
1119
|
+
if (definition.category === "actor") throw new ModelOperationError("actors cannot be nested");
|
|
1120
|
+
if (!isActorIn$1(m, requireElement(m, input.parent).kind)) throw new ModelOperationError(`parent "${input.parent}" is not an actor`);
|
|
720
1121
|
}
|
|
721
1122
|
const element = withoutUndefined({
|
|
722
1123
|
id,
|
|
723
1124
|
kind: input.kind,
|
|
724
|
-
name: input.name ??
|
|
1125
|
+
name: input.name ?? definition.label,
|
|
725
1126
|
x: input.x,
|
|
726
1127
|
y: input.y,
|
|
727
1128
|
parent: input.parent,
|
|
@@ -729,7 +1130,7 @@ function addElement(model, input, ctx = {}) {
|
|
|
729
1130
|
display: input.display
|
|
730
1131
|
});
|
|
731
1132
|
return {
|
|
732
|
-
model: derive(
|
|
1133
|
+
model: typed(derive(m, { elements: new Map(m.elements).set(id, element) })),
|
|
733
1134
|
element
|
|
734
1135
|
};
|
|
735
1136
|
}
|
|
@@ -742,38 +1143,40 @@ function mergeDisplay(current, patch) {
|
|
|
742
1143
|
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
743
1144
|
}
|
|
744
1145
|
function updateElement(model, id, patch) {
|
|
745
|
-
const
|
|
1146
|
+
const m = any(model);
|
|
1147
|
+
const current = requireElement(m, id);
|
|
746
1148
|
const next = withoutUndefined({
|
|
747
1149
|
...current,
|
|
748
1150
|
name: patch.name ?? current.name,
|
|
749
1151
|
customProperties: patch.customProperties === void 0 ? current.customProperties : patch.customProperties ?? void 0,
|
|
750
1152
|
display: mergeDisplay(current.display, patch.display)
|
|
751
1153
|
});
|
|
752
|
-
return derive(
|
|
1154
|
+
return typed(derive(m, { elements: new Map(m.elements).set(id, next) }));
|
|
753
1155
|
}
|
|
754
1156
|
/**
|
|
755
1157
|
* Moves an element to an absolute position. Moving an actor shifts its inner elements by the
|
|
756
1158
|
* same offset.
|
|
757
1159
|
*/
|
|
758
1160
|
function moveElement(model, id, x, y) {
|
|
759
|
-
const
|
|
1161
|
+
const m = any(model);
|
|
1162
|
+
const current = requireElement(m, id);
|
|
760
1163
|
const dx = x - current.x;
|
|
761
1164
|
const dy = y - current.y;
|
|
762
1165
|
if (dx === 0 && dy === 0) return model;
|
|
763
|
-
const elements = new Map(
|
|
1166
|
+
const elements = new Map(m.elements);
|
|
764
1167
|
elements.set(id, {
|
|
765
1168
|
...current,
|
|
766
1169
|
x,
|
|
767
1170
|
y
|
|
768
1171
|
});
|
|
769
|
-
if (
|
|
770
|
-
for (const child of
|
|
1172
|
+
if (isActorIn$1(m, current.kind)) {
|
|
1173
|
+
for (const child of m.elements.values()) if (child.parent === id) elements.set(child.id, {
|
|
771
1174
|
...child,
|
|
772
1175
|
x: child.x + dx,
|
|
773
1176
|
y: child.y + dy
|
|
774
1177
|
});
|
|
775
1178
|
}
|
|
776
|
-
return derive(
|
|
1179
|
+
return typed(derive(m, { elements }));
|
|
777
1180
|
}
|
|
778
1181
|
/**
|
|
779
1182
|
* Moves an inner element into another actor (or out of any actor with `null`). The element
|
|
@@ -781,32 +1184,35 @@ function moveElement(model, id, x, y) {
|
|
|
781
1184
|
* them.
|
|
782
1185
|
*/
|
|
783
1186
|
function nestElement(model, id, parent) {
|
|
784
|
-
const
|
|
785
|
-
|
|
1187
|
+
const m = any(model);
|
|
1188
|
+
const current = requireElement(m, id);
|
|
1189
|
+
if (isActorIn$1(m, current.kind)) throw new ModelOperationError("actors cannot be nested");
|
|
786
1190
|
if (current.isDependum) throw new ModelOperationError("a dependum cannot be nested");
|
|
787
1191
|
if ((current.parent ?? null) === parent) return model;
|
|
788
|
-
if (parent !== null && !
|
|
789
|
-
const elements = new Map(
|
|
1192
|
+
if (parent !== null && !isActorIn$1(m, requireElement(m, parent).kind)) throw new ModelOperationError(`parent "${parent}" is not an actor`);
|
|
1193
|
+
const elements = new Map(m.elements);
|
|
790
1194
|
elements.delete(id);
|
|
791
1195
|
const { parent: _old, ...rest } = current;
|
|
792
1196
|
elements.set(id, parent === null ? rest : {
|
|
793
1197
|
...rest,
|
|
794
1198
|
parent
|
|
795
1199
|
});
|
|
796
|
-
return derive(
|
|
1200
|
+
return typed(derive(m, { elements }));
|
|
797
1201
|
}
|
|
798
1202
|
/**
|
|
799
1203
|
* Removes elements and everything that depends on them: inner elements of removed actors,
|
|
800
1204
|
* links touching removed elements, and the rest of any dependency that loses a part.
|
|
801
1205
|
*/
|
|
802
1206
|
function removeElements(model, ids) {
|
|
803
|
-
return removeCascade(model, new Set(ids), /* @__PURE__ */ new Set());
|
|
1207
|
+
return typed(removeCascade(any(model), new Set(ids), /* @__PURE__ */ new Set()));
|
|
804
1208
|
}
|
|
805
1209
|
function removeElement(model, id) {
|
|
806
|
-
requireElement(model, id);
|
|
1210
|
+
requireElement(any(model), id);
|
|
807
1211
|
return removeElements(model, [id]);
|
|
808
1212
|
}
|
|
809
1213
|
function removeCascade(model, elementIds, linkIds) {
|
|
1214
|
+
const metamodel = metaOf(model);
|
|
1215
|
+
const isDependencyHalf = (link) => metamodel.links.get(link.kind)?.category === "dependency";
|
|
810
1216
|
let changed = true;
|
|
811
1217
|
while (changed) {
|
|
812
1218
|
changed = false;
|
|
@@ -825,7 +1231,7 @@ function removeCascade(model, elementIds, linkIds) {
|
|
|
825
1231
|
}
|
|
826
1232
|
}
|
|
827
1233
|
for (const link of model.links.values()) {
|
|
828
|
-
if (!linkIds.has(link.id) || link
|
|
1234
|
+
if (!linkIds.has(link.id) || !isDependencyHalf(link)) continue;
|
|
829
1235
|
for (const end of [link.source, link.target]) if (model.elements.get(end)?.isDependum && !elementIds.has(end)) {
|
|
830
1236
|
elementIds.add(end);
|
|
831
1237
|
changed = true;
|
|
@@ -844,14 +1250,18 @@ function removeCascade(model, elementIds, linkIds) {
|
|
|
844
1250
|
}
|
|
845
1251
|
/** Adds a link between two elements if the iStar constraints allow it. */
|
|
846
1252
|
function connect(model, input, options = {}) {
|
|
847
|
-
|
|
1253
|
+
const m = any(model);
|
|
1254
|
+
const metamodel = metaOf(m);
|
|
1255
|
+
const kind = input.kind;
|
|
1256
|
+
const definition = metamodel.links.get(kind);
|
|
1257
|
+
if (definition?.category === "dependency") throw new ModelOperationError("use addDependency to create dependencies");
|
|
848
1258
|
let { source, target } = input;
|
|
849
1259
|
let reversed = false;
|
|
850
1260
|
if (!options.force) {
|
|
851
|
-
let check = canLink(
|
|
852
|
-
const tryReversed = options.tryReversed ??
|
|
1261
|
+
let check = canLink(m, source, target, kind);
|
|
1262
|
+
const tryReversed = options.tryReversed ?? definition?.info.tryReversedWhenAdding;
|
|
853
1263
|
if (!check.ok && tryReversed) {
|
|
854
|
-
const swapped = canLink(
|
|
1264
|
+
const swapped = canLink(m, target, source, kind);
|
|
855
1265
|
if (swapped.ok) {
|
|
856
1266
|
[source, target] = [target, source];
|
|
857
1267
|
reversed = true;
|
|
@@ -860,14 +1270,15 @@ function connect(model, input, options = {}) {
|
|
|
860
1270
|
}
|
|
861
1271
|
if (!check.ok) return check;
|
|
862
1272
|
} else {
|
|
863
|
-
|
|
864
|
-
requireElement(
|
|
1273
|
+
if (!definition) throw new ModelOperationError(`unknown link kind "${kind}" in ${metamodel.name}`);
|
|
1274
|
+
requireElement(m, source);
|
|
1275
|
+
requireElement(m, target);
|
|
865
1276
|
}
|
|
866
1277
|
const id = input.id ?? (options.createId ?? defaultIdGenerator)();
|
|
867
|
-
if (
|
|
1278
|
+
if (m.elements.has(id) || m.links.has(id)) throw new ModelOperationError(`id "${id}" is already in use`);
|
|
868
1279
|
const link = withoutUndefined({
|
|
869
1280
|
id,
|
|
870
|
-
kind
|
|
1281
|
+
kind,
|
|
871
1282
|
source,
|
|
872
1283
|
target,
|
|
873
1284
|
label: input.label,
|
|
@@ -876,23 +1287,29 @@ function connect(model, input, options = {}) {
|
|
|
876
1287
|
});
|
|
877
1288
|
return {
|
|
878
1289
|
ok: true,
|
|
879
|
-
model: derive(
|
|
1290
|
+
model: typed(derive(m, { links: new Map(m.links).set(id, link) })),
|
|
880
1291
|
link,
|
|
881
1292
|
reversed
|
|
882
1293
|
};
|
|
883
1294
|
}
|
|
884
1295
|
/** Adds depender → dependum → dependee. */
|
|
885
1296
|
function addDependency(model, input, options = {}) {
|
|
886
|
-
|
|
1297
|
+
const m = any(model);
|
|
1298
|
+
const metamodel = metaOf(m);
|
|
1299
|
+
const dependumKind = input.dependum.kind;
|
|
1300
|
+
const dependumDefinition = metamodel.elements.get(dependumKind);
|
|
1301
|
+
if (dependumDefinition?.category !== "node" || dependumDefinition.info?.canBeDependum === false) throw new ModelOperationError(`${dependumKind} cannot be a dependum`);
|
|
1302
|
+
const linkKind = input.linkKind ?? "istar.DependencyLink";
|
|
1303
|
+
if (metamodel.links.get(linkKind)?.category !== "dependency") throw new ModelOperationError(`${linkKind} is not a dependency link kind`);
|
|
887
1304
|
if (!options.force) {
|
|
888
|
-
const check = canLink(
|
|
1305
|
+
const check = canLink(m, input.depender, input.dependee, linkKind);
|
|
889
1306
|
if (!check.ok) return check;
|
|
890
1307
|
}
|
|
891
|
-
const depender = requireElement(
|
|
892
|
-
const dependee = requireElement(
|
|
1308
|
+
const depender = requireElement(m, input.depender);
|
|
1309
|
+
const dependee = requireElement(m, input.dependee);
|
|
893
1310
|
const createId = options.createId ?? defaultIdGenerator;
|
|
894
|
-
const added = addElement(
|
|
895
|
-
kind:
|
|
1311
|
+
const added = addElement(m, {
|
|
1312
|
+
kind: dependumKind,
|
|
896
1313
|
id: input.dependum.id,
|
|
897
1314
|
name: input.dependum.name,
|
|
898
1315
|
x: input.dependum.x ?? (depender.x + dependee.x) / 2,
|
|
@@ -906,37 +1323,39 @@ function addDependency(model, input, options = {}) {
|
|
|
906
1323
|
const [id1, id2] = input.linkIds ?? [createId(), createId()];
|
|
907
1324
|
const first = {
|
|
908
1325
|
id: id1,
|
|
909
|
-
kind:
|
|
1326
|
+
kind: linkKind,
|
|
910
1327
|
source: depender.id,
|
|
911
1328
|
target: dependum.id
|
|
912
1329
|
};
|
|
913
1330
|
const second = {
|
|
914
1331
|
id: id2,
|
|
915
|
-
kind:
|
|
1332
|
+
kind: linkKind,
|
|
916
1333
|
source: dependum.id,
|
|
917
1334
|
target: dependee.id
|
|
918
1335
|
};
|
|
919
1336
|
for (const id of [id1, id2]) if (added.model.elements.has(id) || added.model.links.has(id) || id1 === id2) throw new ModelOperationError(`id "${id}" is already in use`);
|
|
920
1337
|
return {
|
|
921
1338
|
ok: true,
|
|
922
|
-
model: derive(
|
|
1339
|
+
model: typed(derive(m, {
|
|
923
1340
|
elements: new Map(added.model.elements).set(dependum.id, dependum),
|
|
924
|
-
links: new Map(
|
|
925
|
-
}),
|
|
1341
|
+
links: new Map(m.links).set(id1, first).set(id2, second)
|
|
1342
|
+
})),
|
|
926
1343
|
dependum,
|
|
927
1344
|
links: [first, second]
|
|
928
1345
|
};
|
|
929
1346
|
}
|
|
930
1347
|
/** Removes a link. Removing either half of a dependency removes the whole dependency. */
|
|
931
1348
|
function disconnect(model, id) {
|
|
932
|
-
|
|
933
|
-
|
|
1349
|
+
const m = any(model);
|
|
1350
|
+
requireLink(m, id);
|
|
1351
|
+
return typed(removeCascade(m, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set([id])));
|
|
934
1352
|
}
|
|
935
1353
|
function pick(value, fallback) {
|
|
936
1354
|
return value === void 0 ? fallback : value ?? void 0;
|
|
937
1355
|
}
|
|
938
1356
|
function updateLink(model, id, patch) {
|
|
939
|
-
const
|
|
1357
|
+
const m = any(model);
|
|
1358
|
+
const current = requireLink(m, id);
|
|
940
1359
|
const next = withoutUndefined({
|
|
941
1360
|
...current,
|
|
942
1361
|
label: pick(patch.label, current.label),
|
|
@@ -944,13 +1363,14 @@ function updateLink(model, id, patch) {
|
|
|
944
1363
|
customProperties: pick(patch.customProperties, current.customProperties),
|
|
945
1364
|
display: mergeDisplay(current.display, patch.display)
|
|
946
1365
|
});
|
|
947
|
-
return derive(
|
|
1366
|
+
return typed(derive(m, { links: new Map(m.links).set(id, next) }));
|
|
948
1367
|
}
|
|
949
1368
|
function updateDiagram(model, patch) {
|
|
950
|
-
|
|
951
|
-
|
|
1369
|
+
const m = any(model);
|
|
1370
|
+
return typed(derive(m, { diagram: withoutUndefined({
|
|
1371
|
+
...m.diagram,
|
|
952
1372
|
...patch
|
|
953
|
-
}) });
|
|
1373
|
+
}) }));
|
|
954
1374
|
}
|
|
955
1375
|
/** Where dependency depender/dependee currently point (see `dependencyLinksOf`). */
|
|
956
1376
|
function dependencyEnds(model, dependumId) {
|
|
@@ -963,7 +1383,8 @@ function dependencyEnds(model, dependumId) {
|
|
|
963
1383
|
}
|
|
964
1384
|
//#endregion
|
|
965
1385
|
//#region src/store.ts
|
|
966
|
-
function createModelStore(initial
|
|
1386
|
+
function createModelStore(initial, options = {}) {
|
|
1387
|
+
initial ??= options.metamodel ? createEmptyModel(void 0, { metamodel: options.metamodel }) : createEmptyModel();
|
|
967
1388
|
const limit = options.historyLimit ?? 100;
|
|
968
1389
|
const ctx = { createId: options.createId ?? defaultIdGenerator };
|
|
969
1390
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -1420,6 +1841,6 @@ function validateModelProperties(model, schemas) {
|
|
|
1420
1841
|
return issues;
|
|
1421
1842
|
}
|
|
1422
1843
|
//#endregion
|
|
1423
|
-
export { ACTOR_KINDS, ACTOR_LINK_KINDS, CONTRIBUTION_LABELS, DEFAULT_DIAGRAM, DEFAULT_ELEMENT_SIZE, DEPENDENCY_LINK_KINDS, ELEMENT_KINDS, ISTAR_PREFIX, ISTAR_VERSION, LINK_KINDS, LINK_KIND_INFO, METAMODEL_VERSION, ModelOperationError, NODE_KINDS, NODE_KIND_INFO, NODE_LINK_KINDS, PISTAR_TOOL, PistarParseError, addDependency, addElement, boolean, canLink, childrenOf, connect, createEmptyModel, createModelStore, defaultIdGenerator, defineProperties, dependencyEnds, dependencyLinksOf, disconnect, enumOf, getSourceLayout, inheritSourceLayout, isActor, isActorKind, isActorLinkKind, isContributionLabel, isDependencyLinkKind, isElementKind, isLinkKind, isNode, isNodeKind, isNodeLinkKind, linksOf, moveElement, nestElement, number, parsePistar, prop, removeElement, removeElements, shortKindName, string, toPistar, toPistarObject, updateDiagram, updateElement, updateLink, validateModel, validateModelProperties };
|
|
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 };
|
|
1424
1845
|
|
|
1425
1846
|
//# sourceMappingURL=index.js.map
|