process-graph 1.0.5 → 1.1.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 +85 -17
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +92 -0
- package/dist/cli.d.ts +512 -0
- package/dist/cli.js +3 -0
- package/dist/control-aDk9560d.js +294 -0
- package/dist/detail-panel-B1ZmMuJT.js +341 -0
- package/dist/dist-BpCBmnMl.js +582 -0
- package/dist/dist-HLlW8vgT.js +1388 -0
- package/dist/dist-IjrgWhHQ.js +3 -0
- package/dist/errors-BZe5WzCn.js +19 -0
- package/dist/exit-codes-0ultCqdS.js +23 -0
- package/dist/graphql-C1j5wQFz.d.ts +631 -0
- package/dist/index-CCfe0csl.d.ts +353 -0
- package/dist/index-CmZOswkx.d.ts +512 -0
- package/dist/index-CoA6vXqe.d.ts +404 -0
- package/dist/index-CptUHq6F.d.ts +58 -0
- package/dist/index-CrM5qWUA.d.ts +110 -0
- package/dist/index-DhbvDgK4.d.ts +1582 -0
- package/dist/index-DricKcyl.d.ts +421 -0
- package/dist/index.d.ts +111 -4
- package/dist/index.js +929 -5
- package/dist/layout-CCM-KBdp.js +307 -0
- package/dist/layout.d.ts +8 -0
- package/dist/layout.js +2 -0
- package/dist/node-AchI1T-K.js +40 -0
- package/dist/open-CyRXJ4bt.js +29 -0
- package/dist/parse-CuipC_2K.js +41 -0
- package/dist/queries-CCexJAE2.js +2202 -0
- package/dist/react-flow/style.css +658 -0
- package/dist/react-flow.d.ts +173 -0
- package/dist/react-flow.js +450 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.js +107 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/router-D4gtTlXm.js +188 -0
- package/dist/schema-CVnS6HiX.js +234 -0
- package/dist/selection-BW_xXWOy.js +314 -0
- package/dist/source/github/node.d.ts +16 -0
- package/dist/source/github/node.js +2 -0
- package/dist/source/github.d.ts +49 -0
- package/dist/source/github.js +201 -0
- package/dist/source-DRDpEA8Q.js +1316 -0
- package/dist/status-Bm1CGu9C.js +11 -0
- package/dist/store/node.d.ts +24 -0
- package/dist/store/node.js +2 -0
- package/dist/store/wasm/worker.d.ts +16 -0
- package/dist/store/wasm/worker.js +98 -0
- package/dist/store/wasm.d.ts +9 -0
- package/dist/store/wasm.js +11 -0
- package/dist/store-BcR70CaO.js +1287 -0
- package/dist/store.d.ts +1328 -0
- package/dist/store.js +238 -0
- package/dist/ui/style.css +351 -0
- package/dist/ui.d.ts +235 -0
- package/dist/ui.js +2 -0
- package/dist/validate-DF3mQZww.js +125 -0
- package/package.json +148 -13
package/dist/index.js
CHANGED
|
@@ -1,5 +1,929 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
+
import { a as validateOverlay, c as fragmentOperators, d as overlayStatuses, f as participantKinds, i as parseOverlay, l as messageModes, n as OverlayParseError, p as pseudoRoles, r as overlaySchema, s as elementKinds, t as modelSchema, u as notations } from "./schema-CVnS6HiX.js";
|
|
3
|
+
import { t as validate } from "./validate-DF3mQZww.js";
|
|
4
|
+
import { n as parseModel, t as ModelParseError } from "./parse-CuipC_2K.js";
|
|
5
|
+
import { C as topologicalLayers, S as immediateDominators, _ as toSwimlane, a as unmetOutputs, b as handoffs, c as deriveStatuses, d as artefactKey, f as elementStatusKey, g as toStateMachine, h as stateMachineParts, i as readySet, l as statusRules, m as toSequence, n as criticalPath, o as whatIfDone, p as evaluateBranches, r as impact, s as explainStatus, t as blockedBy, u as UnsupportedConditionError, v as flowchartParts, w as actorMap, x as criticalPath$1, y as toFlowchart } from "./queries-CCexJAE2.js";
|
|
6
|
+
import { a as findCycles, c as descendantsOf, d as lookup, f as CycleError, i as assertAcyclic, l as assembleDag, n as illegalCycles, o as stronglyConnectedComponents, p as UnknownNodeError, r as loopTransitions, s as ancestorsOf, t as controlDag, u as buildDag } from "./control-aDk9560d.js";
|
|
7
|
+
import { t as statuses } from "./status-Bm1CGu9C.js";
|
|
8
|
+
//#region ../../core/graph/schema/dist/artefacts.js
|
|
9
|
+
/**
|
|
10
|
+
* The artefacts the process takes as given: those no activity produces, in declaration order.
|
|
11
|
+
* @param model - The model to read.
|
|
12
|
+
* @returns The declared artefacts that appear in no activity's `produces`.
|
|
13
|
+
*/
|
|
14
|
+
const inputsOf = (model) => {
|
|
15
|
+
const produced = new Set(model.elements.flatMap((element) => element.kind === "activity" ? element.produces ?? [] : []));
|
|
16
|
+
return model.artefacts.filter((artefact) => !produced.has(artefact.id));
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The artefacts the process exists to deliver: those flagged `deliverable`, in declaration order.
|
|
20
|
+
* @param model - The model to read.
|
|
21
|
+
* @returns The declared artefacts flagged `deliverable`.
|
|
22
|
+
*/
|
|
23
|
+
const outputsOf = (model) => model.artefacts.filter((artefact) => artefact.deliverable === true);
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region ../../core/graph/schema/dist/model.js
|
|
26
|
+
/**
|
|
27
|
+
* Declare a model. Each identifier union is inferred from the declaring arrays only, so every reference elsewhere is checked against what was declared.
|
|
28
|
+
*
|
|
29
|
+
* The participants array is captured whole, with each kind kept as a literal. Actors and message ends are then checked against the role and system participants, and a state machine subject against the entity participants.
|
|
30
|
+
*/
|
|
31
|
+
const defineModel = (model) => model;
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region ../../core/graph/dag/dist/reduction.js
|
|
34
|
+
/**
|
|
35
|
+
* The transitive reduction: the same nodes with every redundant edge removed, so a node keeps a direct need only when no other need of it already depends on that node, transitively.
|
|
36
|
+
*
|
|
37
|
+
* Defined for acyclic graphs, where it is unique and has exactly the same ancestors and descendants as the input. Each node's remaining needs keep the order they had. Unknown references are carried over unchanged; they are not edges and take no part in reduction.
|
|
38
|
+
*
|
|
39
|
+
* Builds each node's full set of ancestors, so memory grows with the size of the transitive closure.
|
|
40
|
+
* @throws CycleError when the graph contains a cycle: with one the reduction is not unique, so nothing is guessed.
|
|
41
|
+
*/
|
|
42
|
+
function transitiveReduction(dag) {
|
|
43
|
+
assertAcyclic(dag);
|
|
44
|
+
const ancestors = /* @__PURE__ */ new Map();
|
|
45
|
+
for (const layer of topologicalLayers(dag).layers) for (const id of layer) {
|
|
46
|
+
const closure = /* @__PURE__ */ new Set();
|
|
47
|
+
for (const need of lookup(dag.needs, id)) {
|
|
48
|
+
closure.add(need);
|
|
49
|
+
for (const further of lookup(ancestors, need)) closure.add(further);
|
|
50
|
+
}
|
|
51
|
+
ancestors.set(id, closure);
|
|
52
|
+
}
|
|
53
|
+
const reduced = /* @__PURE__ */ new Map();
|
|
54
|
+
for (const id of dag.nodes) {
|
|
55
|
+
const direct = lookup(dag.needs, id);
|
|
56
|
+
reduced.set(id, direct.filter((candidate) => !direct.some((other) => lookup(ancestors, other).has(candidate))));
|
|
57
|
+
}
|
|
58
|
+
return assembleDag(dag.nodes, reduced, dag.unknownReferences);
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region ../../core/graph/dag/dist/index.js
|
|
62
|
+
var dist_exports = /* @__PURE__ */ __exportAll({
|
|
63
|
+
CycleError: () => CycleError,
|
|
64
|
+
UnknownNodeError: () => UnknownNodeError,
|
|
65
|
+
ancestorsOf: () => ancestorsOf,
|
|
66
|
+
buildDag: () => buildDag,
|
|
67
|
+
criticalPath: () => criticalPath$1,
|
|
68
|
+
descendantsOf: () => descendantsOf,
|
|
69
|
+
findCycles: () => findCycles,
|
|
70
|
+
immediateDominators: () => immediateDominators,
|
|
71
|
+
stronglyConnectedComponents: () => stronglyConnectedComponents,
|
|
72
|
+
topologicalLayers: () => topologicalLayers,
|
|
73
|
+
transitiveReduction: () => transitiveReduction
|
|
74
|
+
});
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region ../../core/graph/projection/dist/diff.js
|
|
77
|
+
/** Thrown by `applyModelDiff` when the diff still holds changes the inverse inferred and nobody confirmed. */
|
|
78
|
+
var UnconfirmedChangeError = class extends Error {
|
|
79
|
+
/** The changes still marked `inferred`. */
|
|
80
|
+
changes;
|
|
81
|
+
constructor(changes) {
|
|
82
|
+
super(`The diff holds inferred changes that have not been confirmed: ${changes.map((change) => change.kind).join(", ")}`);
|
|
83
|
+
this.name = "UnconfirmedChangeError";
|
|
84
|
+
this.changes = changes;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Marks inferred changes as confirmed, so `applyModelDiff` will apply them.
|
|
89
|
+
* @param diff - A diff returned by one of the view inverses.
|
|
90
|
+
* @param accept - Chooses which inferred changes to confirm; the rest stay inferred.
|
|
91
|
+
* @returns A new diff; `diff` is not modified.
|
|
92
|
+
*/
|
|
93
|
+
const confirmChanges = (diff, accept) => ({
|
|
94
|
+
elements: diff.elements.map((change) => change.inferred && accept(change) ? {
|
|
95
|
+
...change,
|
|
96
|
+
inferred: false
|
|
97
|
+
} : change),
|
|
98
|
+
transitions: diff.transitions.map((change) => change.inferred && accept(change) ? {
|
|
99
|
+
...change,
|
|
100
|
+
inferred: false
|
|
101
|
+
} : change),
|
|
102
|
+
events: diff.events.map((change) => change.inferred && accept(change) ? {
|
|
103
|
+
...change,
|
|
104
|
+
inferred: false
|
|
105
|
+
} : change)
|
|
106
|
+
});
|
|
107
|
+
/** A copy of `value` without `key`, for clearing an optional field, which `exactOptionalPropertyTypes` will not let be set to `undefined`. */
|
|
108
|
+
const without = (value, key) => {
|
|
109
|
+
const copy = { ...value };
|
|
110
|
+
Reflect.deleteProperty(copy, key);
|
|
111
|
+
return copy;
|
|
112
|
+
};
|
|
113
|
+
/** Applies one change to the element it names; additions and removals are applied to the element list instead. */
|
|
114
|
+
const applyElementChange = (element, change) => {
|
|
115
|
+
if (change.kind === "renamed") {
|
|
116
|
+
if (element.kind === "pseudo") throw new Error(`Element "${element.id}" is a pseudo-state and has no label to rename`);
|
|
117
|
+
return {
|
|
118
|
+
...element,
|
|
119
|
+
label: change.to
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (change.kind === "relaned") {
|
|
123
|
+
if (element.kind !== "activity" && element.kind !== "decision") throw new Error(`Element "${element.id}" is a ${element.kind} and has no actor to move`);
|
|
124
|
+
return change.to === void 0 ? without(element, "actor") : {
|
|
125
|
+
...element,
|
|
126
|
+
actor: change.to
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (change.kind === "readdressed") {
|
|
130
|
+
if (element.kind !== "message") throw new Error(`Element "${element.id}" is a ${element.kind}, not a message`);
|
|
131
|
+
return {
|
|
132
|
+
...element,
|
|
133
|
+
from: change.to.from,
|
|
134
|
+
to: change.to.to
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return element;
|
|
138
|
+
};
|
|
139
|
+
/** Applies one change to the transition it names; additions and removals are applied to the transition list instead. */
|
|
140
|
+
const applyTransitionChange = (transition, change) => {
|
|
141
|
+
if (change.kind === "rewired") return {
|
|
142
|
+
...transition,
|
|
143
|
+
...change.to
|
|
144
|
+
};
|
|
145
|
+
if (change.kind !== "changed") return transition;
|
|
146
|
+
if (change.field === "guard") return change.to === void 0 ? without(transition, "guard") : {
|
|
147
|
+
...transition,
|
|
148
|
+
guard: change.to
|
|
149
|
+
};
|
|
150
|
+
if (change.field === "trigger") return change.to === void 0 ? without(transition, "trigger") : {
|
|
151
|
+
...transition,
|
|
152
|
+
trigger: change.to
|
|
153
|
+
};
|
|
154
|
+
return change.to === void 0 ? without(transition, "effect") : {
|
|
155
|
+
...transition,
|
|
156
|
+
effect: change.to
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
/** The items of `items` grouped by the key `keyOf` gives each, every group in the order of `items`; one pass, so looking a group up afterwards costs nothing per item. */
|
|
160
|
+
const groupBy = (items, keyOf) => {
|
|
161
|
+
const groups = /* @__PURE__ */ new Map();
|
|
162
|
+
for (const item of items) {
|
|
163
|
+
const key = keyOf(item);
|
|
164
|
+
const group = groups.get(key);
|
|
165
|
+
if (group === void 0) groups.set(key, [item]);
|
|
166
|
+
else group.push(item);
|
|
167
|
+
}
|
|
168
|
+
return groups;
|
|
169
|
+
};
|
|
170
|
+
/** The identifier a change is about, used to find what it applies to. */
|
|
171
|
+
const subjectOf = (change) => change.kind === "added" ? change.element.id : change.element;
|
|
172
|
+
const transitionSubjectOf = (change) => change.kind === "added" ? change.transition.id : change.transition;
|
|
173
|
+
/**
|
|
174
|
+
* Applies a confirmed diff to the model, returning a new model.
|
|
175
|
+
*
|
|
176
|
+
* Removing an element also takes it out of fragment bodies and recorded positions; removing a transition also takes it out of every sequence scenario. Transitions that touched a removed element are only removed when the diff says so, as every view inverse does, so a diff built by hand that leaves one dangling yields a model `validate` reports.
|
|
177
|
+
* @param model - The model the diff was computed against.
|
|
178
|
+
* @param diff - The changes, with every inferred one confirmed.
|
|
179
|
+
* @returns The changed model; `model` is not modified.
|
|
180
|
+
* @throws UnconfirmedChangeError when a change is still marked `inferred`.
|
|
181
|
+
* @throws Error when a change names an element, transition or event the model does not have, or asks for something the element's kind cannot carry, such as a label on a pseudo-state.
|
|
182
|
+
*/
|
|
183
|
+
const applyModelDiff = (model, diff) => {
|
|
184
|
+
const unconfirmed = [
|
|
185
|
+
...diff.elements,
|
|
186
|
+
...diff.transitions,
|
|
187
|
+
...diff.events
|
|
188
|
+
].filter((change) => change.inferred);
|
|
189
|
+
if (unconfirmed.length > 0) throw new UnconfirmedChangeError(unconfirmed);
|
|
190
|
+
const elementIds = new Set(model.elements.map((element) => element.id));
|
|
191
|
+
for (const change of diff.elements) if (change.kind !== "added" && !elementIds.has(subjectOf(change))) throw new Error(`Element "${subjectOf(change)}" is not in the model`);
|
|
192
|
+
const transitionIds = new Set(model.transitions.map((transition) => transition.id));
|
|
193
|
+
for (const change of diff.transitions) {
|
|
194
|
+
const id = transitionSubjectOf(change);
|
|
195
|
+
if (change.kind !== "added" && !transitionIds.has(id)) throw new Error(`Transition "${id}" is not in the model`);
|
|
196
|
+
}
|
|
197
|
+
const eventIds = new Set(model.events.map((event) => event.id));
|
|
198
|
+
for (const change of diff.events) if (!eventIds.has(change.event)) throw new Error(`Event "${change.event}" is not in the model`);
|
|
199
|
+
const removedElements = new Set(diff.elements.flatMap((change) => change.kind === "removed" ? [change.element] : []));
|
|
200
|
+
const removedTransitions = new Set(diff.transitions.flatMap((change) => change.kind === "removed" ? [change.transition] : []));
|
|
201
|
+
const elementChanges = groupBy(diff.elements, subjectOf);
|
|
202
|
+
const transitionChanges = groupBy(diff.transitions, transitionSubjectOf);
|
|
203
|
+
const eventChanges = groupBy(diff.events, (change) => change.event);
|
|
204
|
+
const elements = [...model.elements.filter((element) => !removedElements.has(element.id)).map((element) => (elementChanges.get(element.id) ?? []).reduce(applyElementChange, element)), ...diff.elements.flatMap((change) => change.kind === "added" ? [change.element] : [])];
|
|
205
|
+
const transitions = [...model.transitions.filter((transition) => !removedTransitions.has(transition.id)).map((transition) => (transitionChanges.get(transition.id) ?? []).reduce(applyTransitionChange, transition)), ...diff.transitions.flatMap((change) => change.kind === "added" ? [change.transition] : [])];
|
|
206
|
+
const events = model.events.map((event) => (eventChanges.get(event.id) ?? []).reduce((renamed, change) => ({
|
|
207
|
+
...renamed,
|
|
208
|
+
name: change.to
|
|
209
|
+
}), event));
|
|
210
|
+
const fragments = model.fragments.map((fragment) => ({
|
|
211
|
+
...fragment,
|
|
212
|
+
operands: fragment.operands.map((operand) => ({
|
|
213
|
+
...operand,
|
|
214
|
+
body: operand.body.filter((id) => !removedElements.has(id))
|
|
215
|
+
}))
|
|
216
|
+
}));
|
|
217
|
+
const projections = model.projections.map((projection) => projection.notation === "sequence" ? {
|
|
218
|
+
...projection,
|
|
219
|
+
scenario: projection.scenario.filter((id) => !removedTransitions.has(id))
|
|
220
|
+
} : projection);
|
|
221
|
+
const views = model.views === void 0 ? void 0 : Object.fromEntries(Object.entries(model.views).map(([notation, positions]) => [notation, Object.fromEntries(Object.entries(positions).filter(([id]) => !removedElements.has(id)))]));
|
|
222
|
+
return {
|
|
223
|
+
...model,
|
|
224
|
+
elements,
|
|
225
|
+
transitions,
|
|
226
|
+
events,
|
|
227
|
+
fragments,
|
|
228
|
+
projections,
|
|
229
|
+
...views === void 0 ? {} : { views }
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region ../../core/graph/projection/dist/inverse-flowchart.js
|
|
234
|
+
const degreesOf = (edges) => {
|
|
235
|
+
const incoming = /* @__PURE__ */ new Map();
|
|
236
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
237
|
+
for (const edge of edges) {
|
|
238
|
+
incoming.set(edge.to, (incoming.get(edge.to) ?? 0) + 1);
|
|
239
|
+
outgoing.set(edge.from, (outgoing.get(edge.from) ?? 0) + 1);
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
in: incoming,
|
|
243
|
+
out: outgoing
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
/** What an added node becomes, with whether the inverse had to choose. A terminator is a start when nothing arrives at it and an end otherwise; a bar is a fork when it has more ways out than in and a join otherwise. */
|
|
247
|
+
const addedElement = (node, degrees, actor) => {
|
|
248
|
+
const owned = actor === void 0 ? {} : { actor };
|
|
249
|
+
if (node.shape === "process") return {
|
|
250
|
+
kind: "added",
|
|
251
|
+
element: {
|
|
252
|
+
kind: "activity",
|
|
253
|
+
id: node.id,
|
|
254
|
+
label: node.label,
|
|
255
|
+
...owned
|
|
256
|
+
},
|
|
257
|
+
inferred: false
|
|
258
|
+
};
|
|
259
|
+
if (node.shape === "decision") return {
|
|
260
|
+
kind: "added",
|
|
261
|
+
element: {
|
|
262
|
+
kind: "decision",
|
|
263
|
+
id: node.id,
|
|
264
|
+
label: node.label,
|
|
265
|
+
...owned
|
|
266
|
+
},
|
|
267
|
+
inferred: false
|
|
268
|
+
};
|
|
269
|
+
if (node.shape === "terminator") return {
|
|
270
|
+
kind: "added",
|
|
271
|
+
element: {
|
|
272
|
+
kind: "pseudo",
|
|
273
|
+
id: node.id,
|
|
274
|
+
role: (degrees.in.get(node.id) ?? 0) > 0 ? "final" : "initial"
|
|
275
|
+
},
|
|
276
|
+
inferred: true
|
|
277
|
+
};
|
|
278
|
+
const ins = degrees.in.get(node.id) ?? 0;
|
|
279
|
+
const outs = degrees.out.get(node.id) ?? 0;
|
|
280
|
+
return {
|
|
281
|
+
kind: "added",
|
|
282
|
+
element: {
|
|
283
|
+
kind: "pseudo",
|
|
284
|
+
id: node.id,
|
|
285
|
+
role: outs > ins ? "fork" : "join"
|
|
286
|
+
},
|
|
287
|
+
inferred: true
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* The element and transition changes shared by the flowchart and swimlane inverses. `actorOf` gives the actor an edited node is placed with, for the swimlane; the flowchart has none and passes a function that always returns `undefined`.
|
|
292
|
+
*/
|
|
293
|
+
const diffFlow = (model, edited, actorOf) => {
|
|
294
|
+
const parts = flowchartParts(model);
|
|
295
|
+
const original = parts.flowchart;
|
|
296
|
+
const elements = new Map(model.elements.map((element) => [element.id, element]));
|
|
297
|
+
const drawn = new Map(original.nodes.map((node) => [node.id, node]));
|
|
298
|
+
const kept = new Set(edited.nodes.map((node) => node.id));
|
|
299
|
+
const degrees = degreesOf(edited.edges);
|
|
300
|
+
const elementChanges = [];
|
|
301
|
+
for (const node of original.nodes) if (!kept.has(node.id)) elementChanges.push({
|
|
302
|
+
kind: "removed",
|
|
303
|
+
element: node.id,
|
|
304
|
+
inferred: false
|
|
305
|
+
});
|
|
306
|
+
for (const node of edited.nodes) {
|
|
307
|
+
const before = drawn.get(node.id);
|
|
308
|
+
if (before === void 0) {
|
|
309
|
+
if (elements.has(node.id)) throw new Error(`Node "${node.id}" is a model element the flowchart does not draw`);
|
|
310
|
+
elementChanges.push(addedElement(node, degrees, actorOf(node.id)));
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (before.shape !== node.shape) throw new Error(`Node "${node.id}" changed shape from ${before.shape} to ${node.shape}; an element's kind is its identity, so remove it and add one under a new id`);
|
|
314
|
+
if (before.label !== node.label) {
|
|
315
|
+
if (node.shape === "terminator" || node.shape === "bar") throw new Error(`Node "${node.id}" is a ${node.shape}, whose label the projection supplies and the model does not hold`);
|
|
316
|
+
elementChanges.push({
|
|
317
|
+
kind: "renamed",
|
|
318
|
+
element: node.id,
|
|
319
|
+
from: before.label,
|
|
320
|
+
to: node.label,
|
|
321
|
+
inferred: false
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const transitionChanges = [];
|
|
326
|
+
const transitions = new Map(model.transitions.map((transition) => [transition.id, transition]));
|
|
327
|
+
const keyOf = (first, target) => JSON.stringify([first, target]);
|
|
328
|
+
const originals = /* @__PURE__ */ new Map();
|
|
329
|
+
for (const [position, origin] of parts.origins.entries()) {
|
|
330
|
+
const edge = original.edges[position];
|
|
331
|
+
if (edge !== void 0) originals.set(keyOf(origin.first.id, origin.target), {
|
|
332
|
+
edge,
|
|
333
|
+
origin
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
/** An edited edge's key: its first transition and the node one of its transitions arrives at, or `undefined` for an edge whose first transition is new. */
|
|
337
|
+
const editedKey = (edge) => {
|
|
338
|
+
const [first] = edge.transitions;
|
|
339
|
+
if (first === void 0 || !transitions.has(first)) return;
|
|
340
|
+
for (const id of edge.transitions) {
|
|
341
|
+
const target = transitions.get(id)?.target;
|
|
342
|
+
if (target !== void 0 && drawn.has(target)) return keyOf(first, target);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
const editedKeys = new Set(edited.edges.map(editedKey));
|
|
346
|
+
const removed = /* @__PURE__ */ new Set();
|
|
347
|
+
const endsLeft = /* @__PURE__ */ new Set();
|
|
348
|
+
for (const [key, { origin }] of originals) {
|
|
349
|
+
if (editedKeys.has(key)) continue;
|
|
350
|
+
if (origin.first.target !== origin.target) endsLeft.add(origin.target);
|
|
351
|
+
else if (!removed.has(origin.first.id)) {
|
|
352
|
+
removed.add(origin.first.id);
|
|
353
|
+
transitionChanges.push({
|
|
354
|
+
kind: "removed",
|
|
355
|
+
transition: origin.first.id,
|
|
356
|
+
inferred: false
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
for (const end of endsLeft) for (const transition of parts.index.arriving(end)) if (!removed.has(transition.id)) {
|
|
361
|
+
removed.add(transition.id);
|
|
362
|
+
transitionChanges.push({
|
|
363
|
+
kind: "removed",
|
|
364
|
+
transition: transition.id,
|
|
365
|
+
inferred: true
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
const movedEnds = /* @__PURE__ */ new Map();
|
|
369
|
+
for (const edge of edited.edges) {
|
|
370
|
+
const key = editedKey(edge);
|
|
371
|
+
const before = key === void 0 ? void 0 : originals.get(key);
|
|
372
|
+
const [first] = edge.transitions;
|
|
373
|
+
if (before === void 0) {
|
|
374
|
+
if (first === void 0 || edge.transitions.length > 1 || transitions.has(first) || edge.through !== void 0) throw new Error(`Edge ${JSON.stringify(edge.transitions)} is neither an edge of the model nor a new edge with a single new transition id`);
|
|
375
|
+
transitionChanges.push({
|
|
376
|
+
kind: "added",
|
|
377
|
+
transition: {
|
|
378
|
+
id: first,
|
|
379
|
+
source: edge.from,
|
|
380
|
+
target: edge.to,
|
|
381
|
+
...edge.label === void 0 ? {} : { guard: edge.label }
|
|
382
|
+
},
|
|
383
|
+
inferred: false
|
|
384
|
+
});
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const { origin } = before;
|
|
388
|
+
const head = origin.first;
|
|
389
|
+
if (!(head.target !== origin.target)) {
|
|
390
|
+
if (before.edge.from !== edge.from || before.edge.to !== edge.to) transitionChanges.push({
|
|
391
|
+
kind: "rewired",
|
|
392
|
+
transition: head.id,
|
|
393
|
+
from: {
|
|
394
|
+
source: head.source,
|
|
395
|
+
target: head.target
|
|
396
|
+
},
|
|
397
|
+
to: {
|
|
398
|
+
source: edge.from,
|
|
399
|
+
target: edge.to
|
|
400
|
+
},
|
|
401
|
+
inferred: false
|
|
402
|
+
});
|
|
403
|
+
if (before.edge.label !== edge.label) transitionChanges.push({
|
|
404
|
+
kind: "changed",
|
|
405
|
+
field: "guard",
|
|
406
|
+
transition: head.id,
|
|
407
|
+
from: head.guard,
|
|
408
|
+
to: edge.label,
|
|
409
|
+
inferred: false
|
|
410
|
+
});
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (before.edge.label !== edge.label) throw new Error(`Edge ${JSON.stringify(edge.transitions)} carries a message; edit the message or its steps rather than the edge's label`);
|
|
414
|
+
if (head.source !== edge.from) transitionChanges.push({
|
|
415
|
+
kind: "rewired",
|
|
416
|
+
transition: head.id,
|
|
417
|
+
from: {
|
|
418
|
+
source: head.source,
|
|
419
|
+
target: head.target
|
|
420
|
+
},
|
|
421
|
+
to: {
|
|
422
|
+
source: edge.from,
|
|
423
|
+
target: head.target
|
|
424
|
+
},
|
|
425
|
+
inferred: false
|
|
426
|
+
});
|
|
427
|
+
if (origin.target === edge.to) continue;
|
|
428
|
+
const already = movedEnds.get(origin.target);
|
|
429
|
+
if (already === edge.to) continue;
|
|
430
|
+
if (already !== void 0) throw new Error(`Edges ending at "${origin.target}" through the same steps were moved to different ends, "${already}" and "${edge.to}"`);
|
|
431
|
+
movedEnds.set(origin.target, edge.to);
|
|
432
|
+
for (const tail of parts.index.arriving(origin.target)) transitionChanges.push({
|
|
433
|
+
kind: "rewired",
|
|
434
|
+
transition: tail.id,
|
|
435
|
+
from: {
|
|
436
|
+
source: tail.source,
|
|
437
|
+
target: tail.target
|
|
438
|
+
},
|
|
439
|
+
to: {
|
|
440
|
+
source: tail.source,
|
|
441
|
+
target: edge.to
|
|
442
|
+
},
|
|
443
|
+
inferred: true
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
elements: elementChanges,
|
|
448
|
+
transitions: transitionChanges
|
|
449
|
+
};
|
|
450
|
+
};
|
|
451
|
+
/**
|
|
452
|
+
* The inverse of `toFlowchart`: what an edited flowchart changes in the model, matched by element and transition id.
|
|
453
|
+
*
|
|
454
|
+
* A node not in the projection is an added element: a process an activity, a decision a decision, both certain; a terminator or bar a pseudo-state whose role is inferred from its edges. A projected node missing from the edit is removed, and a changed label renames the element. An edge is matched by where it comes from: the transition it starts with and the node one of its transitions arrives at. A new edge must carry one new transition id and is an added transition; a projected edge missing from the edit removes its transition; moved ends rewire it; a changed label changes its guard. An edge that carries messages stands for every route through them, so its earlier steps are shared with other edges: moving its source rewires its first transition, which is certain, while removing it or moving its target acts on each step from a message into its end, once however many edges share them, and is inferred.
|
|
455
|
+
* @param model - The model the flowchart was projected from.
|
|
456
|
+
* @param edited - The projected flowchart after editing. Its `loss` is not read.
|
|
457
|
+
* @returns The changes, with no event changes, since a flowchart draws no events.
|
|
458
|
+
* @throws Error when the edit cannot be expressed in the model: a node that is a model element the flowchart does not draw, a changed shape, a relabelled terminator or bar, an unmatched edge that is not a single new transition, a relabelled edge that carries a message, or edges sharing their last steps moved to different ends.
|
|
459
|
+
*/
|
|
460
|
+
const diffFlowchart = (model, edited) => ({
|
|
461
|
+
...diffFlow(model, edited, () => void 0),
|
|
462
|
+
events: []
|
|
463
|
+
});
|
|
464
|
+
/**
|
|
465
|
+
* The inverse of `toSwimlane`: the flowchart changes plus the lane moves. A node is placed with the actor of the lane it is in, or with none when it is in `unassigned`; an activity or decision whose actor differs from the model's is relaned, and an added activity or decision takes its lane's actor. Hand-offs are derived and not read.
|
|
466
|
+
* @param model - The model the swimlane was projected from.
|
|
467
|
+
* @param edited - The projected swimlane after editing.
|
|
468
|
+
* @returns The changes; see `diffFlowchart` for nodes and edges.
|
|
469
|
+
* @throws Error in the cases `diffFlowchart` does, when a lane belongs to a participant that cannot act, or when a pseudo-state is put in a lane.
|
|
470
|
+
*/
|
|
471
|
+
const diffSwimlane = (model, edited) => {
|
|
472
|
+
const actors = /* @__PURE__ */ new Set([...model.participants.flatMap((participant) => participant.kind === "entity" ? [] : [participant.id]), ...model.elements.flatMap((element) => (element.kind === "activity" || element.kind === "decision") && element.actor !== void 0 ? [element.actor] : [])]);
|
|
473
|
+
const canAct = (participant) => actors.has(participant);
|
|
474
|
+
const placed = /* @__PURE__ */ new Map();
|
|
475
|
+
for (const lane of edited.lanes) {
|
|
476
|
+
if (!canAct(lane.participant)) throw new Error(`Lane "${lane.participant}" belongs to a participant that cannot act`);
|
|
477
|
+
for (const node of lane.nodes) placed.set(node, lane.participant);
|
|
478
|
+
}
|
|
479
|
+
const flow = diffFlow(model, edited, (node) => placed.get(node));
|
|
480
|
+
const relaned = [];
|
|
481
|
+
const shapes = new Map(edited.nodes.map((node) => [node.id, node.shape]));
|
|
482
|
+
for (const node of placed.keys()) {
|
|
483
|
+
const shape = shapes.get(node);
|
|
484
|
+
if (shape === "terminator" || shape === "bar") throw new Error(`Node "${node}" is a pseudo-state and has no actor to put in a lane`);
|
|
485
|
+
}
|
|
486
|
+
for (const element of model.elements) {
|
|
487
|
+
if (element.kind !== "activity" && element.kind !== "decision" || !shapes.has(element.id)) continue;
|
|
488
|
+
const to = placed.get(element.id);
|
|
489
|
+
if (to !== element.actor) relaned.push({
|
|
490
|
+
kind: "relaned",
|
|
491
|
+
element: element.id,
|
|
492
|
+
from: element.actor,
|
|
493
|
+
to,
|
|
494
|
+
inferred: false
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
elements: [...flow.elements, ...relaned],
|
|
499
|
+
transitions: flow.transitions,
|
|
500
|
+
events: []
|
|
501
|
+
};
|
|
502
|
+
};
|
|
503
|
+
//#endregion
|
|
504
|
+
//#region ../../core/graph/projection/dist/inverse-state-machine.js
|
|
505
|
+
/** The element an added state becomes: an authored `state`, or an initial or final pseudo-state. */
|
|
506
|
+
const addedState = (state) => ({
|
|
507
|
+
kind: "added",
|
|
508
|
+
element: state.role === "state" ? {
|
|
509
|
+
kind: "state",
|
|
510
|
+
id: state.id,
|
|
511
|
+
label: state.label
|
|
512
|
+
} : {
|
|
513
|
+
kind: "pseudo",
|
|
514
|
+
id: state.id,
|
|
515
|
+
role: state.role
|
|
516
|
+
},
|
|
517
|
+
inferred: false
|
|
518
|
+
});
|
|
519
|
+
/**
|
|
520
|
+
* The inverse of `toStateMachine`: what an edited state machine changes in the model, matched by element and transition id.
|
|
521
|
+
*
|
|
522
|
+
* A state not in the projection is an added element: a `state` element for a plain state, or an initial or final pseudo-state. A projected state missing from the edit is removed, and a changed label renames the activity or state behind it.
|
|
523
|
+
*
|
|
524
|
+
* A drawn transition is matched by where it comes from: its first model transition and the state one of its model transitions arrives at. One that stands for a single transition maps straight back: moved ends rewire it, and a changed event, guard or effect changes that transition's trigger, guard or effect. One that stands for a chain through folded elements (decisions, activities that do not affect the subject, messages) shares its later steps with every other chain through the same elements, so the inverse has to choose: moving its source rewires the first step and is certain, but moving its target, changing its labels or removing it acts on each step from a folded element into its target state, once however many drawn transitions share it, and is marked `inferred`. A new transition must carry one new transition id and is added with its event, guard and effect.
|
|
525
|
+
* @param model - The model the machine was projected from.
|
|
526
|
+
* @param edited - The projected machine after editing; `subject` says which projection it was. Its `loss` is not read.
|
|
527
|
+
* @returns The changes, with no event changes, since a state machine names events by id.
|
|
528
|
+
* @throws Error when the edit cannot be expressed in the model: a state that is a model element the machine does not draw, a changed role, a relabelled initial or final state, an unmatched transition that is not a single new transition, or a changed effect where the effect is the target activity's action.
|
|
529
|
+
*/
|
|
530
|
+
const diffStateMachine = (model, edited) => {
|
|
531
|
+
const parts = stateMachineParts(model, edited.subject);
|
|
532
|
+
const original = parts.machine;
|
|
533
|
+
const elements = new Map(model.elements.map((element) => [element.id, element]));
|
|
534
|
+
const drawn = new Map(original.states.map((state) => [state.id, state]));
|
|
535
|
+
const kept = new Set(edited.states.map((state) => state.id));
|
|
536
|
+
const elementChanges = [];
|
|
537
|
+
for (const state of original.states) if (!kept.has(state.id)) elementChanges.push({
|
|
538
|
+
kind: "removed",
|
|
539
|
+
element: state.id,
|
|
540
|
+
inferred: false
|
|
541
|
+
});
|
|
542
|
+
for (const state of edited.states) {
|
|
543
|
+
const before = drawn.get(state.id);
|
|
544
|
+
if (before === void 0) {
|
|
545
|
+
if (elements.has(state.id)) throw new Error(`State "${state.id}" is a model element the state machine does not draw`);
|
|
546
|
+
elementChanges.push(addedState(state));
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (before.role !== state.role) throw new Error(`State "${state.id}" changed role from ${before.role} to ${state.role}; remove it and add one under a new id`);
|
|
550
|
+
if (before.label !== state.label) {
|
|
551
|
+
if (state.role !== "state") throw new Error(`State "${state.id}" is ${state.role === "initial" ? "an" : "a"} ${state.role} pseudo-state, whose label the projection supplies`);
|
|
552
|
+
elementChanges.push({
|
|
553
|
+
kind: "renamed",
|
|
554
|
+
element: state.id,
|
|
555
|
+
from: before.label,
|
|
556
|
+
to: state.label,
|
|
557
|
+
inferred: false
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
const transitions = new Map(model.transitions.map((transition) => [transition.id, transition]));
|
|
562
|
+
const keyOf = (first, target) => JSON.stringify([first, target]);
|
|
563
|
+
const originals = /* @__PURE__ */ new Map();
|
|
564
|
+
for (const [position, origin] of parts.origins.entries()) {
|
|
565
|
+
const drawnTransition = original.transitions[position];
|
|
566
|
+
if (drawnTransition !== void 0) originals.set(keyOf(origin.first.id, origin.target), {
|
|
567
|
+
drawn: drawnTransition,
|
|
568
|
+
origin
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
/** An edited transition's key: its first model transition and the state one of its model transitions arrives at, or `undefined` when its first transition is new. */
|
|
572
|
+
const editedKey = (edit) => {
|
|
573
|
+
const [first] = edit.transitions;
|
|
574
|
+
if (first === void 0 || !transitions.has(first)) return;
|
|
575
|
+
for (const id of edit.transitions) {
|
|
576
|
+
const target = transitions.get(id)?.target;
|
|
577
|
+
if (target !== void 0 && drawn.has(target)) return keyOf(first, target);
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
const editedKeys = new Set(edited.transitions.map(editedKey));
|
|
581
|
+
/** The model transitions arriving at a drawn transition's state: its one transition, or every step from a folded element into that state. */
|
|
582
|
+
const arriving = (origin) => origin.first.target === origin.target ? [origin.first] : parts.index.arriving(origin.target);
|
|
583
|
+
const transitionChanges = [];
|
|
584
|
+
const made = /* @__PURE__ */ new Set();
|
|
585
|
+
const push = (change) => {
|
|
586
|
+
const key = JSON.stringify(change);
|
|
587
|
+
if (!made.has(key)) {
|
|
588
|
+
made.add(key);
|
|
589
|
+
transitionChanges.push(change);
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
const endsLeft = /* @__PURE__ */ new Set();
|
|
593
|
+
for (const [key, { origin }] of originals) {
|
|
594
|
+
if (editedKeys.has(key)) continue;
|
|
595
|
+
if (origin.first.target === origin.target) push({
|
|
596
|
+
kind: "removed",
|
|
597
|
+
transition: origin.first.id,
|
|
598
|
+
inferred: false
|
|
599
|
+
});
|
|
600
|
+
else endsLeft.add(origin.target);
|
|
601
|
+
}
|
|
602
|
+
for (const end of endsLeft) for (const tail of parts.index.arriving(end)) push({
|
|
603
|
+
kind: "removed",
|
|
604
|
+
transition: tail.id,
|
|
605
|
+
inferred: true
|
|
606
|
+
});
|
|
607
|
+
const editedEnds = /* @__PURE__ */ new Map();
|
|
608
|
+
const firstAtEnd = (end, kind, value) => {
|
|
609
|
+
const key = JSON.stringify([end, kind]);
|
|
610
|
+
const wanted = value === void 0 ? "" : `=${JSON.stringify(value)}`;
|
|
611
|
+
const already = editedEnds.get(key);
|
|
612
|
+
if (already === void 0) {
|
|
613
|
+
editedEnds.set(key, wanted);
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
if (already !== wanted) throw new Error(`Transitions into "${end}" through the same steps were edited differently (${kind})`);
|
|
617
|
+
return false;
|
|
618
|
+
};
|
|
619
|
+
for (const edit of edited.transitions) {
|
|
620
|
+
const key = editedKey(edit);
|
|
621
|
+
const before = key === void 0 ? void 0 : originals.get(key);
|
|
622
|
+
const [first] = edit.transitions;
|
|
623
|
+
if (before === void 0) {
|
|
624
|
+
if (first === void 0 || edit.transitions.length > 1 || transitions.has(first) || edit.through !== void 0) throw new Error(`Transition ${JSON.stringify(edit.transitions)} is neither a transition of the model nor a new transition with a single new id`);
|
|
625
|
+
push({
|
|
626
|
+
kind: "added",
|
|
627
|
+
transition: {
|
|
628
|
+
id: first,
|
|
629
|
+
source: edit.source,
|
|
630
|
+
target: edit.target,
|
|
631
|
+
...edit.event === void 0 ? {} : { trigger: edit.event },
|
|
632
|
+
...edit.guard === void 0 ? {} : { guard: edit.guard },
|
|
633
|
+
...edit.effect === void 0 ? {} : { effect: edit.effect }
|
|
634
|
+
},
|
|
635
|
+
inferred: false
|
|
636
|
+
});
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
const { origin, drawn: previous } = before;
|
|
640
|
+
const head = origin.first;
|
|
641
|
+
const chain = head.target !== origin.target;
|
|
642
|
+
const tails = arriving(origin);
|
|
643
|
+
/** The arriving steps to act on for one kind of edit: the transition itself, or the shared steps into the state the first time that kind of edit reaches it. */
|
|
644
|
+
const acting = (kind, value) => !chain || firstAtEnd(origin.target, kind, value) ? tails : [];
|
|
645
|
+
if (!chain && (previous.source !== edit.source || previous.target !== edit.target)) push({
|
|
646
|
+
kind: "rewired",
|
|
647
|
+
transition: head.id,
|
|
648
|
+
from: {
|
|
649
|
+
source: head.source,
|
|
650
|
+
target: head.target
|
|
651
|
+
},
|
|
652
|
+
to: {
|
|
653
|
+
source: edit.source,
|
|
654
|
+
target: edit.target
|
|
655
|
+
},
|
|
656
|
+
inferred: false
|
|
657
|
+
});
|
|
658
|
+
else if (chain) {
|
|
659
|
+
if (previous.source !== edit.source) push({
|
|
660
|
+
kind: "rewired",
|
|
661
|
+
transition: head.id,
|
|
662
|
+
from: {
|
|
663
|
+
source: head.source,
|
|
664
|
+
target: head.target
|
|
665
|
+
},
|
|
666
|
+
to: {
|
|
667
|
+
source: edit.source,
|
|
668
|
+
target: head.target
|
|
669
|
+
},
|
|
670
|
+
inferred: false
|
|
671
|
+
});
|
|
672
|
+
for (const tail of previous.target === edit.target ? [] : acting("target", edit.target)) push({
|
|
673
|
+
kind: "rewired",
|
|
674
|
+
transition: tail.id,
|
|
675
|
+
from: {
|
|
676
|
+
source: tail.source,
|
|
677
|
+
target: tail.target
|
|
678
|
+
},
|
|
679
|
+
to: {
|
|
680
|
+
source: tail.source,
|
|
681
|
+
target: edit.target
|
|
682
|
+
},
|
|
683
|
+
inferred: true
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
for (const tail of previous.event === edit.event ? [] : acting("trigger", edit.event)) push({
|
|
687
|
+
kind: "changed",
|
|
688
|
+
field: "trigger",
|
|
689
|
+
transition: tail.id,
|
|
690
|
+
from: tail.trigger,
|
|
691
|
+
to: edit.event,
|
|
692
|
+
inferred: chain
|
|
693
|
+
});
|
|
694
|
+
for (const tail of previous.guard === edit.guard ? [] : acting("guard", edit.guard)) push({
|
|
695
|
+
kind: "changed",
|
|
696
|
+
field: "guard",
|
|
697
|
+
transition: tail.id,
|
|
698
|
+
from: tail.guard,
|
|
699
|
+
to: edit.guard,
|
|
700
|
+
inferred: chain
|
|
701
|
+
});
|
|
702
|
+
if (previous.effect !== edit.effect) {
|
|
703
|
+
const target = elements.get(previous.target);
|
|
704
|
+
if (target?.kind === "activity" && target.action !== void 0) throw new Error(`The effect of ${JSON.stringify(edit.transitions)} is the action of "${target.id}"; change that activity's action instead`);
|
|
705
|
+
for (const tail of acting("effect", edit.effect)) push({
|
|
706
|
+
kind: "changed",
|
|
707
|
+
field: "effect",
|
|
708
|
+
transition: tail.id,
|
|
709
|
+
from: tail.effect,
|
|
710
|
+
to: edit.effect,
|
|
711
|
+
inferred: chain
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return {
|
|
716
|
+
elements: elementChanges,
|
|
717
|
+
transitions: transitionChanges,
|
|
718
|
+
events: []
|
|
719
|
+
};
|
|
720
|
+
};
|
|
721
|
+
//#endregion
|
|
722
|
+
//#region ../../core/graph/projection/dist/occurrences.js
|
|
723
|
+
/**
|
|
724
|
+
* Items grouped by key, each group kept in its original order and read off one at a time: the projected messages of a sequence diagram grouped by transition, matched against an edited diagram occurrence by occurrence, since a loop repeats a step.
|
|
725
|
+
*
|
|
726
|
+
* Storing an item appends it to its group and reading one advances a position in that group, so building and draining the groups touches each item a constant number of times however often a key repeats.
|
|
727
|
+
*/
|
|
728
|
+
var Occurrences = class {
|
|
729
|
+
#groups = /* @__PURE__ */ new Map();
|
|
730
|
+
/**
|
|
731
|
+
* @param items - The items, in order.
|
|
732
|
+
* @param keyOf - The key an item is grouped by.
|
|
733
|
+
* @param observe - Told of each item as it is stored, so a caller can measure the work done.
|
|
734
|
+
*/
|
|
735
|
+
constructor(items, keyOf, observe) {
|
|
736
|
+
for (const item of items) {
|
|
737
|
+
observe?.(item);
|
|
738
|
+
const key = keyOf(item);
|
|
739
|
+
const group = this.#groups.get(key);
|
|
740
|
+
if (group === void 0) this.#groups.set(key, {
|
|
741
|
+
items: [item],
|
|
742
|
+
read: 0
|
|
743
|
+
});
|
|
744
|
+
else group.items.push(item);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* The next unread item of a key's group, which is then read.
|
|
749
|
+
* @param key - The group to read from.
|
|
750
|
+
* @returns The item, or `undefined` when the key has no group or every item in it has been read.
|
|
751
|
+
*/
|
|
752
|
+
next(key) {
|
|
753
|
+
const group = this.#groups.get(key);
|
|
754
|
+
const item = group?.items[group.read];
|
|
755
|
+
if (group !== void 0 && item !== void 0) group.read += 1;
|
|
756
|
+
return item;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* The keys with items still unread, in the order their groups were first stored.
|
|
760
|
+
* @returns The keys.
|
|
761
|
+
*/
|
|
762
|
+
unread() {
|
|
763
|
+
return [...this.#groups].filter(([, group]) => group.read < group.items.length).map(([key]) => key);
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
//#endregion
|
|
767
|
+
//#region ../../core/graph/projection/dist/inverse-sequence.js
|
|
768
|
+
/** The changes in the order found, each once: a step a loop repeats yields the same change on every pass. */
|
|
769
|
+
const distinct = (changes) => {
|
|
770
|
+
const seen = /* @__PURE__ */ new Set();
|
|
771
|
+
return changes.filter((change) => {
|
|
772
|
+
const key = JSON.stringify(change);
|
|
773
|
+
if (seen.has(key)) return false;
|
|
774
|
+
seen.add(key);
|
|
775
|
+
return true;
|
|
776
|
+
});
|
|
777
|
+
};
|
|
778
|
+
/**
|
|
779
|
+
* The inverse of `toSequence`: what an edited sequence diagram changes in the model. A sequence diagram shows one path and leaves out who owns which step, so most of what it implies has to be inferred, and every such change is marked `inferred` until it is confirmed.
|
|
780
|
+
*
|
|
781
|
+
* Messages are matched to the projection by transition id, occurrence by occurrence, since a loop repeats a step. For a matched message:
|
|
782
|
+
*
|
|
783
|
+
* - a changed label renames what the label came from, which is certain: the `message` element it draws, the event that triggers the step, or the step's target element;
|
|
784
|
+
* - changed ends on a `message` element readdress it, which is certain;
|
|
785
|
+
* - a changed receiver relanes the step's target and a changed sender relanes its source, both inferred, since the edit could equally mean a different element.
|
|
786
|
+
*
|
|
787
|
+
* A projected message missing from the edit removes its transition, inferred, since it may only have left this scenario. A message with a new transition id is a new step, inferred: it must name the element it arrives at in `element` (an existing element, or a new activity owned by its receiver, labelled with the message), and it leaves from where the latest earlier message to its sender arrived. When the message after it was a step from that same place, that step is rewired to leave from the new element instead, inferred. Lifelines and items are derived from the messages and not read.
|
|
788
|
+
* @param model - The model the diagram was projected from.
|
|
789
|
+
* @param scenario - The scenario it was projected for.
|
|
790
|
+
* @param edited - The projected diagram after editing.
|
|
791
|
+
* @returns The changes, each listed once.
|
|
792
|
+
* @throws Error when the edit cannot be expressed in the model: a model transition added to the diagram that the scenario does not take (a scenario change, not a model change), a changed `element` on a drawn message, a changed sender on a step whose sender comes from an earlier step, or a new message with no `element` or with no earlier message to its sender.
|
|
793
|
+
*/
|
|
794
|
+
const diffSequence = (model, scenario, edited) => {
|
|
795
|
+
const original = toSequence(model, scenario);
|
|
796
|
+
const transitions = new Map(model.transitions.map((transition) => [transition.id, transition]));
|
|
797
|
+
const events = new Map(model.events.map((event) => [event.id, event]));
|
|
798
|
+
const elementIds = new Set(model.elements.map((element) => element.id));
|
|
799
|
+
const actors = actorMap(model);
|
|
800
|
+
const pending = new Occurrences(original.messages, (message) => message.transition);
|
|
801
|
+
const elementChanges = [];
|
|
802
|
+
const transitionChanges = [];
|
|
803
|
+
const eventChanges = [];
|
|
804
|
+
/** Where the latest edited message to each actor arrived, for placing a new step that actor sends. */
|
|
805
|
+
const arrivals = /* @__PURE__ */ new Map();
|
|
806
|
+
/** A new step just placed, and where it left from, so the next drawn step can be rewired after it. */
|
|
807
|
+
let inserted;
|
|
808
|
+
for (const message of edited.messages) {
|
|
809
|
+
const before = pending.next(message.transition);
|
|
810
|
+
const transition = transitions.get(message.transition);
|
|
811
|
+
if (before === void 0 || transition === void 0) {
|
|
812
|
+
if (transition !== void 0) throw new Error(`Transition "${message.transition}" is in the model but not on this scenario; a scenario change is not a model change`);
|
|
813
|
+
const target = message.element;
|
|
814
|
+
if (target === void 0) throw new Error(`New message "${message.transition}" must name the element it arrives at in \`element\``);
|
|
815
|
+
const source = arrivals.get(message.from);
|
|
816
|
+
if (source === void 0) throw new Error(`New message "${message.transition}" has no earlier message to "${message.from}" to leave from`);
|
|
817
|
+
if (!elementIds.has(target)) elementChanges.push({
|
|
818
|
+
kind: "added",
|
|
819
|
+
element: {
|
|
820
|
+
kind: "activity",
|
|
821
|
+
id: target,
|
|
822
|
+
label: message.label,
|
|
823
|
+
actor: message.to
|
|
824
|
+
},
|
|
825
|
+
inferred: true
|
|
826
|
+
});
|
|
827
|
+
transitionChanges.push({
|
|
828
|
+
kind: "added",
|
|
829
|
+
transition: {
|
|
830
|
+
id: message.transition,
|
|
831
|
+
source,
|
|
832
|
+
target
|
|
833
|
+
},
|
|
834
|
+
inferred: true
|
|
835
|
+
});
|
|
836
|
+
arrivals.set(message.to, target);
|
|
837
|
+
inserted = {
|
|
838
|
+
from: source,
|
|
839
|
+
element: target
|
|
840
|
+
};
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
if (message.element !== before.element) throw new Error(`Message "${message.transition}" draws "${String(before.element)}"; which element a drawn message is cannot be edited`);
|
|
844
|
+
if (inserted?.from === transition.source) transitionChanges.push({
|
|
845
|
+
kind: "rewired",
|
|
846
|
+
transition: transition.id,
|
|
847
|
+
from: {
|
|
848
|
+
source: transition.source,
|
|
849
|
+
target: transition.target
|
|
850
|
+
},
|
|
851
|
+
to: {
|
|
852
|
+
source: inserted.element,
|
|
853
|
+
target: transition.target
|
|
854
|
+
},
|
|
855
|
+
inferred: true
|
|
856
|
+
});
|
|
857
|
+
inserted = void 0;
|
|
858
|
+
arrivals.set(message.to, before.element ?? transition.target);
|
|
859
|
+
if (before.label !== message.label) {
|
|
860
|
+
const trigger = transition.trigger === void 0 ? void 0 : events.get(transition.trigger);
|
|
861
|
+
if (before.element !== void 0) elementChanges.push({
|
|
862
|
+
kind: "renamed",
|
|
863
|
+
element: before.element,
|
|
864
|
+
from: before.label,
|
|
865
|
+
to: message.label,
|
|
866
|
+
inferred: false
|
|
867
|
+
});
|
|
868
|
+
else if (trigger === void 0) elementChanges.push({
|
|
869
|
+
kind: "renamed",
|
|
870
|
+
element: transition.target,
|
|
871
|
+
from: before.label,
|
|
872
|
+
to: message.label,
|
|
873
|
+
inferred: false
|
|
874
|
+
});
|
|
875
|
+
else eventChanges.push({
|
|
876
|
+
kind: "renamed",
|
|
877
|
+
event: trigger.id,
|
|
878
|
+
from: trigger.name,
|
|
879
|
+
to: message.label,
|
|
880
|
+
inferred: false
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
if (before.element !== void 0) {
|
|
884
|
+
if (before.from !== message.from || before.to !== message.to) elementChanges.push({
|
|
885
|
+
kind: "readdressed",
|
|
886
|
+
element: before.element,
|
|
887
|
+
from: {
|
|
888
|
+
from: before.from,
|
|
889
|
+
to: before.to
|
|
890
|
+
},
|
|
891
|
+
to: {
|
|
892
|
+
from: message.from,
|
|
893
|
+
to: message.to
|
|
894
|
+
},
|
|
895
|
+
inferred: false
|
|
896
|
+
});
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
if (before.to !== message.to) elementChanges.push({
|
|
900
|
+
kind: "relaned",
|
|
901
|
+
element: transition.target,
|
|
902
|
+
from: before.to,
|
|
903
|
+
to: message.to,
|
|
904
|
+
inferred: true
|
|
905
|
+
});
|
|
906
|
+
if (before.from !== message.from) {
|
|
907
|
+
if (actors.get(transition.source) === void 0) throw new Error(`The sender of "${message.transition}" is whoever reached "${transition.source}"; change the earlier step instead`);
|
|
908
|
+
elementChanges.push({
|
|
909
|
+
kind: "relaned",
|
|
910
|
+
element: transition.source,
|
|
911
|
+
from: before.from,
|
|
912
|
+
to: message.from,
|
|
913
|
+
inferred: true
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
for (const transition of pending.unread()) transitionChanges.push({
|
|
918
|
+
kind: "removed",
|
|
919
|
+
transition,
|
|
920
|
+
inferred: true
|
|
921
|
+
});
|
|
922
|
+
return {
|
|
923
|
+
elements: distinct(elementChanges),
|
|
924
|
+
transitions: distinct(transitionChanges),
|
|
925
|
+
events: distinct(eventChanges)
|
|
926
|
+
};
|
|
927
|
+
};
|
|
928
|
+
//#endregion
|
|
929
|
+
export { ModelParseError, OverlayParseError, UnconfirmedChangeError, UnsupportedConditionError, applyModelDiff, artefactKey, blockedBy, confirmChanges, controlDag, criticalPath, dist_exports as dag, defineModel, deriveStatuses, diffFlowchart, diffSequence, diffStateMachine, diffSwimlane, elementKinds, elementStatusKey, evaluateBranches, explainStatus, fragmentOperators, handoffs, illegalCycles, impact, inputsOf, loopTransitions, messageModes, modelSchema, notations, outputsOf, overlaySchema, overlayStatuses, parseModel, parseOverlay, participantKinds, pseudoRoles, readySet, statusRules, statuses, toFlowchart, toSequence, toStateMachine, toSwimlane, unmetOutputs, validate, validateOverlay, whatIfDone };
|