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