@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/README.md +77 -0
- package/dist/index.cjs +548 -121
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +329 -95
- package/dist/index.d.ts +329 -95
- package/dist/index.js +534 -122
- 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,52 @@ 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
|
}
|
|
157
|
-
|
|
158
|
-
|
|
417
|
+
/** True for actor-category elements; pass the metamodel for extended actor kinds. */
|
|
418
|
+
function isActor(element, metamodel = ISTAR_2_0) {
|
|
419
|
+
return element !== void 0 && isActorKindIn(metamodel, element.kind);
|
|
159
420
|
}
|
|
160
|
-
|
|
161
|
-
|
|
421
|
+
/** True for node-category elements; pass the metamodel for extended node kinds. */
|
|
422
|
+
function isNode(element, metamodel = ISTAR_2_0) {
|
|
423
|
+
return element !== void 0 && !isActorKindIn(metamodel, element.kind);
|
|
162
424
|
}
|
|
163
425
|
/** Inner elements of an actor, in model order. */
|
|
164
426
|
function childrenOf(model, actorId) {
|
|
@@ -174,10 +436,11 @@ function linksOf(model, elementId) {
|
|
|
174
436
|
}
|
|
175
437
|
/** For a dependum, the two halves of its dependency: depender → dependum → dependee. */
|
|
176
438
|
function dependencyLinksOf(model, dependumId) {
|
|
439
|
+
const metamodel = metamodelOf(model);
|
|
177
440
|
let inbound;
|
|
178
441
|
let outbound;
|
|
179
442
|
for (const link of model.links.values()) {
|
|
180
|
-
if (link.kind !== "
|
|
443
|
+
if (metamodel.links.get(link.kind)?.category !== "dependency") continue;
|
|
181
444
|
if (link.target === dependumId && !inbound) inbound = link;
|
|
182
445
|
if (link.source === dependumId && !outbound) outbound = link;
|
|
183
446
|
}
|
|
@@ -199,17 +462,41 @@ function fail(code, reason) {
|
|
|
199
462
|
/**
|
|
200
463
|
* Checks whether a link of `kind` may be added from `source` to `target` in `model`.
|
|
201
464
|
*
|
|
202
|
-
* For
|
|
203
|
-
*
|
|
465
|
+
* For dependency links, `source` is the depender and `target` the dependee (an actor or one of
|
|
466
|
+
* its inner elements); the dependum sits between them and is not part of the check.
|
|
467
|
+
*
|
|
468
|
+
* Extended kinds (see `extendMetamodel`) are checked with the rules of the kind they behave
|
|
469
|
+
* like, or with their declarative rules, then with their extra predicate.
|
|
204
470
|
*/
|
|
205
|
-
function canLink(model, source, target, kind) {
|
|
206
|
-
const
|
|
207
|
-
const
|
|
471
|
+
function canLink(model, source, target, kind, options = {}) {
|
|
472
|
+
const any = model;
|
|
473
|
+
const metamodel = options.metamodel ?? metamodelOf(model);
|
|
474
|
+
const s = resolve(any, source);
|
|
475
|
+
const t = resolve(any, target);
|
|
208
476
|
if (!s) return fail("unknown-element", `unknown source element "${refId(source)}"`);
|
|
209
477
|
if (!t) return fail("unknown-element", `unknown target element "${refId(target)}"`);
|
|
210
|
-
const
|
|
211
|
-
if (!
|
|
212
|
-
return
|
|
478
|
+
const definition = metamodel.links.get(kind);
|
|
479
|
+
if (!definition) return fail("unknown-link-kind", `unknown link kind "${String(kind)}"`);
|
|
480
|
+
return checkWith(new Graph(any, metamodel), definition, s, t);
|
|
481
|
+
}
|
|
482
|
+
/** Runs the rule a link kind resolves to, then every extra predicate along its chain. */
|
|
483
|
+
function checkWith(graph, definition, source, target) {
|
|
484
|
+
const chain = [];
|
|
485
|
+
for (let current = definition; current && !chain.includes(current); current = current.behavesLike ? graph.metamodel.links.get(current.behavesLike) : void 0) chain.push(current);
|
|
486
|
+
const root = chain[chain.length - 1];
|
|
487
|
+
const builtIn = CHECKS[root.kind];
|
|
488
|
+
let result;
|
|
489
|
+
if (root.rules) result = declarativeCheck(graph, definition, root.rules, root.kind, source, target);
|
|
490
|
+
else if (builtIn) result = builtIn(graph, source, target);
|
|
491
|
+
else result = fail("unknown-link-kind", `link kind "${definition.kind}" has no rules`);
|
|
492
|
+
if (!result.ok) return result;
|
|
493
|
+
const context = graph.context(definition.kind, source, target);
|
|
494
|
+
for (const step of chain.toReversed()) {
|
|
495
|
+
if (!step.check) continue;
|
|
496
|
+
const extra = step.check(context);
|
|
497
|
+
if (!extra.ok) return extra;
|
|
498
|
+
}
|
|
499
|
+
return OK;
|
|
213
500
|
}
|
|
214
501
|
function refId(ref) {
|
|
215
502
|
return typeof ref === "string" ? ref : ref.id;
|
|
@@ -217,11 +504,20 @@ function refId(ref) {
|
|
|
217
504
|
function resolve(model, ref) {
|
|
218
505
|
return model.elements.get(refId(ref));
|
|
219
506
|
}
|
|
220
|
-
/**
|
|
507
|
+
/**
|
|
508
|
+
* The graph queries used by upstream constraints (`istar.isThereLinkBetween`, etc.). Link kinds
|
|
509
|
+
* are compared by what they behave like, so an extension link behaving like an OR-refinement
|
|
510
|
+
* counts as one for the iStar 2.0 rules (no mixing with AND, no refining a depender…).
|
|
511
|
+
*/
|
|
221
512
|
var Graph = class {
|
|
222
513
|
model;
|
|
223
|
-
|
|
514
|
+
metamodel;
|
|
515
|
+
constructor(model, metamodel) {
|
|
224
516
|
this.model = model;
|
|
517
|
+
this.metamodel = metamodel;
|
|
518
|
+
}
|
|
519
|
+
matches(link, kind) {
|
|
520
|
+
return link.kind === kind || effectiveLinkKind(this.metamodel, link.kind) === kind;
|
|
225
521
|
}
|
|
226
522
|
/**
|
|
227
523
|
* True when a link (optionally of `kind`) already connects the two elements, in either
|
|
@@ -229,47 +525,76 @@ var Graph = class {
|
|
|
229
525
|
*/
|
|
230
526
|
isThereLinkBetween(a, b, kind) {
|
|
231
527
|
for (const link of this.model.links.values()) {
|
|
232
|
-
if (kind && link
|
|
528
|
+
if (kind && !this.matches(link, kind)) continue;
|
|
529
|
+
if (link.source === a.id && link.target === b.id || link.source === b.id && link.target === a.id) return true;
|
|
530
|
+
}
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
/** Like `isThereLinkBetween`, counting only links of exactly `kind`. */
|
|
534
|
+
isThereLinkOfKindBetween(a, b, kind) {
|
|
535
|
+
for (const link of this.model.links.values()) {
|
|
536
|
+
if (link.kind !== kind) continue;
|
|
233
537
|
if (link.source === a.id && link.target === b.id || link.source === b.id && link.target === a.id) return true;
|
|
234
538
|
}
|
|
235
539
|
return false;
|
|
236
540
|
}
|
|
237
541
|
isSourceOfType(element, kind) {
|
|
238
|
-
for (const link of this.model.links.values()) if (link.
|
|
542
|
+
for (const link of this.model.links.values()) if (link.source === element.id && this.matches(link, kind)) return true;
|
|
239
543
|
return false;
|
|
240
544
|
}
|
|
241
545
|
isTargetOfType(element, kind) {
|
|
242
|
-
for (const link of this.model.links.values()) if (link.
|
|
546
|
+
for (const link of this.model.links.values()) if (link.target === element.id && this.matches(link, kind)) return true;
|
|
243
547
|
return false;
|
|
244
548
|
}
|
|
549
|
+
/** The kind the iStar 2.0 rules see: built-in kinds as they are, extensions as what they behave like. */
|
|
550
|
+
kindOf(element) {
|
|
551
|
+
return effectiveElementKind(this.metamodel, element.kind);
|
|
552
|
+
}
|
|
553
|
+
isActor(element) {
|
|
554
|
+
return this.metamodel.elements.get(element.kind)?.category === "actor";
|
|
555
|
+
}
|
|
556
|
+
context(kind, source, target) {
|
|
557
|
+
return {
|
|
558
|
+
model: this.model,
|
|
559
|
+
metamodel: this.metamodel,
|
|
560
|
+
kind,
|
|
561
|
+
source,
|
|
562
|
+
target,
|
|
563
|
+
isThereLinkBetween: (a, b, k) => this.isThereLinkBetween(a, b, k),
|
|
564
|
+
isSourceOfType: (e, k) => this.isSourceOfType(e, k),
|
|
565
|
+
isTargetOfType: (e, k) => this.isTargetOfType(e, k)
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
const is = (graph, element, ...shortNames) => {
|
|
570
|
+
const kind = graph.kindOf(element);
|
|
571
|
+
return shortNames.some((name) => kind === `istar.${name}`);
|
|
245
572
|
};
|
|
246
|
-
const is = (element, ...shortNames) => shortNames.some((name) => element.kind === `istar.${name}`);
|
|
247
|
-
const isKindOfActor = (element) => isActorKind(element.kind);
|
|
248
573
|
const isDependum = (element) => element.isDependum === true;
|
|
249
574
|
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).");
|
|
575
|
+
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
576
|
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).");
|
|
577
|
+
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
578
|
if (source.id === target.id) return fail("self-link", "you cannot make Is-A links from an actor onto itself.");
|
|
254
579
|
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
580
|
return OK;
|
|
256
581
|
};
|
|
257
582
|
const participatesInLink = (graph, source, target) => {
|
|
258
|
-
if (!
|
|
259
|
-
if (!
|
|
583
|
+
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)");
|
|
584
|
+
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
585
|
if (source.id === target.id) return fail("self-link", "you cannot make a Participates-In link from an actor onto itself.");
|
|
261
586
|
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
587
|
return OK;
|
|
263
588
|
};
|
|
264
589
|
/** The actor an element belongs to: itself for actors, its parent for inner elements. */
|
|
265
|
-
function owningActorId(element) {
|
|
266
|
-
return
|
|
590
|
+
function owningActorId(graph, element) {
|
|
591
|
+
return graph.isActor(element) ? element.id : element.parent;
|
|
267
592
|
}
|
|
268
593
|
const dependencyLink = (graph, source, target) => {
|
|
269
594
|
if (source.id === target.id) return fail("self-link", "a Dependency link cannot link an element onto itself");
|
|
270
595
|
if (isDependum(source)) return fail("dependum-endpoint", "a Dependency link cannot start from a dependum");
|
|
271
596
|
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)");
|
|
597
|
+
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
598
|
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
599
|
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
600
|
return OK;
|
|
@@ -277,8 +602,8 @@ const dependencyLink = (graph, source, target) => {
|
|
|
277
602
|
function refinementLink(label) {
|
|
278
603
|
const other = label === "AND" ? "istar.OrRefinementLink" : "istar.AndRefinementLink";
|
|
279
604
|
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)`);
|
|
605
|
+
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)`);
|
|
606
|
+
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
607
|
if (source.id === target.id) return fail("self-link", `you cannot make an ${label}-refinement link from an element onto itself`);
|
|
283
608
|
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
609
|
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 +614,8 @@ function refinementLink(label) {
|
|
|
289
614
|
};
|
|
290
615
|
}
|
|
291
616
|
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)");
|
|
617
|
+
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)");
|
|
618
|
+
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
619
|
if (source.id === target.id) return fail("self-link", "you cannot make a Needed-By link from an element onto itself");
|
|
295
620
|
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
621
|
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 +623,8 @@ const neededByLink = (graph, source, target) => {
|
|
|
298
623
|
return OK;
|
|
299
624
|
};
|
|
300
625
|
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)");
|
|
626
|
+
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)");
|
|
627
|
+
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
628
|
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
629
|
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
630
|
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 +634,8 @@ const contributionLink = (graph, source, target) => {
|
|
|
309
634
|
return OK;
|
|
310
635
|
};
|
|
311
636
|
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)");
|
|
637
|
+
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)");
|
|
638
|
+
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
639
|
if (source.id === target.id) return fail("self-link", "you cannot make a Qualification link from an element onto itself");
|
|
315
640
|
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
641
|
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 +643,42 @@ const qualificationLink = (graph, source, target) => {
|
|
|
318
643
|
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
644
|
return OK;
|
|
320
645
|
};
|
|
646
|
+
/** The words used for a list of kind selectors in messages: "a Goal or a Task". */
|
|
647
|
+
function describe(graph, selectors) {
|
|
648
|
+
const names = selectors.map((selector) => {
|
|
649
|
+
if (selector === "*") return "any element";
|
|
650
|
+
if (selector === "node") return "an intentional element";
|
|
651
|
+
if (selector === "actor") return "an actor";
|
|
652
|
+
const label = graph.metamodel.elements.get(selector)?.label ?? selector;
|
|
653
|
+
return /^[aeiou]/i.test(label) ? `an ${label}` : `a ${label}`;
|
|
654
|
+
});
|
|
655
|
+
return names.length <= 2 ? names.join(" or ") : `${names.slice(0, -1).join(", ")} or ${names[names.length - 1]}`;
|
|
656
|
+
}
|
|
657
|
+
function matchesSelector(graph, element, selector) {
|
|
658
|
+
if (selector === "*") return true;
|
|
659
|
+
const category = graph.metamodel.elements.get(element.kind)?.category;
|
|
660
|
+
if (selector === "node" || selector === "actor") return category === selector;
|
|
661
|
+
return elementBehavesLike(graph.metamodel, element.kind, selector);
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Declarative rules of an extension link kind, checked in the order of the iStar 2.0 node-link
|
|
665
|
+
* rules (and of piStar-ext's generated `isValid`): source kind, target kind, self link,
|
|
666
|
+
* dependum, same actor, duplicates.
|
|
667
|
+
*/
|
|
668
|
+
function declarativeCheck(graph, definition, rules, rulesKind, source, target) {
|
|
669
|
+
const name = definition.label;
|
|
670
|
+
const article = /^[aeiou]/i.test(name) ? "an" : "a";
|
|
671
|
+
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)}`);
|
|
672
|
+
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)}`);
|
|
673
|
+
if (!rules.allowSelf && source.id === target.id) return fail("self-link", `you cannot make ${article} ${name} link from an element onto itself`);
|
|
674
|
+
if (definition.category === "node") {
|
|
675
|
+
if (!rules.allowDependum && (isDependum(source) || isDependum(target))) return fail("dependum-endpoint", `you cannot make ${article} ${name} link with a dependum`);
|
|
676
|
+
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)`);
|
|
677
|
+
}
|
|
678
|
+
const unique = rules.unique ?? "kind";
|
|
679
|
+
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`);
|
|
680
|
+
return OK;
|
|
681
|
+
}
|
|
321
682
|
const CHECKS = {
|
|
322
683
|
"istar.IsALink": isALink,
|
|
323
684
|
"istar.ParticipatesInLink": participatesInLink,
|
|
@@ -333,37 +694,42 @@ const CHECKS = {
|
|
|
333
694
|
* first (each checked before its own links are added), then the remaining links in file
|
|
334
695
|
* order, each against the graph built so far. Invalid links are reported, not removed.
|
|
335
696
|
*/
|
|
336
|
-
function validateModel(model) {
|
|
697
|
+
function validateModel(model, options = {}) {
|
|
698
|
+
const metamodel = options.metamodel ?? metamodelOf(model);
|
|
699
|
+
const any = model;
|
|
337
700
|
const issues = [];
|
|
338
701
|
const accepted = /* @__PURE__ */ new Map();
|
|
339
702
|
const view = () => ({
|
|
340
|
-
...
|
|
703
|
+
...any,
|
|
341
704
|
links: accepted
|
|
342
705
|
});
|
|
343
|
-
const
|
|
344
|
-
|
|
706
|
+
const check = (source, target, kind) => canLink(view(), source, target, kind, { metamodel });
|
|
707
|
+
const isDependency = (link) => metamodel.links.get(link.kind)?.category === "dependency";
|
|
708
|
+
const report = (linkId, kind, source, target, result) => {
|
|
709
|
+
if (!result.ok) issues.push({
|
|
345
710
|
linkId,
|
|
346
711
|
kind,
|
|
347
712
|
source,
|
|
348
713
|
target,
|
|
349
|
-
code:
|
|
350
|
-
message:
|
|
714
|
+
code: result.code,
|
|
715
|
+
message: result.reason
|
|
351
716
|
});
|
|
352
717
|
};
|
|
353
|
-
const dependencyLinks = [...
|
|
354
|
-
for (const dependum of
|
|
718
|
+
const dependencyLinks = [...any.links.values()].filter(isDependency);
|
|
719
|
+
for (const dependum of any.elements.values()) {
|
|
355
720
|
if (!dependum.isDependum) continue;
|
|
356
721
|
const inbound = dependencyLinks.find((l) => l.target === dependum.id);
|
|
357
722
|
const outbound = dependencyLinks.find((l) => l.source === dependum.id);
|
|
358
723
|
const depender = inbound?.source ?? dependum.dependency?.source;
|
|
359
724
|
const dependee = outbound?.target ?? dependum.dependency?.target;
|
|
360
|
-
|
|
725
|
+
const kind = inbound?.kind ?? outbound?.kind ?? "istar.DependencyLink";
|
|
726
|
+
if (depender !== void 0 && dependee !== void 0) report(inbound?.id ?? dependum.id, kind, depender, dependee, check(depender, dependee, kind));
|
|
361
727
|
if (inbound) accepted.set(inbound.id, inbound);
|
|
362
728
|
if (outbound) accepted.set(outbound.id, outbound);
|
|
363
729
|
}
|
|
364
|
-
for (const link of
|
|
365
|
-
if (link
|
|
366
|
-
if (
|
|
730
|
+
for (const link of any.links.values()) {
|
|
731
|
+
if (isDependency(link)) continue;
|
|
732
|
+
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
733
|
else report(link.id, link.kind, link.source, link.target, {
|
|
368
734
|
ok: false,
|
|
369
735
|
code: "unknown-element",
|
|
@@ -378,6 +744,10 @@ function validateModel(model) {
|
|
|
378
744
|
var PistarParseError = class extends Error {
|
|
379
745
|
name = "PistarParseError";
|
|
380
746
|
};
|
|
747
|
+
/** Thrown by `toPistar` for a kind its metamodel doesn't know (rather than writing it wrong). */
|
|
748
|
+
var PistarWriteError = class extends Error {
|
|
749
|
+
name = "PistarWriteError";
|
|
750
|
+
};
|
|
381
751
|
const layouts = /* @__PURE__ */ new WeakMap();
|
|
382
752
|
/** The key order recorded when `model` was parsed, if it came from `parsePistar`. */
|
|
383
753
|
function getSourceLayout(model) {
|
|
@@ -446,13 +816,9 @@ function asArray(value, where) {
|
|
|
446
816
|
if (!Array.isArray(value)) throw new PistarParseError(`"${where}" must be an array`);
|
|
447
817
|
return value;
|
|
448
818
|
}
|
|
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) {
|
|
819
|
+
function parsePistar(input, options = {}) {
|
|
820
|
+
const metamodel = options.metamodel ?? ISTAR_2_0;
|
|
821
|
+
const categoryOf = (kind) => metamodel.elements.get(kind)?.category;
|
|
456
822
|
let json;
|
|
457
823
|
if (typeof input === "string") try {
|
|
458
824
|
json = JSON.parse(input);
|
|
@@ -475,11 +841,12 @@ function parsePistar(input) {
|
|
|
475
841
|
if (!isRecord(raw)) throw new PistarParseError(`${where}: expected an object`);
|
|
476
842
|
const { id, type } = raw;
|
|
477
843
|
if (typeof id !== "string" || id === "") throw new PistarParseError(`${where}: missing "id"`);
|
|
478
|
-
|
|
844
|
+
const kind = typeof type === "string" ? metamodel.elementsByPistarType.get(type) : void 0;
|
|
845
|
+
if (kind === void 0) throw new PistarParseError(`${where} (${id}): unknown element type ${JSON.stringify(type)}`);
|
|
479
846
|
if (elements.has(id) || links.has(id)) throw new PistarParseError(`${where}: duplicated id "${id}"`);
|
|
480
847
|
const element = {
|
|
481
848
|
id,
|
|
482
|
-
kind
|
|
849
|
+
kind,
|
|
483
850
|
name: typeof raw.text === "string" ? raw.text : "",
|
|
484
851
|
x: typeof raw.x === "number" ? raw.x : 0,
|
|
485
852
|
y: typeof raw.y === "number" ? raw.y : 0,
|
|
@@ -493,9 +860,10 @@ function parsePistar(input) {
|
|
|
493
860
|
};
|
|
494
861
|
asArray(json.actors, "actors").forEach((raw, i) => {
|
|
495
862
|
const actor = readElement(raw, `actors[${i}]`, ACTOR_KEYS, {});
|
|
496
|
-
if (
|
|
863
|
+
if (categoryOf(actor.kind) !== "actor") throw new PistarParseError(`actors[${i}] (${actor.id}): ${actor.kind} is not an actor kind`);
|
|
497
864
|
asArray(raw.nodes, `actors[${i}].nodes`).forEach((node, j) => {
|
|
498
|
-
|
|
865
|
+
const child = readElement(node, `actors[${i}].nodes[${j}]`, ELEMENT_KEYS, { parent: actor.id });
|
|
866
|
+
if (categoryOf(child.kind) === "actor") throw new PistarParseError(`actors[${i}].nodes[${j}]: actors cannot be nested`);
|
|
499
867
|
});
|
|
500
868
|
});
|
|
501
869
|
asArray(json.orphans, "orphans").forEach((raw, i) => {
|
|
@@ -516,12 +884,13 @@ function parsePistar(input) {
|
|
|
516
884
|
if (!isRecord(raw)) throw new PistarParseError(`links[${i}]: expected an object`);
|
|
517
885
|
const { id, type, source, target } = raw;
|
|
518
886
|
if (typeof id !== "string" || id === "") throw new PistarParseError(`links[${i}]: missing "id"`);
|
|
519
|
-
|
|
887
|
+
const kind = typeof type === "string" ? metamodel.linksByPistarType.get(type) : void 0;
|
|
888
|
+
if (kind === void 0) throw new PistarParseError(`links[${i}] (${id}): unknown link type ${JSON.stringify(type)}`);
|
|
520
889
|
if (typeof source !== "string" || typeof target !== "string") throw new PistarParseError(`links[${i}] (${id}): missing "source" or "target"`);
|
|
521
890
|
if (elements.has(id) || links.has(id)) throw new PistarParseError(`links[${i}]: duplicated id "${id}"`);
|
|
522
891
|
links.set(id, {
|
|
523
892
|
id,
|
|
524
|
-
kind
|
|
893
|
+
kind,
|
|
525
894
|
source,
|
|
526
895
|
target,
|
|
527
896
|
...optional("name", typeof raw.name === "string" ? raw.name : void 0),
|
|
@@ -558,7 +927,7 @@ function parsePistar(input) {
|
|
|
558
927
|
keys: Object.keys(json),
|
|
559
928
|
display: Object.keys(display)
|
|
560
929
|
});
|
|
561
|
-
return model;
|
|
930
|
+
return withMetamodel(model, metamodel);
|
|
562
931
|
}
|
|
563
932
|
function optional(key, value) {
|
|
564
933
|
return value === void 0 ? {} : { [key]: value };
|
|
@@ -566,18 +935,25 @@ function optional(key, value) {
|
|
|
566
935
|
function formatDate(date) {
|
|
567
936
|
return date.toUTCString();
|
|
568
937
|
}
|
|
569
|
-
function
|
|
938
|
+
function pistarTypeOf(type, kind, metamodel) {
|
|
939
|
+
if (type === void 0) throw new PistarWriteError(`kind "${kind}" is not part of ${metamodel.name}; pass its metamodel (toPistar(model, { metamodel }) or withMetamodel)`);
|
|
940
|
+
return type;
|
|
941
|
+
}
|
|
942
|
+
function elementJson(element, metamodel) {
|
|
570
943
|
return {
|
|
571
944
|
id: element.id,
|
|
572
945
|
text: element.name,
|
|
573
|
-
type: element.kind,
|
|
946
|
+
type: pistarTypeOf(metamodel.elements.get(element.kind)?.pistarType, element.kind, metamodel),
|
|
574
947
|
x: element.x,
|
|
575
948
|
y: element.y,
|
|
576
949
|
...element.customProperties ? { customProperties: { ...element.customProperties } } : {}
|
|
577
950
|
};
|
|
578
951
|
}
|
|
579
952
|
/** Builds the save-file object for a model. `toPistar` is this plus `JSON.stringify`. */
|
|
580
|
-
function toPistarObject(
|
|
953
|
+
function toPistarObject(input, options = {}) {
|
|
954
|
+
const model = input;
|
|
955
|
+
const metamodel = options.metamodel ?? metamodelOf(input);
|
|
956
|
+
const isActorKind = (kind) => metamodel.elements.get(kind)?.category === "actor";
|
|
581
957
|
const actors = [];
|
|
582
958
|
const orphans = [];
|
|
583
959
|
const dependencies = [];
|
|
@@ -586,31 +962,31 @@ function toPistarObject(model, options = {}) {
|
|
|
586
962
|
let list = children.get(element.parent);
|
|
587
963
|
if (!list) children.set(element.parent, list = []);
|
|
588
964
|
list.push({
|
|
589
|
-
...elementJson(element),
|
|
965
|
+
...elementJson(element, metamodel),
|
|
590
966
|
...element.extra
|
|
591
967
|
});
|
|
592
968
|
}
|
|
593
969
|
for (const element of model.elements.values()) if (isActorKind(element.kind)) actors.push({
|
|
594
|
-
...elementJson(element),
|
|
970
|
+
...elementJson(element, metamodel),
|
|
595
971
|
nodes: children.get(element.id) ?? [],
|
|
596
972
|
...element.extra
|
|
597
973
|
});
|
|
598
974
|
else if (element.isDependum) {
|
|
599
975
|
const { inbound, outbound } = dependencyLinksOf(model, element.id);
|
|
600
976
|
dependencies.push({
|
|
601
|
-
...elementJson(element),
|
|
977
|
+
...elementJson(element, metamodel),
|
|
602
978
|
source: inbound?.source ?? element.dependency?.source ?? "",
|
|
603
979
|
target: outbound?.target ?? element.dependency?.target ?? "",
|
|
604
980
|
...element.extra
|
|
605
981
|
});
|
|
606
982
|
} else if (element.parent === void 0 || !model.elements.has(element.parent)) orphans.push({
|
|
607
|
-
...elementJson(element),
|
|
983
|
+
...elementJson(element, metamodel),
|
|
608
984
|
...element.extra
|
|
609
985
|
});
|
|
610
986
|
const links = [];
|
|
611
987
|
for (const link of model.links.values()) links.push({
|
|
612
988
|
id: link.id,
|
|
613
|
-
type: link.kind,
|
|
989
|
+
type: pistarTypeOf(metamodel.links.get(link.kind)?.pistarType, link.kind, metamodel),
|
|
614
990
|
source: link.source,
|
|
615
991
|
target: link.target,
|
|
616
992
|
...link.name !== void 0 ? { name: link.name } : {},
|
|
@@ -690,11 +1066,23 @@ const defaultIdGenerator = () => {
|
|
|
690
1066
|
return (ch === "x" ? r : r & 3 | 8).toString(16);
|
|
691
1067
|
});
|
|
692
1068
|
};
|
|
1069
|
+
function any(model) {
|
|
1070
|
+
return model;
|
|
1071
|
+
}
|
|
1072
|
+
function typed(model) {
|
|
1073
|
+
return model;
|
|
1074
|
+
}
|
|
1075
|
+
function metaOf(model) {
|
|
1076
|
+
return metamodelOf(model);
|
|
1077
|
+
}
|
|
1078
|
+
function isActorIn(model, kind) {
|
|
1079
|
+
return metaOf(model).elements.get(kind)?.category === "actor";
|
|
1080
|
+
}
|
|
693
1081
|
function derive(from, changes) {
|
|
694
|
-
return inheritSourceLayout(from, {
|
|
1082
|
+
return inheritMetamodel(from, inheritSourceLayout(from, {
|
|
695
1083
|
...from,
|
|
696
1084
|
...changes
|
|
697
|
-
});
|
|
1085
|
+
}));
|
|
698
1086
|
}
|
|
699
1087
|
function requireElement(model, id) {
|
|
700
1088
|
const element = model.elements.get(id);
|
|
@@ -712,16 +1100,20 @@ function withoutUndefined(value) {
|
|
|
712
1100
|
return out;
|
|
713
1101
|
}
|
|
714
1102
|
function addElement(model, input, ctx = {}) {
|
|
1103
|
+
const m = any(model);
|
|
1104
|
+
const metamodel = metaOf(m);
|
|
1105
|
+
const definition = metamodel.elements.get(input.kind);
|
|
1106
|
+
if (!definition) throw new ModelOperationError(`unknown element kind "${input.kind}" in ${metamodel.name}`);
|
|
715
1107
|
const id = input.id ?? (ctx.createId ?? defaultIdGenerator)();
|
|
716
|
-
if (
|
|
1108
|
+
if (m.elements.has(id) || m.links.has(id)) throw new ModelOperationError(`id "${id}" is already in use`);
|
|
717
1109
|
if (input.parent !== void 0) {
|
|
718
|
-
if (
|
|
719
|
-
if (!
|
|
1110
|
+
if (definition.category === "actor") throw new ModelOperationError("actors cannot be nested");
|
|
1111
|
+
if (!isActorIn(m, requireElement(m, input.parent).kind)) throw new ModelOperationError(`parent "${input.parent}" is not an actor`);
|
|
720
1112
|
}
|
|
721
1113
|
const element = withoutUndefined({
|
|
722
1114
|
id,
|
|
723
1115
|
kind: input.kind,
|
|
724
|
-
name: input.name ??
|
|
1116
|
+
name: input.name ?? definition.label,
|
|
725
1117
|
x: input.x,
|
|
726
1118
|
y: input.y,
|
|
727
1119
|
parent: input.parent,
|
|
@@ -729,7 +1121,7 @@ function addElement(model, input, ctx = {}) {
|
|
|
729
1121
|
display: input.display
|
|
730
1122
|
});
|
|
731
1123
|
return {
|
|
732
|
-
model: derive(
|
|
1124
|
+
model: typed(derive(m, { elements: new Map(m.elements).set(id, element) })),
|
|
733
1125
|
element
|
|
734
1126
|
};
|
|
735
1127
|
}
|
|
@@ -742,38 +1134,40 @@ function mergeDisplay(current, patch) {
|
|
|
742
1134
|
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
743
1135
|
}
|
|
744
1136
|
function updateElement(model, id, patch) {
|
|
745
|
-
const
|
|
1137
|
+
const m = any(model);
|
|
1138
|
+
const current = requireElement(m, id);
|
|
746
1139
|
const next = withoutUndefined({
|
|
747
1140
|
...current,
|
|
748
1141
|
name: patch.name ?? current.name,
|
|
749
1142
|
customProperties: patch.customProperties === void 0 ? current.customProperties : patch.customProperties ?? void 0,
|
|
750
1143
|
display: mergeDisplay(current.display, patch.display)
|
|
751
1144
|
});
|
|
752
|
-
return derive(
|
|
1145
|
+
return typed(derive(m, { elements: new Map(m.elements).set(id, next) }));
|
|
753
1146
|
}
|
|
754
1147
|
/**
|
|
755
1148
|
* Moves an element to an absolute position. Moving an actor shifts its inner elements by the
|
|
756
1149
|
* same offset.
|
|
757
1150
|
*/
|
|
758
1151
|
function moveElement(model, id, x, y) {
|
|
759
|
-
const
|
|
1152
|
+
const m = any(model);
|
|
1153
|
+
const current = requireElement(m, id);
|
|
760
1154
|
const dx = x - current.x;
|
|
761
1155
|
const dy = y - current.y;
|
|
762
1156
|
if (dx === 0 && dy === 0) return model;
|
|
763
|
-
const elements = new Map(
|
|
1157
|
+
const elements = new Map(m.elements);
|
|
764
1158
|
elements.set(id, {
|
|
765
1159
|
...current,
|
|
766
1160
|
x,
|
|
767
1161
|
y
|
|
768
1162
|
});
|
|
769
|
-
if (
|
|
770
|
-
for (const child of
|
|
1163
|
+
if (isActorIn(m, current.kind)) {
|
|
1164
|
+
for (const child of m.elements.values()) if (child.parent === id) elements.set(child.id, {
|
|
771
1165
|
...child,
|
|
772
1166
|
x: child.x + dx,
|
|
773
1167
|
y: child.y + dy
|
|
774
1168
|
});
|
|
775
1169
|
}
|
|
776
|
-
return derive(
|
|
1170
|
+
return typed(derive(m, { elements }));
|
|
777
1171
|
}
|
|
778
1172
|
/**
|
|
779
1173
|
* Moves an inner element into another actor (or out of any actor with `null`). The element
|
|
@@ -781,32 +1175,35 @@ function moveElement(model, id, x, y) {
|
|
|
781
1175
|
* them.
|
|
782
1176
|
*/
|
|
783
1177
|
function nestElement(model, id, parent) {
|
|
784
|
-
const
|
|
785
|
-
|
|
1178
|
+
const m = any(model);
|
|
1179
|
+
const current = requireElement(m, id);
|
|
1180
|
+
if (isActorIn(m, current.kind)) throw new ModelOperationError("actors cannot be nested");
|
|
786
1181
|
if (current.isDependum) throw new ModelOperationError("a dependum cannot be nested");
|
|
787
1182
|
if ((current.parent ?? null) === parent) return model;
|
|
788
|
-
if (parent !== null && !
|
|
789
|
-
const elements = new Map(
|
|
1183
|
+
if (parent !== null && !isActorIn(m, requireElement(m, parent).kind)) throw new ModelOperationError(`parent "${parent}" is not an actor`);
|
|
1184
|
+
const elements = new Map(m.elements);
|
|
790
1185
|
elements.delete(id);
|
|
791
1186
|
const { parent: _old, ...rest } = current;
|
|
792
1187
|
elements.set(id, parent === null ? rest : {
|
|
793
1188
|
...rest,
|
|
794
1189
|
parent
|
|
795
1190
|
});
|
|
796
|
-
return derive(
|
|
1191
|
+
return typed(derive(m, { elements }));
|
|
797
1192
|
}
|
|
798
1193
|
/**
|
|
799
1194
|
* Removes elements and everything that depends on them: inner elements of removed actors,
|
|
800
1195
|
* links touching removed elements, and the rest of any dependency that loses a part.
|
|
801
1196
|
*/
|
|
802
1197
|
function removeElements(model, ids) {
|
|
803
|
-
return removeCascade(model, new Set(ids), /* @__PURE__ */ new Set());
|
|
1198
|
+
return typed(removeCascade(any(model), new Set(ids), /* @__PURE__ */ new Set()));
|
|
804
1199
|
}
|
|
805
1200
|
function removeElement(model, id) {
|
|
806
|
-
requireElement(model, id);
|
|
1201
|
+
requireElement(any(model), id);
|
|
807
1202
|
return removeElements(model, [id]);
|
|
808
1203
|
}
|
|
809
1204
|
function removeCascade(model, elementIds, linkIds) {
|
|
1205
|
+
const metamodel = metaOf(model);
|
|
1206
|
+
const isDependencyHalf = (link) => metamodel.links.get(link.kind)?.category === "dependency";
|
|
810
1207
|
let changed = true;
|
|
811
1208
|
while (changed) {
|
|
812
1209
|
changed = false;
|
|
@@ -825,7 +1222,7 @@ function removeCascade(model, elementIds, linkIds) {
|
|
|
825
1222
|
}
|
|
826
1223
|
}
|
|
827
1224
|
for (const link of model.links.values()) {
|
|
828
|
-
if (!linkIds.has(link.id) || link
|
|
1225
|
+
if (!linkIds.has(link.id) || !isDependencyHalf(link)) continue;
|
|
829
1226
|
for (const end of [link.source, link.target]) if (model.elements.get(end)?.isDependum && !elementIds.has(end)) {
|
|
830
1227
|
elementIds.add(end);
|
|
831
1228
|
changed = true;
|
|
@@ -844,14 +1241,18 @@ function removeCascade(model, elementIds, linkIds) {
|
|
|
844
1241
|
}
|
|
845
1242
|
/** Adds a link between two elements if the iStar constraints allow it. */
|
|
846
1243
|
function connect(model, input, options = {}) {
|
|
847
|
-
|
|
1244
|
+
const m = any(model);
|
|
1245
|
+
const metamodel = metaOf(m);
|
|
1246
|
+
const kind = input.kind;
|
|
1247
|
+
const definition = metamodel.links.get(kind);
|
|
1248
|
+
if (definition?.category === "dependency") throw new ModelOperationError("use addDependency to create dependencies");
|
|
848
1249
|
let { source, target } = input;
|
|
849
1250
|
let reversed = false;
|
|
850
1251
|
if (!options.force) {
|
|
851
|
-
let check = canLink(
|
|
852
|
-
const tryReversed = options.tryReversed ??
|
|
1252
|
+
let check = canLink(m, source, target, kind);
|
|
1253
|
+
const tryReversed = options.tryReversed ?? definition?.info.tryReversedWhenAdding;
|
|
853
1254
|
if (!check.ok && tryReversed) {
|
|
854
|
-
const swapped = canLink(
|
|
1255
|
+
const swapped = canLink(m, target, source, kind);
|
|
855
1256
|
if (swapped.ok) {
|
|
856
1257
|
[source, target] = [target, source];
|
|
857
1258
|
reversed = true;
|
|
@@ -860,14 +1261,15 @@ function connect(model, input, options = {}) {
|
|
|
860
1261
|
}
|
|
861
1262
|
if (!check.ok) return check;
|
|
862
1263
|
} else {
|
|
863
|
-
|
|
864
|
-
requireElement(
|
|
1264
|
+
if (!definition) throw new ModelOperationError(`unknown link kind "${kind}" in ${metamodel.name}`);
|
|
1265
|
+
requireElement(m, source);
|
|
1266
|
+
requireElement(m, target);
|
|
865
1267
|
}
|
|
866
1268
|
const id = input.id ?? (options.createId ?? defaultIdGenerator)();
|
|
867
|
-
if (
|
|
1269
|
+
if (m.elements.has(id) || m.links.has(id)) throw new ModelOperationError(`id "${id}" is already in use`);
|
|
868
1270
|
const link = withoutUndefined({
|
|
869
1271
|
id,
|
|
870
|
-
kind
|
|
1272
|
+
kind,
|
|
871
1273
|
source,
|
|
872
1274
|
target,
|
|
873
1275
|
label: input.label,
|
|
@@ -876,23 +1278,29 @@ function connect(model, input, options = {}) {
|
|
|
876
1278
|
});
|
|
877
1279
|
return {
|
|
878
1280
|
ok: true,
|
|
879
|
-
model: derive(
|
|
1281
|
+
model: typed(derive(m, { links: new Map(m.links).set(id, link) })),
|
|
880
1282
|
link,
|
|
881
1283
|
reversed
|
|
882
1284
|
};
|
|
883
1285
|
}
|
|
884
1286
|
/** Adds depender → dependum → dependee. */
|
|
885
1287
|
function addDependency(model, input, options = {}) {
|
|
886
|
-
|
|
1288
|
+
const m = any(model);
|
|
1289
|
+
const metamodel = metaOf(m);
|
|
1290
|
+
const dependumKind = input.dependum.kind;
|
|
1291
|
+
const dependumDefinition = metamodel.elements.get(dependumKind);
|
|
1292
|
+
if (dependumDefinition?.category !== "node" || dependumDefinition.info?.canBeDependum === false) throw new ModelOperationError(`${dependumKind} cannot be a dependum`);
|
|
1293
|
+
const linkKind = input.linkKind ?? "istar.DependencyLink";
|
|
1294
|
+
if (metamodel.links.get(linkKind)?.category !== "dependency") throw new ModelOperationError(`${linkKind} is not a dependency link kind`);
|
|
887
1295
|
if (!options.force) {
|
|
888
|
-
const check = canLink(
|
|
1296
|
+
const check = canLink(m, input.depender, input.dependee, linkKind);
|
|
889
1297
|
if (!check.ok) return check;
|
|
890
1298
|
}
|
|
891
|
-
const depender = requireElement(
|
|
892
|
-
const dependee = requireElement(
|
|
1299
|
+
const depender = requireElement(m, input.depender);
|
|
1300
|
+
const dependee = requireElement(m, input.dependee);
|
|
893
1301
|
const createId = options.createId ?? defaultIdGenerator;
|
|
894
|
-
const added = addElement(
|
|
895
|
-
kind:
|
|
1302
|
+
const added = addElement(m, {
|
|
1303
|
+
kind: dependumKind,
|
|
896
1304
|
id: input.dependum.id,
|
|
897
1305
|
name: input.dependum.name,
|
|
898
1306
|
x: input.dependum.x ?? (depender.x + dependee.x) / 2,
|
|
@@ -906,37 +1314,39 @@ function addDependency(model, input, options = {}) {
|
|
|
906
1314
|
const [id1, id2] = input.linkIds ?? [createId(), createId()];
|
|
907
1315
|
const first = {
|
|
908
1316
|
id: id1,
|
|
909
|
-
kind:
|
|
1317
|
+
kind: linkKind,
|
|
910
1318
|
source: depender.id,
|
|
911
1319
|
target: dependum.id
|
|
912
1320
|
};
|
|
913
1321
|
const second = {
|
|
914
1322
|
id: id2,
|
|
915
|
-
kind:
|
|
1323
|
+
kind: linkKind,
|
|
916
1324
|
source: dependum.id,
|
|
917
1325
|
target: dependee.id
|
|
918
1326
|
};
|
|
919
1327
|
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
1328
|
return {
|
|
921
1329
|
ok: true,
|
|
922
|
-
model: derive(
|
|
1330
|
+
model: typed(derive(m, {
|
|
923
1331
|
elements: new Map(added.model.elements).set(dependum.id, dependum),
|
|
924
|
-
links: new Map(
|
|
925
|
-
}),
|
|
1332
|
+
links: new Map(m.links).set(id1, first).set(id2, second)
|
|
1333
|
+
})),
|
|
926
1334
|
dependum,
|
|
927
1335
|
links: [first, second]
|
|
928
1336
|
};
|
|
929
1337
|
}
|
|
930
1338
|
/** Removes a link. Removing either half of a dependency removes the whole dependency. */
|
|
931
1339
|
function disconnect(model, id) {
|
|
932
|
-
|
|
933
|
-
|
|
1340
|
+
const m = any(model);
|
|
1341
|
+
requireLink(m, id);
|
|
1342
|
+
return typed(removeCascade(m, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set([id])));
|
|
934
1343
|
}
|
|
935
1344
|
function pick(value, fallback) {
|
|
936
1345
|
return value === void 0 ? fallback : value ?? void 0;
|
|
937
1346
|
}
|
|
938
1347
|
function updateLink(model, id, patch) {
|
|
939
|
-
const
|
|
1348
|
+
const m = any(model);
|
|
1349
|
+
const current = requireLink(m, id);
|
|
940
1350
|
const next = withoutUndefined({
|
|
941
1351
|
...current,
|
|
942
1352
|
label: pick(patch.label, current.label),
|
|
@@ -944,13 +1354,14 @@ function updateLink(model, id, patch) {
|
|
|
944
1354
|
customProperties: pick(patch.customProperties, current.customProperties),
|
|
945
1355
|
display: mergeDisplay(current.display, patch.display)
|
|
946
1356
|
});
|
|
947
|
-
return derive(
|
|
1357
|
+
return typed(derive(m, { links: new Map(m.links).set(id, next) }));
|
|
948
1358
|
}
|
|
949
1359
|
function updateDiagram(model, patch) {
|
|
950
|
-
|
|
951
|
-
|
|
1360
|
+
const m = any(model);
|
|
1361
|
+
return typed(derive(m, { diagram: withoutUndefined({
|
|
1362
|
+
...m.diagram,
|
|
952
1363
|
...patch
|
|
953
|
-
}) });
|
|
1364
|
+
}) }));
|
|
954
1365
|
}
|
|
955
1366
|
/** Where dependency depender/dependee currently point (see `dependencyLinksOf`). */
|
|
956
1367
|
function dependencyEnds(model, dependumId) {
|
|
@@ -963,7 +1374,8 @@ function dependencyEnds(model, dependumId) {
|
|
|
963
1374
|
}
|
|
964
1375
|
//#endregion
|
|
965
1376
|
//#region src/store.ts
|
|
966
|
-
function createModelStore(initial
|
|
1377
|
+
function createModelStore(initial, options = {}) {
|
|
1378
|
+
initial ??= options.metamodel ? createEmptyModel(void 0, { metamodel: options.metamodel }) : createEmptyModel();
|
|
967
1379
|
const limit = options.historyLimit ?? 100;
|
|
968
1380
|
const ctx = { createId: options.createId ?? defaultIdGenerator };
|
|
969
1381
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -1420,6 +1832,6 @@ function validateModelProperties(model, schemas) {
|
|
|
1420
1832
|
return issues;
|
|
1421
1833
|
}
|
|
1422
1834
|
//#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 };
|
|
1835
|
+
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, isActorKind, isActorKindIn, isActorLinkKind, isContributionLabel, isDependencyLinkKind, isElementKind, isLinkKind, isNode, isNodeKind, isNodeKindIn, isNodeLinkKind, linkKindDefinition, linksOf, metamodelOf, moveElement, nestElement, number, parsePistar, prop, removeElement, removeElements, shortKindName, string, toPistar, toPistarObject, updateDiagram, updateElement, updateLink, validateModel, validateModelProperties, withMetamodel };
|
|
1424
1836
|
|
|
1425
1837
|
//# sourceMappingURL=index.js.map
|