@xxrandr/conecta-diagram 0.0.0-stage → 0.1.4
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/LICENSE +21 -0
- package/README.md +319 -2
- package/dist/chunk-3QGSCPWL.cjs +663 -0
- package/dist/chunk-43VYPB4U.js +2136 -0
- package/dist/chunk-6ADTOXBT.cjs +626 -0
- package/dist/chunk-B2RLLFGC.js +0 -0
- package/dist/chunk-GXOBW2BK.js +290 -0
- package/dist/chunk-HZER5BPR.js +1122 -0
- package/dist/chunk-IT5H6WO2.js +626 -0
- package/dist/chunk-LKOWNTPR.cjs +1 -0
- package/dist/chunk-Q62JPMJH.js +663 -0
- package/dist/chunk-QI4NNFRQ.cjs +290 -0
- package/dist/chunk-VD32JNDO.cjs +2136 -0
- package/dist/chunk-ZMSQTBVW.cjs +1122 -0
- package/dist/core/index.cjs +56 -0
- package/dist/core/index.d.cts +65 -0
- package/dist/core/index.d.ts +65 -0
- package/dist/core/index.js +56 -0
- package/dist/d3/index.cjs +8 -0
- package/dist/d3/index.d.cts +46 -0
- package/dist/d3/index.d.ts +46 -0
- package/dist/d3/index.js +8 -0
- package/dist/index.cjs +71 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +71 -0
- package/dist/photon.css +339 -0
- package/dist/runner-display-BkBJpBY-.d.ts +151 -0
- package/dist/runner-display-DHHtavSX.d.cts +151 -0
- package/dist/store-CtOInpPu.d.cts +242 -0
- package/dist/store-CtOInpPu.d.ts +242 -0
- package/dist/svg/index.cjs +15 -0
- package/dist/svg/index.d.cts +172 -0
- package/dist/svg/index.d.ts +172 -0
- package/dist/svg/index.js +15 -0
- package/docs/devtools-refactor.md +93 -0
- package/docs/java-runtime-integration.md +268 -0
- package/docs/photon-template.html +547 -0
- package/docs/presentation-architecture.md +30 -0
- package/docs/runner-display.md +109 -0
- package/package.json +130 -4
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import {
|
|
2
|
+
evaluateCondition
|
|
3
|
+
} from "./chunk-GXOBW2BK.js";
|
|
4
|
+
import {
|
|
5
|
+
FlowError,
|
|
6
|
+
assertFlow,
|
|
7
|
+
assertJson,
|
|
8
|
+
clone,
|
|
9
|
+
equal,
|
|
10
|
+
freeze
|
|
11
|
+
} from "./chunk-Q62JPMJH.js";
|
|
12
|
+
|
|
13
|
+
// src/core/globals.ts
|
|
14
|
+
function globalValueType(value) {
|
|
15
|
+
return Array.isArray(value) ? "array" : typeof value === "boolean" ? "boolean" : typeof value === "number" && Number.isSafeInteger(value) ? "integer" : "json";
|
|
16
|
+
}
|
|
17
|
+
function flowCreationOrder(document) {
|
|
18
|
+
return document.creationOrder ? [...document.creationOrder] : [
|
|
19
|
+
...document.nodes.map((node) => ({
|
|
20
|
+
type: "node",
|
|
21
|
+
id: node.id
|
|
22
|
+
})),
|
|
23
|
+
...document.edges.map((edge) => ({
|
|
24
|
+
type: "edge",
|
|
25
|
+
id: edge.id
|
|
26
|
+
}))
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
function orderedFlowItems(document) {
|
|
30
|
+
return [
|
|
31
|
+
...(document.globals ?? []).map((variable) => ({
|
|
32
|
+
type: "global",
|
|
33
|
+
id: variable.id
|
|
34
|
+
})),
|
|
35
|
+
...flowCreationOrder(document)
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/core/store.ts
|
|
40
|
+
var FlowStore = class {
|
|
41
|
+
constructor(document, options = {}) {
|
|
42
|
+
this.options = options;
|
|
43
|
+
assertFlow(document);
|
|
44
|
+
this.limit = options.historyLimit ?? 100;
|
|
45
|
+
if (!Number.isSafeInteger(this.limit) || this.limit < 0)
|
|
46
|
+
throw new FlowError(
|
|
47
|
+
"INVALID_LIMIT",
|
|
48
|
+
"historyLimit must be a nonnegative integer."
|
|
49
|
+
);
|
|
50
|
+
this.document = freeze(clone(document));
|
|
51
|
+
}
|
|
52
|
+
options;
|
|
53
|
+
document;
|
|
54
|
+
past = [];
|
|
55
|
+
future = [];
|
|
56
|
+
listeners = /* @__PURE__ */ new Set();
|
|
57
|
+
revision = 0;
|
|
58
|
+
editing = false;
|
|
59
|
+
notifying = false;
|
|
60
|
+
limit;
|
|
61
|
+
/** Frozen at runtime; mutate through transaction() instead. */
|
|
62
|
+
getSnapshot() {
|
|
63
|
+
return this.document;
|
|
64
|
+
}
|
|
65
|
+
getRevision() {
|
|
66
|
+
return this.revision;
|
|
67
|
+
}
|
|
68
|
+
get canUndo() {
|
|
69
|
+
return this.past.length > 0;
|
|
70
|
+
}
|
|
71
|
+
get canRedo() {
|
|
72
|
+
return this.future.length > 0;
|
|
73
|
+
}
|
|
74
|
+
subscribe(listener) {
|
|
75
|
+
this.listeners.add(listener);
|
|
76
|
+
return () => {
|
|
77
|
+
this.listeners.delete(listener);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
transaction(reason, edit) {
|
|
81
|
+
this.guard();
|
|
82
|
+
this.editing = true;
|
|
83
|
+
let next;
|
|
84
|
+
try {
|
|
85
|
+
next = clone(this.document);
|
|
86
|
+
const result = edit(next);
|
|
87
|
+
if (result && typeof result.then === "function") {
|
|
88
|
+
throw new FlowError(
|
|
89
|
+
"ASYNC_TRANSACTION",
|
|
90
|
+
"Transactions must be synchronous."
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
const before = [
|
|
94
|
+
...this.document.nodes.map((node) => ["node", node.id]),
|
|
95
|
+
...this.document.edges.map((edge) => ["edge", edge.id])
|
|
96
|
+
];
|
|
97
|
+
const after = [
|
|
98
|
+
...next.nodes.map((node) => ["node", node.id]),
|
|
99
|
+
...next.edges.map((edge) => ["edge", edge.id])
|
|
100
|
+
];
|
|
101
|
+
if (!equal(before, after) && equal(next.creationOrder, this.document.creationOrder)) {
|
|
102
|
+
const current = new Set(
|
|
103
|
+
after.map((reference) => JSON.stringify(reference))
|
|
104
|
+
);
|
|
105
|
+
const order = flowCreationOrder(this.document).filter(
|
|
106
|
+
(reference) => current.has(JSON.stringify([reference.type, reference.id]))
|
|
107
|
+
);
|
|
108
|
+
const recorded = new Set(
|
|
109
|
+
order.map(
|
|
110
|
+
(reference) => JSON.stringify([reference.type, reference.id])
|
|
111
|
+
)
|
|
112
|
+
);
|
|
113
|
+
for (const [type, id] of after)
|
|
114
|
+
if (!recorded.has(JSON.stringify([type, id])))
|
|
115
|
+
order.push({ type, id });
|
|
116
|
+
next.creationOrder = order;
|
|
117
|
+
}
|
|
118
|
+
assertFlow(next);
|
|
119
|
+
next = freeze(clone(next));
|
|
120
|
+
} finally {
|
|
121
|
+
this.editing = false;
|
|
122
|
+
}
|
|
123
|
+
if (equal(next, this.document)) return;
|
|
124
|
+
if (this.limit)
|
|
125
|
+
this.past = [...this.past, this.document].slice(-this.limit);
|
|
126
|
+
this.future = [];
|
|
127
|
+
this.document = next;
|
|
128
|
+
this.emit(reason);
|
|
129
|
+
}
|
|
130
|
+
replace(document) {
|
|
131
|
+
assertFlow(document);
|
|
132
|
+
this.transaction("replace", (draft) => {
|
|
133
|
+
for (const key of Object.keys(draft))
|
|
134
|
+
delete draft[key];
|
|
135
|
+
Object.assign(draft, clone(document));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
addNode(node) {
|
|
139
|
+
assertJson(node);
|
|
140
|
+
this.transaction("add-node", (draft) => {
|
|
141
|
+
draft.nodes.push(clone(node));
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
addGlobal(variable) {
|
|
145
|
+
assertJson(variable);
|
|
146
|
+
this.transaction("add-global", (draft) => {
|
|
147
|
+
(draft.globals ??= []).push(clone(variable));
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
updateGlobal(id, update) {
|
|
151
|
+
this.transaction("update-global", (draft) => {
|
|
152
|
+
const index = draft.globals?.findIndex((variable2) => variable2.id === id) ?? -1;
|
|
153
|
+
if (index < 0)
|
|
154
|
+
throw new FlowError("GLOBAL_NOT_FOUND", `Unknown global ${id}.`);
|
|
155
|
+
const variable = update(draft.globals[index]);
|
|
156
|
+
if (variable.id !== id)
|
|
157
|
+
throw new FlowError(
|
|
158
|
+
"ID_CHANGE",
|
|
159
|
+
"Global ids cannot be changed by updateGlobal."
|
|
160
|
+
);
|
|
161
|
+
draft.globals[index] = variable;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
removeGlobal(id) {
|
|
165
|
+
this.transaction("remove-global", (draft) => {
|
|
166
|
+
if (draft.globals)
|
|
167
|
+
draft.globals = draft.globals.filter((variable) => variable.id !== id);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
updateNode(id, update) {
|
|
171
|
+
this.transaction("update-node", (draft) => {
|
|
172
|
+
const index = draft.nodes.findIndex((node) => node.id === id);
|
|
173
|
+
if (index < 0)
|
|
174
|
+
throw new FlowError("NODE_NOT_FOUND", `Unknown node ${id}.`);
|
|
175
|
+
const next = update(draft.nodes[index]);
|
|
176
|
+
if (next.id !== id)
|
|
177
|
+
throw new FlowError(
|
|
178
|
+
"ID_CHANGE",
|
|
179
|
+
"Node ids cannot be changed by updateNode."
|
|
180
|
+
);
|
|
181
|
+
draft.nodes[index] = next;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
moveNode(id, position) {
|
|
185
|
+
this.updateNode(id, (node) => ({ ...node, position }));
|
|
186
|
+
}
|
|
187
|
+
removeNode(id) {
|
|
188
|
+
this.transaction("remove-node", (draft) => {
|
|
189
|
+
draft.nodes = draft.nodes.filter((node) => node.id !== id);
|
|
190
|
+
draft.edges = draft.edges.filter(
|
|
191
|
+
(edge) => edge.source !== id && edge.target !== id
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
addEdge(edge) {
|
|
196
|
+
assertJson(edge);
|
|
197
|
+
this.transaction("add-edge", (draft) => {
|
|
198
|
+
draft.edges.push(clone(edge));
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
updateEdge(id, update) {
|
|
202
|
+
this.transaction("update-edge", (draft) => {
|
|
203
|
+
const index = draft.edges.findIndex((edge) => edge.id === id);
|
|
204
|
+
if (index < 0)
|
|
205
|
+
throw new FlowError("EDGE_NOT_FOUND", `Unknown edge ${id}.`);
|
|
206
|
+
const next = update(draft.edges[index]);
|
|
207
|
+
if (next.id !== id)
|
|
208
|
+
throw new FlowError(
|
|
209
|
+
"ID_CHANGE",
|
|
210
|
+
"Edge ids cannot be changed by updateEdge."
|
|
211
|
+
);
|
|
212
|
+
draft.edges[index] = next;
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
removeEdge(id) {
|
|
216
|
+
this.transaction("remove-edge", (draft) => {
|
|
217
|
+
draft.edges = draft.edges.filter((edge) => edge.id !== id);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
undo() {
|
|
221
|
+
this.guard();
|
|
222
|
+
const previous = this.past.pop();
|
|
223
|
+
if (!previous) return;
|
|
224
|
+
this.future.push(this.document);
|
|
225
|
+
this.document = previous;
|
|
226
|
+
this.emit("undo");
|
|
227
|
+
}
|
|
228
|
+
redo() {
|
|
229
|
+
this.guard();
|
|
230
|
+
const next = this.future.pop();
|
|
231
|
+
if (!next) return;
|
|
232
|
+
this.past.push(this.document);
|
|
233
|
+
this.document = next;
|
|
234
|
+
this.emit("redo");
|
|
235
|
+
}
|
|
236
|
+
guard() {
|
|
237
|
+
if (this.editing || this.notifying)
|
|
238
|
+
throw new FlowError(
|
|
239
|
+
"REENTRANT_EDIT",
|
|
240
|
+
"Nested edits and edits during notification are not supported."
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
emit(reason) {
|
|
244
|
+
this.revision++;
|
|
245
|
+
this.notifying = true;
|
|
246
|
+
try {
|
|
247
|
+
for (const listener of [...this.listeners]) {
|
|
248
|
+
try {
|
|
249
|
+
listener({
|
|
250
|
+
document: this.document,
|
|
251
|
+
reason,
|
|
252
|
+
revision: this.revision
|
|
253
|
+
});
|
|
254
|
+
} catch (error) {
|
|
255
|
+
try {
|
|
256
|
+
this.options.onListenerError?.(error);
|
|
257
|
+
} catch {
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
} finally {
|
|
262
|
+
this.notifying = false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// src/core/simulation.ts
|
|
268
|
+
var FlowSimulator = class {
|
|
269
|
+
document;
|
|
270
|
+
state;
|
|
271
|
+
nextId = 1;
|
|
272
|
+
sequence = 0;
|
|
273
|
+
traceLimit;
|
|
274
|
+
maxTokens;
|
|
275
|
+
constructor(document, context = {}, options = {}) {
|
|
276
|
+
assertFlow(document, "executable");
|
|
277
|
+
this.checkContext(context);
|
|
278
|
+
this.traceLimit = options.traceLimit ?? 1e3;
|
|
279
|
+
this.maxTokens = options.maxTokens ?? 1e4;
|
|
280
|
+
if (!Number.isSafeInteger(this.traceLimit) || this.traceLimit < 0 || !Number.isSafeInteger(this.maxTokens) || this.maxTokens < 1) {
|
|
281
|
+
throw new FlowError(
|
|
282
|
+
"INVALID_LIMIT",
|
|
283
|
+
"traceLimit must be nonnegative and maxTokens positive integers."
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
this.document = freeze(clone(document));
|
|
287
|
+
this.state = {
|
|
288
|
+
status: "running",
|
|
289
|
+
context: clone(context),
|
|
290
|
+
tokens: [
|
|
291
|
+
{
|
|
292
|
+
id: this.id(),
|
|
293
|
+
nodeId: document.nodes.find((node) => node.type === "start").id,
|
|
294
|
+
status: "ready",
|
|
295
|
+
frames: []
|
|
296
|
+
}
|
|
297
|
+
],
|
|
298
|
+
completedNodes: [],
|
|
299
|
+
traversedEdges: [],
|
|
300
|
+
events: [],
|
|
301
|
+
steps: 0
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
getSnapshot() {
|
|
305
|
+
return freeze(clone(this.state));
|
|
306
|
+
}
|
|
307
|
+
/** Process one ready token, using stable FIFO order. */
|
|
308
|
+
step() {
|
|
309
|
+
if (this.terminal()) return this.getSnapshot();
|
|
310
|
+
const token = this.state.tokens.find((item) => item.status === "ready");
|
|
311
|
+
if (!token) {
|
|
312
|
+
this.refresh();
|
|
313
|
+
return this.getSnapshot();
|
|
314
|
+
}
|
|
315
|
+
const node = this.document.nodes.find((item) => item.id === token.nodeId);
|
|
316
|
+
const outputs = this.document.edges.filter(
|
|
317
|
+
(edge) => edge.source === node.id
|
|
318
|
+
);
|
|
319
|
+
this.state.steps++;
|
|
320
|
+
this.event({ type: "entered", nodeId: node.id, tokenId: token.id });
|
|
321
|
+
if (node.type === "task" && node.mode === "async") {
|
|
322
|
+
token.status = "async";
|
|
323
|
+
this.event({ type: "waiting", nodeId: node.id, tokenId: token.id });
|
|
324
|
+
} else if (node.type === "wait") {
|
|
325
|
+
token.status = "signal";
|
|
326
|
+
this.event({
|
|
327
|
+
type: "waiting",
|
|
328
|
+
nodeId: node.id,
|
|
329
|
+
tokenId: token.id,
|
|
330
|
+
message: node.signal
|
|
331
|
+
});
|
|
332
|
+
} else if (node.type === "end") {
|
|
333
|
+
this.completed(token);
|
|
334
|
+
this.remove(token);
|
|
335
|
+
} else if (node.type === "decision") {
|
|
336
|
+
const edge = outputs.filter((item) => !item.default).sort(
|
|
337
|
+
(a, b) => (a.priority ?? 0) - (b.priority ?? 0) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)
|
|
338
|
+
).find(
|
|
339
|
+
(item) => item.condition && evaluateCondition(item.condition, this.state.context)
|
|
340
|
+
) ?? outputs.find((item) => item.default);
|
|
341
|
+
this.advance(token, edge);
|
|
342
|
+
} else if (node.type === "fork") {
|
|
343
|
+
if (this.state.tokens.length - 1 + outputs.length > this.maxTokens) {
|
|
344
|
+
this.failToken(token, "Active token limit exceeded.");
|
|
345
|
+
} else {
|
|
346
|
+
const group = this.id();
|
|
347
|
+
this.completed(token);
|
|
348
|
+
this.remove(token);
|
|
349
|
+
for (const edge of outputs) {
|
|
350
|
+
const child = {
|
|
351
|
+
id: this.id(),
|
|
352
|
+
nodeId: node.id,
|
|
353
|
+
status: "ready",
|
|
354
|
+
frames: [
|
|
355
|
+
...clone(token.frames),
|
|
356
|
+
{
|
|
357
|
+
group,
|
|
358
|
+
fork: node.id,
|
|
359
|
+
join: node.join,
|
|
360
|
+
branch: edge.id,
|
|
361
|
+
expected: outputs.length
|
|
362
|
+
}
|
|
363
|
+
]
|
|
364
|
+
};
|
|
365
|
+
this.state.tokens.push(child);
|
|
366
|
+
this.traverse(child, edge);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
} else if (node.type === "join") {
|
|
370
|
+
const frame = token.frames.at(-1);
|
|
371
|
+
if (!frame || frame.join !== node.id || frame.fork !== node.fork) {
|
|
372
|
+
this.failToken(token, "Token reached a join outside its fork scope.");
|
|
373
|
+
} else {
|
|
374
|
+
token.status = "joining";
|
|
375
|
+
const siblings = this.state.tokens.filter(
|
|
376
|
+
(item) => item.nodeId === node.id && item.status === "joining" && item.frames.at(-1)?.group === frame.group
|
|
377
|
+
);
|
|
378
|
+
const branches = new Set(
|
|
379
|
+
siblings.map((item) => item.frames.at(-1).branch)
|
|
380
|
+
);
|
|
381
|
+
if (branches.size !== siblings.length)
|
|
382
|
+
this.failToken(
|
|
383
|
+
token,
|
|
384
|
+
"A parallel branch arrived at the join more than once."
|
|
385
|
+
);
|
|
386
|
+
else if (siblings.length === frame.expected) {
|
|
387
|
+
for (const sibling of siblings) this.remove(sibling);
|
|
388
|
+
token.frames.pop();
|
|
389
|
+
token.status = "ready";
|
|
390
|
+
this.state.tokens.push(token);
|
|
391
|
+
this.advance(token, outputs[0]);
|
|
392
|
+
} else
|
|
393
|
+
this.event({ type: "waiting", nodeId: node.id, tokenId: token.id });
|
|
394
|
+
}
|
|
395
|
+
} else this.advance(token, outputs[0]);
|
|
396
|
+
this.refresh();
|
|
397
|
+
return this.getSnapshot();
|
|
398
|
+
}
|
|
399
|
+
run(maxSteps = 1e3) {
|
|
400
|
+
if (!Number.isSafeInteger(maxSteps) || maxSteps < 1)
|
|
401
|
+
throw new FlowError(
|
|
402
|
+
"INVALID_LIMIT",
|
|
403
|
+
"maxSteps must be a positive integer."
|
|
404
|
+
);
|
|
405
|
+
for (let index = 0; index < maxSteps && this.state.status === "running"; index++)
|
|
406
|
+
this.step();
|
|
407
|
+
return {
|
|
408
|
+
reason: this.state.status === "running" ? "step-limit" : this.state.status,
|
|
409
|
+
snapshot: this.getSnapshot()
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
/** Complete one async activation, not every activation of the same node. */
|
|
413
|
+
complete(tokenId, patch = {}) {
|
|
414
|
+
return this.resume(tokenId, "async", patch);
|
|
415
|
+
}
|
|
416
|
+
/** External applications collect user input and explicitly deliver it here. */
|
|
417
|
+
signal(tokenId, signal, patch = {}) {
|
|
418
|
+
const token = this.findWaiting(tokenId, "signal");
|
|
419
|
+
const node = this.document.nodes.find((item) => item.id === token.nodeId);
|
|
420
|
+
if (node.type !== "wait" || node.signal !== signal)
|
|
421
|
+
throw new FlowError("SIGNAL_MISMATCH", `Unexpected signal ${signal}.`);
|
|
422
|
+
return this.resume(tokenId, "signal", patch);
|
|
423
|
+
}
|
|
424
|
+
fail(tokenId, message) {
|
|
425
|
+
if (this.terminal())
|
|
426
|
+
throw new FlowError(
|
|
427
|
+
"TERMINAL_SIMULATION",
|
|
428
|
+
"The simulation has finished."
|
|
429
|
+
);
|
|
430
|
+
const token = this.state.tokens.find((item) => item.id === tokenId);
|
|
431
|
+
if (!token)
|
|
432
|
+
throw new FlowError("TOKEN_NOT_FOUND", `Unknown token ${tokenId}.`);
|
|
433
|
+
this.failToken(token, message);
|
|
434
|
+
return this.getSnapshot();
|
|
435
|
+
}
|
|
436
|
+
cancel() {
|
|
437
|
+
if (!this.terminal()) {
|
|
438
|
+
this.state.status = "cancelled";
|
|
439
|
+
this.state.tokens = [];
|
|
440
|
+
this.event({ type: "cancelled" });
|
|
441
|
+
}
|
|
442
|
+
return this.getSnapshot();
|
|
443
|
+
}
|
|
444
|
+
resume(tokenId, status, patch) {
|
|
445
|
+
const token = this.findWaiting(tokenId, status);
|
|
446
|
+
this.checkContext(patch);
|
|
447
|
+
this.state.context = { ...this.state.context, ...clone(patch) };
|
|
448
|
+
this.advance(
|
|
449
|
+
token,
|
|
450
|
+
this.document.edges.find((edge) => edge.source === token.nodeId)
|
|
451
|
+
);
|
|
452
|
+
this.refresh();
|
|
453
|
+
return this.getSnapshot();
|
|
454
|
+
}
|
|
455
|
+
findWaiting(id, status) {
|
|
456
|
+
if (this.terminal())
|
|
457
|
+
throw new FlowError(
|
|
458
|
+
"TERMINAL_SIMULATION",
|
|
459
|
+
"The simulation has finished."
|
|
460
|
+
);
|
|
461
|
+
const token = this.state.tokens.find((item) => item.id === id);
|
|
462
|
+
if (!token || token.status !== status)
|
|
463
|
+
throw new FlowError(
|
|
464
|
+
"TOKEN_NOT_WAITING",
|
|
465
|
+
`Token ${id} is not waiting for ${status}.`
|
|
466
|
+
);
|
|
467
|
+
return token;
|
|
468
|
+
}
|
|
469
|
+
advance(token, edge) {
|
|
470
|
+
this.completed(token);
|
|
471
|
+
this.traverse(token, edge);
|
|
472
|
+
this.remove(token);
|
|
473
|
+
this.state.tokens.push(token);
|
|
474
|
+
}
|
|
475
|
+
traverse(token, edge) {
|
|
476
|
+
this.event({ type: "traversed", edgeId: edge.id, tokenId: token.id });
|
|
477
|
+
if (!this.state.traversedEdges.includes(edge.id))
|
|
478
|
+
this.state.traversedEdges.push(edge.id);
|
|
479
|
+
token.nodeId = edge.target;
|
|
480
|
+
token.status = "ready";
|
|
481
|
+
}
|
|
482
|
+
completed(token) {
|
|
483
|
+
if (!this.state.completedNodes.includes(token.nodeId))
|
|
484
|
+
this.state.completedNodes.push(token.nodeId);
|
|
485
|
+
this.event({ type: "completed", nodeId: token.nodeId, tokenId: token.id });
|
|
486
|
+
}
|
|
487
|
+
failToken(token, message) {
|
|
488
|
+
this.state.status = "failed";
|
|
489
|
+
this.state.error = message;
|
|
490
|
+
this.event({
|
|
491
|
+
type: "failed",
|
|
492
|
+
nodeId: token.nodeId,
|
|
493
|
+
tokenId: token.id,
|
|
494
|
+
message
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
remove(token) {
|
|
498
|
+
this.state.tokens = this.state.tokens.filter(
|
|
499
|
+
(item) => item.id !== token.id
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
id() {
|
|
503
|
+
return `t${this.nextId++}`;
|
|
504
|
+
}
|
|
505
|
+
terminal() {
|
|
506
|
+
return ["completed", "failed", "cancelled"].includes(this.state.status);
|
|
507
|
+
}
|
|
508
|
+
refresh() {
|
|
509
|
+
if (this.terminal()) return;
|
|
510
|
+
if (!this.state.tokens.length) this.state.status = "completed";
|
|
511
|
+
else if (this.state.tokens.some((token) => token.status === "ready"))
|
|
512
|
+
this.state.status = "running";
|
|
513
|
+
else if (this.state.tokens.every((token) => token.status === "joining")) {
|
|
514
|
+
this.failToken(this.state.tokens[0], "Parallel join deadlock.");
|
|
515
|
+
} else this.state.status = "waiting";
|
|
516
|
+
}
|
|
517
|
+
event(event) {
|
|
518
|
+
this.sequence++;
|
|
519
|
+
if (this.traceLimit) {
|
|
520
|
+
this.state.events.push({ ...event, sequence: this.sequence });
|
|
521
|
+
if (this.state.events.length > this.traceLimit) this.state.events.shift();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
checkContext(context) {
|
|
525
|
+
assertJson(context);
|
|
526
|
+
if (!context || typeof context !== "object" || Array.isArray(context))
|
|
527
|
+
throw new FlowError("INVALID_CONTEXT", "Context must be a JSON object.");
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
// src/core/layout.ts
|
|
532
|
+
function layoutFlow(document, options = {}) {
|
|
533
|
+
assertFlow(document);
|
|
534
|
+
const columnGap = options.columnGap ?? 220;
|
|
535
|
+
const rowGap = options.rowGap ?? 120;
|
|
536
|
+
if (!Number.isFinite(columnGap) || columnGap <= 0 || !Number.isFinite(rowGap) || rowGap <= 0)
|
|
537
|
+
throw new RangeError("Layout gaps must be positive finite numbers.");
|
|
538
|
+
const result = clone(document);
|
|
539
|
+
const levels = /* @__PURE__ */ new Map();
|
|
540
|
+
const queue = result.nodes.filter(
|
|
541
|
+
(node) => node.type === "start" || !result.edges.some((edge) => edge.target === node.id)
|
|
542
|
+
).map((node) => node.id);
|
|
543
|
+
for (const id of queue) levels.set(id, 0);
|
|
544
|
+
for (let index = 0; index < queue.length; index++) {
|
|
545
|
+
const id = queue[index];
|
|
546
|
+
for (const edge of result.edges.filter((edge2) => edge2.source === id)) {
|
|
547
|
+
if (!levels.has(edge.target)) {
|
|
548
|
+
levels.set(edge.target, levels.get(id) + 1);
|
|
549
|
+
queue.push(edge.target);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
let fallback = Math.max(0, ...levels.values()) + 1;
|
|
554
|
+
const rows = /* @__PURE__ */ new Map();
|
|
555
|
+
for (const node of result.nodes) {
|
|
556
|
+
const level = levels.get(node.id) ?? fallback++;
|
|
557
|
+
const row = rows.get(level) ?? 0;
|
|
558
|
+
rows.set(level, row + 1);
|
|
559
|
+
node.position = options.direction === "down" ? { x: 100 + row * columnGap, y: 80 + level * rowGap } : { x: 100 + level * columnGap, y: 80 + row * rowGap };
|
|
560
|
+
}
|
|
561
|
+
return result;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/core/performance.ts
|
|
565
|
+
var finite = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
566
|
+
var text = (value) => typeof value === "string" && value.trim().length > 0;
|
|
567
|
+
var uint64 = (value) => typeof value === "string" && /^(0|[1-9][0-9]*)$/.test(value) && value.length <= 20 && BigInt(value) <= 18446744073709551615n;
|
|
568
|
+
var PerformanceTimeline = class {
|
|
569
|
+
runs = /* @__PURE__ */ new Map();
|
|
570
|
+
record(sample) {
|
|
571
|
+
assertJson(sample);
|
|
572
|
+
if (!sample || ![sample.runId, sample.nodeId, sample.commandId, sample.process].every(
|
|
573
|
+
text
|
|
574
|
+
) || !finite(sample.startMs) || !finite(sample.durationMs) || !Number.isSafeInteger(sample.cost) || sample.cost < 0 || !Number.isFinite(sample.startMs + sample.durationMs))
|
|
575
|
+
throw new FlowError(
|
|
576
|
+
"INVALID_PERFORMANCE",
|
|
577
|
+
"Expected backend run identity, finite times and a nonnegative safe integer cost."
|
|
578
|
+
);
|
|
579
|
+
if (sample.network !== void 0 && (!Array.isArray(sample.network) || sample.network.some(
|
|
580
|
+
(item) => !item || !finite(item.startMs) || !finite(item.durationMs) || !Number.isFinite(item.startMs + item.durationMs) || !text(item.label) || item.bytes !== void 0 && !uint64(item.bytes)
|
|
581
|
+
)))
|
|
582
|
+
throw new FlowError(
|
|
583
|
+
"INVALID_PERFORMANCE",
|
|
584
|
+
"Invalid backend network interval."
|
|
585
|
+
);
|
|
586
|
+
if (sample.memory !== void 0 && (!Array.isArray(sample.memory) || sample.memory.some(
|
|
587
|
+
(item) => !item || !finite(item.timeMs) || !uint64(item.bytes)
|
|
588
|
+
)))
|
|
589
|
+
throw new FlowError(
|
|
590
|
+
"INVALID_PERFORMANCE",
|
|
591
|
+
"Invalid backend memory sample."
|
|
592
|
+
);
|
|
593
|
+
const previous = this.runs.get(sample.runId);
|
|
594
|
+
if (previous) {
|
|
595
|
+
if (!equal(previous, sample))
|
|
596
|
+
throw new FlowError(
|
|
597
|
+
"PERFORMANCE_CONFLICT",
|
|
598
|
+
"A node-run metric id cannot be reused with different data."
|
|
599
|
+
);
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
if (this.runs.size >= 1e4)
|
|
603
|
+
throw new FlowError(
|
|
604
|
+
"PERFORMANCE_LIMIT",
|
|
605
|
+
"Start a new performance view after 10000 node runs."
|
|
606
|
+
);
|
|
607
|
+
this.runs.set(sample.runId, freeze(clone(sample)));
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
getSnapshot() {
|
|
611
|
+
return freeze(clone([...this.runs.values()]));
|
|
612
|
+
}
|
|
613
|
+
clear() {
|
|
614
|
+
this.runs.clear();
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
export {
|
|
619
|
+
globalValueType,
|
|
620
|
+
flowCreationOrder,
|
|
621
|
+
orderedFlowItems,
|
|
622
|
+
FlowStore,
|
|
623
|
+
FlowSimulator,
|
|
624
|
+
layoutFlow,
|
|
625
|
+
PerformanceTimeline
|
|
626
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|