@istar-ts/core 0.13.3 → 0.13.5
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 +8 -1
- package/dist/index.cjs +70 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +69 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,7 +35,14 @@ writeFileSync('model.pistar', toPistar(model));
|
|
|
35
35
|
- Files saved by the piStar web tool round-trip **byte-identically** through
|
|
36
36
|
`toPistar(parsePistar(file))` when you do not edit the model.
|
|
37
37
|
- Pass `{ saveDate: new Date() }` when saving an edited model (piStar updates `saveDate` on save).
|
|
38
|
-
Omit it to preserve the model’s existing `saveDate` on a load/save cycle
|
|
38
|
+
Omit it to preserve the model’s existing `saveDate` on a load/save cycle; a file that had none
|
|
39
|
+
keeps none until a save passes one.
|
|
40
|
+
- A model's metamodel and layout are kept beside it, in registries shared by every copy of
|
|
41
|
+
`@istar-ts/core` in the process (ESM and CommonJS builds alike), so a model parsed by one copy
|
|
42
|
+
is read correctly by another.
|
|
43
|
+
- A parsed file is written back with its own key order at every level (top level, each element,
|
|
44
|
+
link, hub entry and the diagram), so files other tools wrote stay byte-identical. `inheritSourceLayout`
|
|
45
|
+
carries that over to an edited model; the store does it on every edit.
|
|
39
46
|
|
|
40
47
|
```ts
|
|
41
48
|
toPistar(editedModel, { saveDate: new Date() });
|
package/dist/index.cjs
CHANGED
|
@@ -551,6 +551,26 @@ function elementBehavesLike(metamodel, kind, ancestor) {
|
|
|
551
551
|
return false;
|
|
552
552
|
}
|
|
553
553
|
//#endregion
|
|
554
|
+
//#region src/shared.ts
|
|
555
|
+
/**
|
|
556
|
+
* Registries the library keeps beside models (their metamodel, their file's layout) are
|
|
557
|
+
* shared across every copy of `@istar-ts/core` loaded in a process. A bundle may load the
|
|
558
|
+
* package twice, once as ESM and once as CommonJS (an application parsing with the ESM build
|
|
559
|
+
* while a CommonJS dependency reads the model with the CJS build): a module-level WeakMap in
|
|
560
|
+
* each copy would make a model parsed by one copy unknown to the other, so `metamodelOf`
|
|
561
|
+
* would fall back to iStar 2.0 there. The registries live on `globalThis` under one symbol
|
|
562
|
+
* instead, keyed by name, and every copy finds the same ones.
|
|
563
|
+
*/
|
|
564
|
+
const REGISTRIES = Symbol.for("@istar-ts/core/registries");
|
|
565
|
+
/** The WeakMap named `name`, the same object in every copy of the library in this process. */
|
|
566
|
+
function sharedWeakMap(name) {
|
|
567
|
+
const host = globalThis;
|
|
568
|
+
const registries = host[REGISTRIES] ??= /* @__PURE__ */ new Map();
|
|
569
|
+
let map = registries.get(name);
|
|
570
|
+
if (!map) registries.set(name, map = /* @__PURE__ */ new WeakMap());
|
|
571
|
+
return map;
|
|
572
|
+
}
|
|
573
|
+
//#endregion
|
|
554
574
|
//#region src/model.ts
|
|
555
575
|
/** Tool identifier written by piStar 2.1.0, whose file format this library emulates. */
|
|
556
576
|
const PISTAR_TOOL = "pistar.2.1.0";
|
|
@@ -581,7 +601,7 @@ const DEFAULT_DIAGRAM = {
|
|
|
581
601
|
* so a model's shape (and `JSON.stringify`, deep equality, snapshots) is the same as before
|
|
582
602
|
* metamodels existed. Operations and the store carry it to every model they derive.
|
|
583
603
|
*/
|
|
584
|
-
const metamodels =
|
|
604
|
+
const metamodels = sharedWeakMap("metamodels");
|
|
585
605
|
/**
|
|
586
606
|
* Associates `metamodel` with `model` and returns the same model, typed for it. Use when a
|
|
587
607
|
* model was built without the library's operations (e.g. with an object spread), which loses
|
|
@@ -596,7 +616,7 @@ function withMetamodel(model, metamodel) {
|
|
|
596
616
|
* For models whose file declares part of their metamodel (see `withFileMetamodel`): the
|
|
597
617
|
* metamodel the host supplied, without the file's kinds.
|
|
598
618
|
*/
|
|
599
|
-
const hostMetamodels =
|
|
619
|
+
const hostMetamodels = sharedWeakMap("hostMetamodels");
|
|
600
620
|
/** @internal Records the host part of a model's metamodel (used by serialization). */
|
|
601
621
|
function setHostMetamodel(model, metamodel) {
|
|
602
622
|
if (metamodel) hostMetamodels.set(model, metamodel);
|
|
@@ -1235,7 +1255,13 @@ var PistarParseError = class extends Error {
|
|
|
1235
1255
|
var PistarWriteError = class extends Error {
|
|
1236
1256
|
name = "PistarWriteError";
|
|
1237
1257
|
};
|
|
1238
|
-
|
|
1258
|
+
/** The `objects` key of a hub's entry (`{ hub, starts, end }`) in a `SourceLayout`. */
|
|
1259
|
+
function hubEntryLayoutKey(hubId) {
|
|
1260
|
+
return `${hubId}\u0000entry`;
|
|
1261
|
+
}
|
|
1262
|
+
/** The `objects` key of the diagram in a `SourceLayout`. */
|
|
1263
|
+
const DIAGRAM_LAYOUT_KEY = "\0diagram";
|
|
1264
|
+
const layouts = sharedWeakMap("layouts");
|
|
1239
1265
|
/** The key order recorded when `model` was parsed, if it came from `parsePistar`. */
|
|
1240
1266
|
function getSourceLayout(model) {
|
|
1241
1267
|
return layouts.get(model);
|
|
@@ -1342,6 +1368,7 @@ function parsePistar(input, options = {}) {
|
|
|
1342
1368
|
usedDisplay.add(id);
|
|
1343
1369
|
return entry;
|
|
1344
1370
|
};
|
|
1371
|
+
const objects = /* @__PURE__ */ new Map();
|
|
1345
1372
|
const readElement = (raw, where, known, extras) => {
|
|
1346
1373
|
if (!isRecord(raw)) throw new PistarParseError(`${where}: expected an object`);
|
|
1347
1374
|
const { id, type } = raw;
|
|
@@ -1361,6 +1388,7 @@ function parsePistar(input, options = {}) {
|
|
|
1361
1388
|
...optional("extra", extraOf(raw, known))
|
|
1362
1389
|
};
|
|
1363
1390
|
elements.set(id, element);
|
|
1391
|
+
objects.set(id, Object.keys(raw));
|
|
1364
1392
|
return element;
|
|
1365
1393
|
};
|
|
1366
1394
|
asArray(json.actors, "actors").forEach((raw, i) => {
|
|
@@ -1405,12 +1433,14 @@ function parsePistar(input, options = {}) {
|
|
|
1405
1433
|
...optional("extra", extraOf(raw, LINK_KEYS))
|
|
1406
1434
|
};
|
|
1407
1435
|
links.set(id, link);
|
|
1436
|
+
objects.set(id, Object.keys(raw));
|
|
1408
1437
|
return link;
|
|
1409
1438
|
};
|
|
1410
1439
|
const readHubEntry = (raw, where) => {
|
|
1411
1440
|
const hub = readElement(raw.hub, `${where}.hub`, HUB_KEYS, {});
|
|
1412
1441
|
const hubKind = metamodel.elements.get(hub.kind);
|
|
1413
1442
|
if (hubKind?.role !== "hub") throw new PistarParseError(`${where}.hub (${hub.id}): ${hub.kind} is not a hub kind`);
|
|
1443
|
+
objects.set(hubEntryLayoutKey(hub.id), Object.keys(raw));
|
|
1414
1444
|
const entryExtra = extraOf(raw, HUB_ENTRY_KEYS);
|
|
1415
1445
|
if (entryExtra) elements.set(hub.id, {
|
|
1416
1446
|
...hub,
|
|
@@ -1448,6 +1478,7 @@ function parsePistar(input, options = {}) {
|
|
|
1448
1478
|
...isRecord(d.customProperties) ? { customProperties: d.customProperties } : {},
|
|
1449
1479
|
...optional("extra", extraOf(d, DIAGRAM_KEYS))
|
|
1450
1480
|
};
|
|
1481
|
+
objects.set(DIAGRAM_LAYOUT_KEY, Object.keys(d));
|
|
1451
1482
|
}
|
|
1452
1483
|
const model = {
|
|
1453
1484
|
elements,
|
|
@@ -1461,7 +1492,8 @@ function parsePistar(input, options = {}) {
|
|
|
1461
1492
|
};
|
|
1462
1493
|
layouts.set(model, {
|
|
1463
1494
|
keys: Object.keys(json),
|
|
1464
|
-
display: Object.keys(display)
|
|
1495
|
+
display: Object.keys(display),
|
|
1496
|
+
objects
|
|
1465
1497
|
});
|
|
1466
1498
|
setHostMetamodel(model, host);
|
|
1467
1499
|
return withMetamodel(model, metamodel);
|
|
@@ -1534,6 +1566,19 @@ function pistarTypeOf(type, kind, metamodel) {
|
|
|
1534
1566
|
if (type === void 0) throw new PistarWriteError(`kind "${kind}" is not part of ${metamodel.name}; pass its metamodel (toPistar(model, { metamodel }) or withMetamodel)`);
|
|
1535
1567
|
return type;
|
|
1536
1568
|
}
|
|
1569
|
+
/**
|
|
1570
|
+
* `value` with its keys in the order `keys` gives (those present), then the rest as built (piStar's
|
|
1571
|
+
* order): an object read from a file is written back with the file's key order, and keys the
|
|
1572
|
+
* file lacked follow.
|
|
1573
|
+
*/
|
|
1574
|
+
function inSourceOrder(value, keys) {
|
|
1575
|
+
if (!keys) return value;
|
|
1576
|
+
const result = {};
|
|
1577
|
+
const record = value;
|
|
1578
|
+
for (const key of keys) if (key in record) result[key] = record[key];
|
|
1579
|
+
for (const key of Object.keys(record)) if (!(key in result)) result[key] = record[key];
|
|
1580
|
+
return result;
|
|
1581
|
+
}
|
|
1537
1582
|
function elementJson(element, metamodel) {
|
|
1538
1583
|
return {
|
|
1539
1584
|
id: element.id,
|
|
@@ -1554,38 +1599,40 @@ function toPistarObject(input, options = {}) {
|
|
|
1554
1599
|
const orphans = [];
|
|
1555
1600
|
const dependencies = [];
|
|
1556
1601
|
const children = /* @__PURE__ */ new Map();
|
|
1602
|
+
const layout = layouts.get(model);
|
|
1603
|
+
const sourceOrder = (id, value) => inSourceOrder(value, layout?.objects?.get(id));
|
|
1557
1604
|
for (const element of model.elements.values()) {
|
|
1558
1605
|
if (isHub(element)) continue;
|
|
1559
1606
|
if (element.parent !== void 0 && !isActorKind(element.kind)) {
|
|
1560
1607
|
let list = children.get(element.parent);
|
|
1561
1608
|
if (!list) children.set(element.parent, list = []);
|
|
1562
|
-
list.push({
|
|
1609
|
+
list.push(sourceOrder(element.id, {
|
|
1563
1610
|
...elementJson(element, metamodel),
|
|
1564
1611
|
...element.extra
|
|
1565
|
-
});
|
|
1612
|
+
}));
|
|
1566
1613
|
}
|
|
1567
1614
|
}
|
|
1568
1615
|
for (const element of model.elements.values()) {
|
|
1569
1616
|
if (isHub(element)) continue;
|
|
1570
|
-
if (isActorKind(element.kind)) actors.push({
|
|
1617
|
+
if (isActorKind(element.kind)) actors.push(sourceOrder(element.id, {
|
|
1571
1618
|
...elementJson(element, metamodel),
|
|
1572
1619
|
nodes: children.get(element.id) ?? [],
|
|
1573
1620
|
...element.extra
|
|
1574
|
-
});
|
|
1621
|
+
}));
|
|
1575
1622
|
else if (element.isDependum) {
|
|
1576
1623
|
const { inbound, outbound } = dependencyLinksOf(model, element.id);
|
|
1577
|
-
dependencies.push({
|
|
1624
|
+
dependencies.push(sourceOrder(element.id, {
|
|
1578
1625
|
...elementJson(element, metamodel),
|
|
1579
1626
|
source: inbound?.source ?? element.dependency?.source ?? "",
|
|
1580
1627
|
target: outbound?.target ?? element.dependency?.target ?? "",
|
|
1581
1628
|
...element.extra
|
|
1582
|
-
});
|
|
1583
|
-
} else if (element.parent === void 0 || !model.elements.has(element.parent)) orphans.push({
|
|
1629
|
+
}));
|
|
1630
|
+
} else if (element.parent === void 0 || !model.elements.has(element.parent)) orphans.push(sourceOrder(element.id, {
|
|
1584
1631
|
...elementJson(element, metamodel),
|
|
1585
1632
|
...element.extra
|
|
1586
|
-
});
|
|
1633
|
+
}));
|
|
1587
1634
|
}
|
|
1588
|
-
const linkJson = (link) => ({
|
|
1635
|
+
const linkJson = (link) => sourceOrder(link.id, {
|
|
1589
1636
|
id: link.id,
|
|
1590
1637
|
type: pistarTypeOf(metamodel.links.get(link.kind)?.pistarType, link.kind, metamodel),
|
|
1591
1638
|
source: link.source,
|
|
@@ -1617,19 +1664,19 @@ function toPistarObject(input, options = {}) {
|
|
|
1617
1664
|
const hubEntry = (hub) => {
|
|
1618
1665
|
const parts = hubs.get(hub.id) ?? { legs: [] };
|
|
1619
1666
|
const { [HUB_ENTRY_KEY]: entryExtra, ...hubExtra } = hub.extra ?? {};
|
|
1620
|
-
return {
|
|
1621
|
-
hub: {
|
|
1667
|
+
return sourceOrder(hubEntryLayoutKey(hub.id), {
|
|
1668
|
+
hub: sourceOrder(hub.id, {
|
|
1622
1669
|
id: hub.id,
|
|
1623
1670
|
type: pistarTypeOf(metamodel.elements.get(hub.kind)?.pistarType, hub.kind, metamodel),
|
|
1624
1671
|
x: hub.x,
|
|
1625
1672
|
y: hub.y,
|
|
1626
1673
|
...hub.customProperties ? { customProperties: { ...hub.customProperties } } : {},
|
|
1627
1674
|
...hubExtra
|
|
1628
|
-
},
|
|
1675
|
+
}),
|
|
1629
1676
|
starts: parts.legs.map(linkJson),
|
|
1630
1677
|
...parts.end ? { end: linkJson(parts.end) } : {},
|
|
1631
1678
|
...isRecord(entryExtra) ? entryExtra : {}
|
|
1632
|
-
};
|
|
1679
|
+
});
|
|
1633
1680
|
};
|
|
1634
1681
|
const links = [];
|
|
1635
1682
|
const written = /* @__PURE__ */ new Set();
|
|
@@ -1643,7 +1690,6 @@ function toPistarObject(input, options = {}) {
|
|
|
1643
1690
|
}
|
|
1644
1691
|
}
|
|
1645
1692
|
for (const element of model.elements.values()) if (isHub(element) && !written.has(element.id)) links.push(hubEntry(element));
|
|
1646
|
-
const layout = layouts.get(model);
|
|
1647
1693
|
const display = {};
|
|
1648
1694
|
const displayOf = (id) => {
|
|
1649
1695
|
const owner = model.elements.get(id) ?? model.links.get(id);
|
|
@@ -1658,15 +1704,16 @@ function toPistarObject(input, options = {}) {
|
|
|
1658
1704
|
for (const [id, entry] of Object.entries(model.extraDisplay ?? {})) if (!(id in display)) display[id] = entry;
|
|
1659
1705
|
let saveDate;
|
|
1660
1706
|
if (options.saveDate instanceof Date) saveDate = formatDate(options.saveDate);
|
|
1661
|
-
else
|
|
1707
|
+
else if (options.saveDate !== void 0 || model.saveDate !== void 0) saveDate = options.saveDate ?? model.saveDate;
|
|
1708
|
+
else if (!layout || layout.keys.includes("saveDate")) saveDate = formatDate(/* @__PURE__ */ new Date());
|
|
1662
1709
|
const d = model.diagram;
|
|
1663
|
-
const diagram = d && {
|
|
1710
|
+
const diagram = d && sourceOrder("\0diagram", {
|
|
1664
1711
|
...d.width !== void 0 ? { width: d.width } : {},
|
|
1665
1712
|
...d.height !== void 0 ? { height: d.height } : {},
|
|
1666
1713
|
...d.name !== void 0 ? { name: d.name } : {},
|
|
1667
1714
|
...d.customProperties ? { customProperties: { ...d.customProperties } } : {},
|
|
1668
1715
|
...d.extra
|
|
1669
|
-
};
|
|
1716
|
+
});
|
|
1670
1717
|
const values = {
|
|
1671
1718
|
...model.extra,
|
|
1672
1719
|
actors,
|
|
@@ -2851,6 +2898,7 @@ exports.DEFAULT_DIAGRAM = DEFAULT_DIAGRAM;
|
|
|
2851
2898
|
exports.DEFAULT_ELEMENT_SIZE = DEFAULT_ELEMENT_SIZE;
|
|
2852
2899
|
exports.DEPENDENCY_LINK_KINDS = DEPENDENCY_LINK_KINDS;
|
|
2853
2900
|
exports.DIAGNOSTIC_SEVERITIES = DIAGNOSTIC_SEVERITIES;
|
|
2901
|
+
exports.DIAGRAM_LAYOUT_KEY = DIAGRAM_LAYOUT_KEY;
|
|
2854
2902
|
exports.DSM3_FILE_KEYS = DSM3_FILE_KEYS;
|
|
2855
2903
|
exports.DSM3_TOOL = DSM3_TOOL;
|
|
2856
2904
|
exports.ELEMENT_KINDS = ELEMENT_KINDS;
|
|
@@ -2909,6 +2957,7 @@ exports.fromNodeIdDiagnostics = fromNodeIdDiagnostics;
|
|
|
2909
2957
|
exports.getSourceLayout = getSourceLayout;
|
|
2910
2958
|
exports.groupDiagnostics = groupDiagnostics;
|
|
2911
2959
|
exports.hostMetamodelOf = hostMetamodelOf;
|
|
2960
|
+
exports.hubEntryLayoutKey = hubEntryLayoutKey;
|
|
2912
2961
|
exports.hubLinkOf = hubLinkOf;
|
|
2913
2962
|
exports.hubOfLink = hubOfLink;
|
|
2914
2963
|
exports.inheritMetamodel = inheritMetamodel;
|