libpetri 2.3.2 → 2.5.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/dist/{chunk-B2CV5M3L.js → chunk-2GAZLDH3.js} +26 -8
- package/dist/chunk-2GAZLDH3.js.map +1 -0
- package/dist/debug/index.d.ts +2 -2
- package/dist/debug/index.js +1 -1
- package/dist/doclet/index.d.ts +1 -1
- package/dist/doclet/index.js +2 -2
- package/dist/doclet/resources/petrinet-diagrams.js +21 -21
- package/dist/dot-exporter-7YDFCBKC.js +8 -0
- package/dist/{event-store-I3QiQABh.d.ts → event-store-z30VMn0T.d.ts} +1 -1
- package/dist/export/index.d.ts +22 -2
- package/dist/export/index.js +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1085 -932
- package/dist/index.js.map +1 -1
- package/dist/{petri-net-CKZYPOsL.d.ts → petri-net-DXdpd0r5.d.ts} +66 -1
- package/dist/verification/index.d.ts +2 -2
- package/dist/viewer/index.d.ts +10 -4
- package/dist/viewer/index.js +76 -4
- package/dist/viewer/index.js.map +1 -1
- package/dist/viewer/viewer.iife.js +21 -21
- package/package.json +1 -1
- package/dist/chunk-B2CV5M3L.js.map +0 -1
- package/dist/dot-exporter-WJMCJEFK.js +0 -8
- /package/dist/{dot-exporter-WJMCJEFK.js.map → dot-exporter-7YDFCBKC.js.map} +0 -0
|
@@ -1297,8 +1297,20 @@ declare class PetriNet {
|
|
|
1297
1297
|
readonly name: string;
|
|
1298
1298
|
readonly places: ReadonlySet<Place<any>>;
|
|
1299
1299
|
readonly transitions: ReadonlySet<Transition>;
|
|
1300
|
+
/**
|
|
1301
|
+
* Subnet-membership metadata per **MOD-026**: maps each node name (place or
|
|
1302
|
+
* transition) contributed by exactly one directly-composed subnet to that
|
|
1303
|
+
* subnet's name. Shared places contributed by two or more subnets, and every
|
|
1304
|
+
* node of a net not built via {@link PetriNetBuilder.compose}`(SubnetDef)`,
|
|
1305
|
+
* are absent. Never null — an empty map when there is no metadata. The DOT
|
|
1306
|
+
* exporter renders `subgraph cluster_*` blocks from this map.
|
|
1307
|
+
*
|
|
1308
|
+
* V8 `Map` is insertion-ordered, preserving compose order so cluster
|
|
1309
|
+
* subgraphs render deterministically (cross-language byte-parity).
|
|
1310
|
+
*/
|
|
1311
|
+
readonly subnetMembership: ReadonlyMap<string, string>;
|
|
1300
1312
|
/** @internal Use {@link PetriNet.builder} to create instances. */
|
|
1301
|
-
constructor(key: symbol, name: string, places: ReadonlySet<Place<any>>, transitions: ReadonlySet<Transition>);
|
|
1313
|
+
constructor(key: symbol, name: string, places: ReadonlySet<Place<any>>, transitions: ReadonlySet<Transition>, subnetMembership?: ReadonlyMap<string, string>);
|
|
1302
1314
|
/**
|
|
1303
1315
|
* Creates a new PetriNet with actions bound to transitions by name.
|
|
1304
1316
|
* Unbound transitions keep passthrough action.
|
|
@@ -1315,6 +1327,7 @@ declare class PetriNetBuilder {
|
|
|
1315
1327
|
private readonly _places;
|
|
1316
1328
|
private readonly _transitions;
|
|
1317
1329
|
private readonly _fusionSets;
|
|
1330
|
+
private readonly _subnetContributions;
|
|
1318
1331
|
constructor(name: string);
|
|
1319
1332
|
/** Add an explicit place. */
|
|
1320
1333
|
place(place: Place<any>): this;
|
|
@@ -1363,6 +1376,42 @@ declare class PetriNetBuilder {
|
|
|
1363
1376
|
compose(instance: Instance<unknown>): this;
|
|
1364
1377
|
compose(instance: Instance<unknown>, portMappings: ReadonlyMap<string, Place<unknown>> | Record<string, Place<unknown>>): this;
|
|
1365
1378
|
compose(instance: Instance<unknown>, bind: (b: ComposeBindings) => void): this;
|
|
1379
|
+
compose(def: SubnetDef<unknown>): this;
|
|
1380
|
+
/**
|
|
1381
|
+
* Composes a subnet {@link SubnetDef} **directly** into this builder per
|
|
1382
|
+
* **MOD-025** — **without instantiation**, and without the prefix-renaming
|
|
1383
|
+
* of {@link SubnetDef.instantiate}.
|
|
1384
|
+
*
|
|
1385
|
+
* Every body place and transition is added under its **original**
|
|
1386
|
+
* (un-prefixed) name. Places merge into this builder by name: a body place
|
|
1387
|
+
* whose name equals an enclosing-net place *is* that place in the composed
|
|
1388
|
+
* flat net. This is the mode for wiring a subnet in as a single shared copy.
|
|
1389
|
+
*
|
|
1390
|
+
* Direct composition is **order-independent**: composing the same set of
|
|
1391
|
+
* subnets in any order yields the same flat net, because merging is by
|
|
1392
|
+
* place name and not by a probe of the builder's place set at call time —
|
|
1393
|
+
* contrast the no-interface body-inference branch of {@link composeAuto}
|
|
1394
|
+
* (MOD-024), which is order-sensitive.
|
|
1395
|
+
*
|
|
1396
|
+
* For multiple *independent* copies — each with isolated per-instance state
|
|
1397
|
+
* per [MOD-012] — use {@link SubnetDef.instantiate} + the `compose(instance)`
|
|
1398
|
+
* overload instead.
|
|
1399
|
+
*
|
|
1400
|
+
* Rejections: a body transition whose name already exists in this builder
|
|
1401
|
+
* (use `instantiate(prefix)` for independent copies); a subnet whose
|
|
1402
|
+
* interface declares any channel (direct composition does not bind
|
|
1403
|
+
* channels — use `instantiate` + the channel-binding `compose` overload).
|
|
1404
|
+
*
|
|
1405
|
+
* Token-type conflicts on a same-named place cannot be detected: TS
|
|
1406
|
+
* {@link Place} equality is name-only at runtime (the documented carve-out,
|
|
1407
|
+
* same as MOD-024).
|
|
1408
|
+
*
|
|
1409
|
+
* @throws when the subnet declares channels, or a body transition name
|
|
1410
|
+
* collides with a transition already in this builder.
|
|
1411
|
+
*/
|
|
1412
|
+
private composeDirect;
|
|
1413
|
+
/** @internal MOD-026: records a node as contributed by the named subnet. */
|
|
1414
|
+
private recordContribution;
|
|
1366
1415
|
/**
|
|
1367
1416
|
* Identity-default auto-compose per **MOD-024**.
|
|
1368
1417
|
*
|
|
@@ -1481,6 +1530,22 @@ declare class PetriNetBuilder {
|
|
|
1481
1530
|
* @throws when two fusion sets share a place
|
|
1482
1531
|
*/
|
|
1483
1532
|
build(): PetriNet;
|
|
1533
|
+
/**
|
|
1534
|
+
* @internal Resolves the per-compose contributions recorded by
|
|
1535
|
+
* `composeDirect` into the final node-name → subnet-name membership map per
|
|
1536
|
+
* **MOD-026**. A node contributed by exactly one subnet maps to that subnet;
|
|
1537
|
+
* a place contributed by two or more subnets is a shared rendezvous place
|
|
1538
|
+
* and is omitted (it renders top-level, outside any cluster). Returns an
|
|
1539
|
+
* empty map — the common case — when no subnet was composed directly.
|
|
1540
|
+
*/
|
|
1541
|
+
private resolveSubnetMembership;
|
|
1542
|
+
/**
|
|
1543
|
+
* @internal Drops membership entries for places removed by fusion: a
|
|
1544
|
+
* non-canonical fused member no longer exists in the net, so its
|
|
1545
|
+
* `node-name → subnet` entry would dangle. The surviving canonical place
|
|
1546
|
+
* keeps its own entry. Per **MOD-026**.
|
|
1547
|
+
*/
|
|
1548
|
+
private static filterFusedMembership;
|
|
1484
1549
|
/**
|
|
1485
1550
|
* @internal Fusion-resolution pass per **MOD-061**. Split out from
|
|
1486
1551
|
* {@link build} so the no-fusion fast path stays trivial.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { b as Transition, a as Place, P as PetriNet, E as EnvironmentPlace, aG as MarkingState, aH as PInvariant, aI as MarkingStateBuilder, aJ as SmtProperty, aK as SmtVerificationResult } from '../petri-net-
|
|
2
|
-
export { aL as DeadlockFree, aM as MutualExclusion, aN as PlaceBound, aO as Proven, aP as SmtStatistics, aQ as TokenSupplier, aR as Unknown, aS as Unreachable, aT as Verdict, X as VerificationHarness, Y as VerificationResult, aU as Violated, aV as deadlockFree, aW as isProven, aX as isViolated, aY as mutualExclusion, aZ as pInvariant, a_ as pInvariantToString, a$ as placeBound, b0 as propertyDescription, b1 as unreachable } from '../petri-net-
|
|
1
|
+
import { b as Transition, a as Place, P as PetriNet, E as EnvironmentPlace, aG as MarkingState, aH as PInvariant, aI as MarkingStateBuilder, aJ as SmtProperty, aK as SmtVerificationResult } from '../petri-net-DXdpd0r5.js';
|
|
2
|
+
export { aL as DeadlockFree, aM as MutualExclusion, aN as PlaceBound, aO as Proven, aP as SmtStatistics, aQ as TokenSupplier, aR as Unknown, aS as Unreachable, aT as Verdict, X as VerificationHarness, Y as VerificationResult, aU as Violated, aV as deadlockFree, aW as isProven, aX as isViolated, aY as mutualExclusion, aZ as pInvariant, a_ as pInvariantToString, a$ as placeBound, b0 as propertyDescription, b1 as unreachable } from '../petri-net-DXdpd0r5.js';
|
|
3
3
|
import { Expr, init, Bool, FuncDecl } from 'z3-solver';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/viewer/index.d.ts
CHANGED
|
@@ -63,10 +63,16 @@ interface ClusterDescriptor {
|
|
|
63
63
|
*
|
|
64
64
|
* Graphviz emits cluster nodes as SIBLINGS of the cluster `<g>`, not as
|
|
65
65
|
* children — `<g class="cluster">` only carries title, polygon border, and
|
|
66
|
-
* the cluster's text label.
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
66
|
+
* the cluster's text label. Cluster membership is recovered two ways:
|
|
67
|
+
*
|
|
68
|
+
* - **Name-based** — instance composition prefixes interior names
|
|
69
|
+
* (`p_<prefix>_…`, `t_<prefix>_…`, `j_<prefix>_…`) or uses slash-separated
|
|
70
|
+
* names (`<prefix>/…`).
|
|
71
|
+
* - **Geometric** — direct composition (spec MOD-026) keeps each node's
|
|
72
|
+
* original, un-prefixed name, so the only structural signal left in the
|
|
73
|
+
* rendered SVG is the node's drawn position: a node whose centre falls
|
|
74
|
+
* inside the cluster's border box belongs to it. This is what Graphviz
|
|
75
|
+
* itself draws the box around.
|
|
70
76
|
*
|
|
71
77
|
* Does not split nested-cluster prefixes (`outer_inner` could mean either
|
|
72
78
|
* `outer/inner` or a flat `outer_inner` prefix — Graphviz sanitisation is
|
package/dist/viewer/index.js
CHANGED
|
@@ -30,13 +30,14 @@ function discoverClusters(svg) {
|
|
|
30
30
|
if (!raw.startsWith("cluster_")) continue;
|
|
31
31
|
const prefix = raw.slice("cluster_".length);
|
|
32
32
|
if (out.has(prefix)) continue;
|
|
33
|
+
const rect = clusterRect(g);
|
|
33
34
|
let nodeCount = 0;
|
|
34
35
|
for (const node of allNodes) {
|
|
35
36
|
if (node.getAttribute("data-instance")) continue;
|
|
36
37
|
const nodeTitle = directChild(node, "title");
|
|
37
38
|
if (!nodeTitle) continue;
|
|
38
39
|
const id = (nodeTitle.textContent ?? "").trim();
|
|
39
|
-
if (
|
|
40
|
+
if (nodeBelongsToCluster(id, node, prefix, rect)) {
|
|
40
41
|
node.setAttribute("data-instance", prefix);
|
|
41
42
|
nodeCount++;
|
|
42
43
|
}
|
|
@@ -50,9 +51,77 @@ function discoverClusters(svg) {
|
|
|
50
51
|
}
|
|
51
52
|
return out;
|
|
52
53
|
}
|
|
54
|
+
function nodeBelongsToCluster(nodeId, node, prefix, rect) {
|
|
55
|
+
if (nodeBelongsToPrefix(nodeId, prefix)) return true;
|
|
56
|
+
if (rect) {
|
|
57
|
+
const c = nodeCenter(node);
|
|
58
|
+
if (c && c.x >= rect.minX && c.x <= rect.maxX && c.y >= rect.minY && c.y <= rect.maxY) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
53
64
|
function nodeBelongsToPrefix(nodeId, prefix) {
|
|
54
65
|
return nodeId === prefix || nodeId.startsWith(`${prefix}/`) || nodeId.startsWith(`p_${prefix}_`) || nodeId.startsWith(`t_${prefix}_`) || nodeId.startsWith(`j_${prefix}_`);
|
|
55
66
|
}
|
|
67
|
+
function clusterRect(group) {
|
|
68
|
+
const border = directChild(group, "polygon") ?? directChild(group, "path");
|
|
69
|
+
if (!border) return null;
|
|
70
|
+
const raw = border.getAttribute("points") ?? border.getAttribute("d") ?? "";
|
|
71
|
+
return boundsOf(raw);
|
|
72
|
+
}
|
|
73
|
+
function nodeCenter(node) {
|
|
74
|
+
const ellipse = directChild(node, "ellipse");
|
|
75
|
+
if (ellipse) {
|
|
76
|
+
const x = parseFloat(ellipse.getAttribute("cx") ?? "");
|
|
77
|
+
const y = parseFloat(ellipse.getAttribute("cy") ?? "");
|
|
78
|
+
if (!Number.isNaN(x) && !Number.isNaN(y)) return { x, y };
|
|
79
|
+
}
|
|
80
|
+
const shape = directChild(node, "polygon") ?? directChild(node, "path");
|
|
81
|
+
if (shape) {
|
|
82
|
+
const bounds = boundsOf(
|
|
83
|
+
shape.getAttribute("points") ?? shape.getAttribute("d") ?? ""
|
|
84
|
+
);
|
|
85
|
+
if (bounds) {
|
|
86
|
+
return {
|
|
87
|
+
x: (bounds.minX + bounds.maxX) / 2,
|
|
88
|
+
y: (bounds.minY + bounds.maxY) / 2
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
function boundsOf(coords) {
|
|
95
|
+
const nums = coords.match(/-?\d+(?:\.\d+)?/g);
|
|
96
|
+
if (!nums || nums.length < 2) return null;
|
|
97
|
+
let minX = Infinity;
|
|
98
|
+
let minY = Infinity;
|
|
99
|
+
let maxX = -Infinity;
|
|
100
|
+
let maxY = -Infinity;
|
|
101
|
+
for (let i = 0; i + 1 < nums.length; i += 2) {
|
|
102
|
+
const x = parseFloat(nums[i]);
|
|
103
|
+
const y = parseFloat(nums[i + 1]);
|
|
104
|
+
if (x < minX) minX = x;
|
|
105
|
+
if (x > maxX) maxX = x;
|
|
106
|
+
if (y < minY) minY = y;
|
|
107
|
+
if (y > maxY) maxY = y;
|
|
108
|
+
}
|
|
109
|
+
if (minX === Infinity) return null;
|
|
110
|
+
return { minX, minY, maxX, maxY };
|
|
111
|
+
}
|
|
112
|
+
function nodeClusterMap(svg) {
|
|
113
|
+
const map = /* @__PURE__ */ new Map();
|
|
114
|
+
svg.querySelectorAll("g.node[data-instance]").forEach((node) => {
|
|
115
|
+
const di = node.getAttribute("data-instance");
|
|
116
|
+
const title = directChild(node, "title");
|
|
117
|
+
const id = (title?.textContent ?? "").trim();
|
|
118
|
+
if (di && id) map.set(id, di);
|
|
119
|
+
});
|
|
120
|
+
return map;
|
|
121
|
+
}
|
|
122
|
+
function endpointInCluster(id, prefix, clusterMap) {
|
|
123
|
+
return nodeBelongsToPrefix(id, prefix) || clusterMap.get(id) === prefix;
|
|
124
|
+
}
|
|
56
125
|
function colorForPrefix(prefix) {
|
|
57
126
|
let h = 2166136261;
|
|
58
127
|
for (let i = 0; i < prefix.length; i++) {
|
|
@@ -84,6 +153,7 @@ function setClusterCollapsed(cluster, collapsed) {
|
|
|
84
153
|
node.classList.add("petri-collapsed-inside");
|
|
85
154
|
hidden.push(node);
|
|
86
155
|
});
|
|
156
|
+
const clusterMap = nodeClusterMap(root);
|
|
87
157
|
root.querySelectorAll("g.edge").forEach((edge) => {
|
|
88
158
|
const titleEl = directChild(edge, "title");
|
|
89
159
|
if (!titleEl) return;
|
|
@@ -92,7 +162,7 @@ function setClusterCollapsed(cluster, collapsed) {
|
|
|
92
162
|
if (arrow < 0) return;
|
|
93
163
|
const from = t.slice(0, arrow);
|
|
94
164
|
const to = t.slice(arrow + 2);
|
|
95
|
-
if (
|
|
165
|
+
if (endpointInCluster(from, cluster.prefix, clusterMap) && endpointInCluster(to, cluster.prefix, clusterMap)) {
|
|
96
166
|
edge.classList.add("petri-collapsed-inside");
|
|
97
167
|
hidden.push(edge);
|
|
98
168
|
}
|
|
@@ -135,6 +205,7 @@ function applyFilter(svg, prefix) {
|
|
|
135
205
|
}
|
|
136
206
|
svg.setAttribute("data-active-filter", prefix);
|
|
137
207
|
svg.classList.add("has-active-filter");
|
|
208
|
+
const clusterMap = nodeClusterMap(svg);
|
|
138
209
|
svg.querySelectorAll("g.node").forEach((node) => {
|
|
139
210
|
const di = node.getAttribute("data-instance");
|
|
140
211
|
const match = !!di && (di === prefix || di.indexOf(prefix + "_") === 0 || di.indexOf(prefix + "/") === 0);
|
|
@@ -152,7 +223,7 @@ function applyFilter(svg, prefix) {
|
|
|
152
223
|
}
|
|
153
224
|
const from = titleText.slice(0, arrow);
|
|
154
225
|
const to = titleText.slice(arrow + 2);
|
|
155
|
-
const matches = (name) => name.indexOf(prefix + "/") >= 0 || name.indexOf(prefix + "_") >= 0 || name === prefix;
|
|
226
|
+
const matches = (name) => name.indexOf(prefix + "/") >= 0 || name.indexOf(prefix + "_") >= 0 || name === prefix || clusterMap.get(name) === prefix;
|
|
156
227
|
if (matches(from) || matches(to)) edge.classList.remove("petri-dimmed");
|
|
157
228
|
else edge.classList.add("petri-dimmed");
|
|
158
229
|
});
|
|
@@ -160,9 +231,10 @@ function applyFilter(svg, prefix) {
|
|
|
160
231
|
function isInsideCollapsedCluster(svg, clusters, collapsedPrefixes, graphId) {
|
|
161
232
|
if (collapsedPrefixes.size === 0) return false;
|
|
162
233
|
if (!svg) return false;
|
|
234
|
+
const clusterMap = nodeClusterMap(svg);
|
|
163
235
|
for (const prefix of collapsedPrefixes) {
|
|
164
236
|
if (!clusters.has(prefix)) continue;
|
|
165
|
-
if (
|
|
237
|
+
if (endpointInCluster(graphId, prefix, clusterMap)) return true;
|
|
166
238
|
}
|
|
167
239
|
return false;
|
|
168
240
|
}
|