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