@arronqzy/vue-blueprint 0.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.
Files changed (57) hide show
  1. package/README.md +50 -0
  2. package/package.json +44 -0
  3. package/src/BlueprintCanvasContext.ts +71 -0
  4. package/src/BlueprintNodeConfigSidebar.vue +338 -0
  5. package/src/blueprint.css +327 -0
  6. package/src/blueprintNodeTypes.ts +20 -0
  7. package/src/components/BluePrintVueRoot.vue +73 -0
  8. package/src/components/BlueprintCanvas.vue +220 -0
  9. package/src/components/BlueprintContextMenu.vue +114 -0
  10. package/src/components/BlueprintExecutionLogPanel.vue +294 -0
  11. package/src/components/BlueprintMetaDialog.vue +80 -0
  12. package/src/components/BlueprintNodeSwitchTaskDialog.vue +41 -0
  13. package/src/components/ClockNodeConfigPanel.vue +124 -0
  14. package/src/components/FetchNodeConfigPanel.vue +559 -0
  15. package/src/components/FetchUrlAutocomplete.vue +174 -0
  16. package/src/components/JsonNodeConfigPanel.vue +73 -0
  17. package/src/components/LogicNodeConfigPanel.vue +73 -0
  18. package/src/components/ViewElementMultiSelect.vue +50 -0
  19. package/src/composables/useBlueprintDebugSession.ts +441 -0
  20. package/src/composables/useBlueprintFlowState.ts +486 -0
  21. package/src/composables/useBlueprintFlowViewport.ts +65 -0
  22. package/src/composables/useBlueprintNodeSelectionGuard.ts +41 -0
  23. package/src/composables/useBlueprintPageLifecycle.ts +244 -0
  24. package/src/createBlueprintEdgeTypes.ts +10 -0
  25. package/src/edges/BlueprintSmoothEdge.vue +31 -0
  26. package/src/env.d.ts +7 -0
  27. package/src/fetch-config-task-store.ts +206 -0
  28. package/src/flowCoordinates.ts +19 -0
  29. package/src/flowDefaults.ts +9 -0
  30. package/src/graph/blueprint-graph.ts +265 -0
  31. package/src/graph/document.ts +422 -0
  32. package/src/graph/index.ts +7 -0
  33. package/src/graph/node-summary.ts +88 -0
  34. package/src/graph/node-types.ts +9 -0
  35. package/src/graph/sync-edges.ts +69 -0
  36. package/src/graph/sync-nodes.ts +110 -0
  37. package/src/graph/vue-flow-adapter.ts +127 -0
  38. package/src/index.ts +37 -0
  39. package/src/library/blueprint-io.ts +108 -0
  40. package/src/library/blueprint-library-db.ts +112 -0
  41. package/src/library/execution-log-db.ts +171 -0
  42. package/src/library/execution-log-settings.ts +50 -0
  43. package/src/library/swagger-docs.ts +56 -0
  44. package/src/library/types.ts +35 -0
  45. package/src/nodes/AndFlowNode.vue +60 -0
  46. package/src/nodes/BlueprintFlowNode.vue +26 -0
  47. package/src/nodes/BlueprintNodeCard.vue +155 -0
  48. package/src/nodes/BlueprintNodeShell.vue +70 -0
  49. package/src/nodes/ClockFlowNode.vue +60 -0
  50. package/src/nodes/FetchFlowNode.vue +26 -0
  51. package/src/nodes/JsonFlowNode.vue +26 -0
  52. package/src/nodes/LifecycleFlowNode.vue +45 -0
  53. package/src/nodes/LogicFlowNode.vue +26 -0
  54. package/src/runtime/document-to-runnable-graph.ts +51 -0
  55. package/src/runtime/execution-overlay.ts +169 -0
  56. package/src/types.ts +1 -0
  57. package/src/utils/cn.ts +3 -0
@@ -0,0 +1,441 @@
1
+ import { computed, ref, shallowRef, toValue, watch, type MaybeRefOrGetter } from "vue";
2
+ import {
3
+ BlueprintGraphRunner,
4
+ detectBlueprintReferenceCycle,
5
+ type ExecutionRunRecord,
6
+ type ExecutionTraceEntry,
7
+ type LibraryBlueprintResolver,
8
+ } from "@arronqzy/blueprint-dsl";
9
+
10
+ import type { BlueprintGraph } from "../graph/blueprint-graph";
11
+ import {
12
+ clearAllExecutionRunRecords,
13
+ downloadExecutionRunExport,
14
+ listExecutionRunRecords,
15
+ purgeExecutionRunsOlderThan,
16
+ putExecutionRunRecord,
17
+ trimExecutionRunRecordsToMax,
18
+ } from "../library/execution-log-db";
19
+ import {
20
+ readExecutionLogSettings,
21
+ retentionCutoffMs,
22
+ writeExecutionLogSettings,
23
+ type ExecutionLogSettings,
24
+ } from "../library/execution-log-settings";
25
+ import { documentToRunnableGraph } from "../runtime/document-to-runnable-graph";
26
+ import { buildExecutionOverlay, shouldHaltDebugOnFalseSignal } from "../runtime/execution-overlay";
27
+ import { edgeListSignature } from "../graph/sync-edges";
28
+ import { nodeStructureSignature } from "../graph/sync-nodes";
29
+
30
+ export type LifecycleNodeOption = {
31
+ id: string;
32
+ label: string;
33
+ phase?: string;
34
+ };
35
+
36
+ export type UseBlueprintDebugSessionOptions = {
37
+ graph: MaybeRefOrGetter<BlueprintGraph>;
38
+ blueprintId: MaybeRefOrGetter<string | null>;
39
+ blueprintName: MaybeRefOrGetter<string>;
40
+ resolveLibraryBlueprint?: LibraryBlueprintResolver;
41
+ libraryNameById?: MaybeRefOrGetter<ReadonlyMap<string, string> | undefined>;
42
+ onExecutionBlocked?: (message: string) => void;
43
+ onViewScopeUpdate?: (viewElementIds: string[], scope: unknown) => void;
44
+ };
45
+
46
+ function buildNodeLabelMap(graph: BlueprintGraph): Record<string, string> {
47
+ return Object.fromEntries(
48
+ graph.document.nodes.map((node) => [node.id, node.label || node.id])
49
+ );
50
+ }
51
+
52
+ function createRunId() {
53
+ return `run_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
54
+ }
55
+
56
+ export function useBlueprintDebugSession(options: UseBlueprintDebugSessionOptions) {
57
+ const runnerRef = shallowRef<BlueprintGraphRunner | null>(null);
58
+ const runIdRef = shallowRef<string | null>(null);
59
+ const runStartedAtRef = shallowRef(0);
60
+
61
+ const selectedLifecycleNodeId = ref<string | null>(null);
62
+ const entries = ref<ExecutionTraceEntry[]>([]);
63
+ const chainComplete = ref(false);
64
+ const logPanelOpen = ref(false);
65
+ const running = ref(false);
66
+ const settings = ref<ExecutionLogSettings>(readExecutionLogSettings());
67
+ const savedRuns = ref<ExecutionRunRecord[]>([]);
68
+ const totalSavedRunCount = ref(0);
69
+
70
+ const graph = computed(() => toValue(options.graph));
71
+ const blueprintId = computed(() => toValue(options.blueprintId));
72
+ const blueprintName = computed(() => toValue(options.blueprintName));
73
+
74
+ const lifecycleNodes = computed<LifecycleNodeOption[]>(() =>
75
+ graph.value.document.nodes
76
+ .filter((node) => node.role === "lifecycle")
77
+ .map((node) => ({
78
+ id: node.id,
79
+ label: node.label || node.id,
80
+ phase: node.lifecyclePhase,
81
+ }))
82
+ );
83
+
84
+ const hasLifecycleNodes = computed(() => lifecycleNodes.value.length > 0);
85
+
86
+ const graphDebugSig = computed(
87
+ () =>
88
+ `${nodeStructureSignature(graph.value.document.nodes)}|${edgeListSignature(graph.value.document.edges)}`
89
+ );
90
+
91
+ const allowFalseSignalPropagation = computed(
92
+ () => graph.value.document.allowFalseSignalPropagation ?? false
93
+ );
94
+
95
+ function buildRunnableGraph() {
96
+ return documentToRunnableGraph(graph.value.document, {
97
+ libraryNameById: toValue(options.libraryNameById),
98
+ });
99
+ }
100
+
101
+ function syncRunnerGraphConfig() {
102
+ const runner = runnerRef.value;
103
+ if (!runner?.isDebugSessionActive()) return;
104
+ runner.updateGraph(buildRunnableGraph());
105
+ if (selectedLifecycleNodeId.value) {
106
+ runner.refreshDebugLifecycleScope(selectedLifecycleNodeId.value);
107
+ }
108
+ }
109
+
110
+ function syncRunnerWithLatestGraph() {
111
+ syncRunnerGraphConfig();
112
+ const runner = runnerRef.value;
113
+ if (!runner?.isDebugSessionActive()) return;
114
+ runner.refreshPendingQueueFromLatestGraph();
115
+ }
116
+
117
+ function resolveBlueprintName(id: string) {
118
+ return toValue(options.libraryNameById)?.get(id) ?? id;
119
+ }
120
+
121
+ async function validateBlueprintGraph() {
122
+ if (!options.resolveLibraryBlueprint) return true;
123
+ const result = await detectBlueprintReferenceCycle({
124
+ rootGraph: buildRunnableGraph(),
125
+ rootLibraryBlueprintId: blueprintId.value,
126
+ resolveLibraryBlueprint: options.resolveLibraryBlueprint,
127
+ resolveBlueprintName,
128
+ });
129
+ if (!result.ok) {
130
+ options.onExecutionBlocked?.(result.message);
131
+ return false;
132
+ }
133
+ return true;
134
+ }
135
+
136
+ function ensureRunner() {
137
+ const runner = new BlueprintGraphRunner(buildRunnableGraph(), {
138
+ resolveLibraryBlueprint: options.resolveLibraryBlueprint,
139
+ rootLibraryBlueprintId: blueprintId.value,
140
+ resolveBlueprintName,
141
+ onViewScopeUpdate: options.onViewScopeUpdate,
142
+ });
143
+ runnerRef.value = runner;
144
+ return runner;
145
+ }
146
+
147
+ async function refreshSavedRuns() {
148
+ const all = await listExecutionRunRecords();
149
+ totalSavedRunCount.value = all.length;
150
+ savedRuns.value = blueprintId.value
151
+ ? all.filter((item) => item.blueprintId === blueprintId.value)
152
+ : all;
153
+ }
154
+
155
+ watch(logPanelOpen, (open) => {
156
+ if (open) void refreshSavedRuns();
157
+ });
158
+
159
+ async function enforceSavedRunLimits() {
160
+ const cutoff = retentionCutoffMs(settings.value.retentionDays);
161
+ await purgeExecutionRunsOlderThan(cutoff);
162
+ await trimExecutionRunRecordsToMax(settings.value.maxSavedRuns);
163
+ await refreshSavedRuns();
164
+ }
165
+
166
+ async function clearAllSavedRuns() {
167
+ const removed = await clearAllExecutionRunRecords();
168
+ savedRuns.value = [];
169
+ totalSavedRunCount.value = 0;
170
+ return removed;
171
+ }
172
+
173
+ function buildRunRecord(
174
+ status: ExecutionRunRecord["status"],
175
+ nextEntries: ExecutionTraceEntry[]
176
+ ): ExecutionRunRecord {
177
+ const lifecycleNode = lifecycleNodes.value.find(
178
+ (item) => item.id === selectedLifecycleNodeId.value
179
+ );
180
+ return {
181
+ runId: runIdRef.value ?? createRunId(),
182
+ blueprintId: blueprintId.value,
183
+ blueprintName: blueprintName.value,
184
+ lifecycleNodeId: selectedLifecycleNodeId.value ?? "",
185
+ lifecycleNodeLabel: lifecycleNode?.label,
186
+ lifecyclePhase: lifecycleNode?.phase,
187
+ startedAt: runStartedAtRef.value || Date.now(),
188
+ finishedAt:
189
+ status === "completed" || status === "failed" ? Date.now() : undefined,
190
+ status,
191
+ entries: nextEntries,
192
+ };
193
+ }
194
+
195
+ async function persistRunIfNeeded(record: ExecutionRunRecord) {
196
+ if (!settings.value.autoSave) return;
197
+ await putExecutionRunRecord(record);
198
+ await enforceSavedRunLimits();
199
+ }
200
+
201
+ function beginRun() {
202
+ if (!selectedLifecycleNodeId.value) return null;
203
+ runIdRef.value = createRunId();
204
+ runStartedAtRef.value = Date.now();
205
+ const runner = ensureRunner();
206
+ runner.beginDebugSession(selectedLifecycleNodeId.value);
207
+ chainComplete.value = false;
208
+ return runner;
209
+ }
210
+
211
+ watch(graphDebugSig, () => {
212
+ const runner = runnerRef.value;
213
+ if (!runner?.isDebugSessionActive()) return;
214
+ syncRunnerGraphConfig();
215
+ runner.refreshPendingQueueFromLatestGraph();
216
+ });
217
+
218
+ watch(
219
+ [entries, () => running.value, allowFalseSignalPropagation],
220
+ () => {
221
+ if (running.value) return;
222
+ const halted = shouldHaltDebugOnFalseSignal(
223
+ entries.value,
224
+ allowFalseSignalPropagation.value
225
+ );
226
+ if (halted) {
227
+ chainComplete.value = true;
228
+ return;
229
+ }
230
+ const runner = runnerRef.value;
231
+ if (
232
+ runner?.isDebugSessionActive() &&
233
+ runner.isDebugQueueEmpty() &&
234
+ entries.value.length > 0
235
+ ) {
236
+ chainComplete.value = true;
237
+ }
238
+ },
239
+ { deep: true }
240
+ );
241
+
242
+ async function runAll() {
243
+ if (!selectedLifecycleNodeId.value) return;
244
+ if (!(await validateBlueprintGraph())) return;
245
+ running.value = true;
246
+ logPanelOpen.value = true;
247
+ try {
248
+ const runner = beginRun();
249
+ if (!runner) return;
250
+ runner.updateGraph(buildRunnableGraph());
251
+ runner.refreshDebugLifecycleScope(selectedLifecycleNodeId.value);
252
+ const labels = buildNodeLabelMap(graph.value);
253
+ const onProgress = (trace: ExecutionTraceEntry[]) => {
254
+ entries.value = [...trace];
255
+ };
256
+ const result = await runner.debugRunAll(labels, onProgress);
257
+ entries.value = result;
258
+ chainComplete.value =
259
+ shouldHaltDebugOnFalseSignal(result, allowFalseSignalPropagation.value) ||
260
+ (runner.isDebugQueueEmpty() && result.length > 0);
261
+ await persistRunIfNeeded(buildRunRecord("completed", result));
262
+ } finally {
263
+ running.value = false;
264
+ }
265
+ }
266
+
267
+ async function stepNext() {
268
+ if (
269
+ !selectedLifecycleNodeId.value ||
270
+ chainComplete.value ||
271
+ shouldHaltDebugOnFalseSignal(entries.value, allowFalseSignalPropagation.value)
272
+ ) {
273
+ return;
274
+ }
275
+ running.value = true;
276
+ logPanelOpen.value = true;
277
+ try {
278
+ let runner: BlueprintGraphRunner | null = runnerRef.value;
279
+ if (!runner?.isDebugSessionActive()) {
280
+ if (!(await validateBlueprintGraph())) return;
281
+ const started = beginRun();
282
+ if (!started) return;
283
+ runner = started;
284
+ } else {
285
+ syncRunnerWithLatestGraph();
286
+ }
287
+ if (runner.isDebugQueueEmpty()) {
288
+ chainComplete.value = true;
289
+ return;
290
+ }
291
+ const labels = buildNodeLabelMap(graph.value);
292
+ const onProgress = (trace: ExecutionTraceEntry[]) => {
293
+ entries.value = [...trace];
294
+ };
295
+ const { done, haltedByFalseSignal } = await runner.debugStep(labels, onProgress);
296
+ const nextEntries = runner.getTraceEntries();
297
+ entries.value = nextEntries;
298
+ chainComplete.value = done || haltedByFalseSignal === true;
299
+ if (done) {
300
+ await persistRunIfNeeded(buildRunRecord("completed", nextEntries));
301
+ }
302
+ } finally {
303
+ running.value = false;
304
+ }
305
+ }
306
+
307
+ function stepBack() {
308
+ if (!selectedLifecycleNodeId.value) return;
309
+ const runner = runnerRef.value;
310
+ if (!runner?.isDebugSessionActive() || !runner.canDebugStepBack()) return;
311
+ running.value = true;
312
+ logPanelOpen.value = true;
313
+ try {
314
+ runner.debugStepBack();
315
+ const nextEntries = runner.getTraceEntries();
316
+ entries.value = nextEntries;
317
+ chainComplete.value =
318
+ shouldHaltDebugOnFalseSignal(nextEntries, allowFalseSignalPropagation.value) ||
319
+ (runner.isDebugSessionActive() && runner.isDebugQueueEmpty());
320
+ } finally {
321
+ running.value = false;
322
+ }
323
+ }
324
+
325
+ function resetToStart() {
326
+ if (!selectedLifecycleNodeId.value || entries.value.length === 0) return;
327
+ const runner = runnerRef.value;
328
+ if (!runner?.isDebugSessionActive()) return;
329
+ running.value = true;
330
+ logPanelOpen.value = true;
331
+ try {
332
+ syncRunnerWithLatestGraph();
333
+ runner.beginDebugSession(selectedLifecycleNodeId.value);
334
+ entries.value = [];
335
+ chainComplete.value = false;
336
+ } finally {
337
+ running.value = false;
338
+ }
339
+ }
340
+
341
+ const falseSignalHalt = computed(() =>
342
+ shouldHaltDebugOnFalseSignal(entries.value, allowFalseSignalPropagation.value)
343
+ );
344
+ const canResetToStart = computed(() => entries.value.length > 0);
345
+ const canStepBack = computed(() => entries.value.length > 0);
346
+ const canStepNext = computed(
347
+ () =>
348
+ Boolean(selectedLifecycleNodeId.value) &&
349
+ !chainComplete.value &&
350
+ !falseSignalHalt.value
351
+ );
352
+
353
+ async function saveCurrentRun() {
354
+ if (entries.value.length === 0) return;
355
+ const record = buildRunRecord(
356
+ runnerRef.value?.isDebugQueueEmpty() ? "completed" : "paused",
357
+ entries.value
358
+ );
359
+ await putExecutionRunRecord(record);
360
+ await enforceSavedRunLimits();
361
+ }
362
+
363
+ function exportCurrentRun() {
364
+ if (entries.value.length === 0) return;
365
+ downloadExecutionRunExport(
366
+ buildRunRecord(
367
+ runnerRef.value?.isDebugQueueEmpty() ? "completed" : "paused",
368
+ entries.value
369
+ )
370
+ );
371
+ }
372
+
373
+ function updateSettings(patch: Partial<ExecutionLogSettings>) {
374
+ const next = { ...settings.value, ...patch };
375
+ settings.value = next;
376
+ writeExecutionLogSettings(next);
377
+ if (patch.maxSavedRuns !== undefined) {
378
+ const maxSavedRuns = Math.max(1, Math.floor(patch.maxSavedRuns));
379
+ void trimExecutionRunRecordsToMax(maxSavedRuns).then(() => refreshSavedRuns());
380
+ }
381
+ }
382
+
383
+ function resetSession() {
384
+ runnerRef.value?.clearDebugSession();
385
+ runnerRef.value = null;
386
+ runIdRef.value = null;
387
+ entries.value = [];
388
+ chainComplete.value = false;
389
+ }
390
+
391
+ function abortClock(nodeId: string) {
392
+ runnerRef.value?.abortClockNode(nodeId);
393
+ }
394
+
395
+ function selectLifecycleNode(nodeId: string | null) {
396
+ selectedLifecycleNodeId.value = nodeId;
397
+ resetSession();
398
+ }
399
+
400
+ const executionOverlay = computed(() =>
401
+ buildExecutionOverlay(graph.value, entries.value)
402
+ );
403
+
404
+ return {
405
+ lifecycleNodes,
406
+ hasLifecycleNodes,
407
+ selectedLifecycleNodeId,
408
+ selectLifecycleNode,
409
+ entries,
410
+ executionOverlay,
411
+ logPanelOpen,
412
+ setLogPanelOpen: (value: boolean | ((prev: boolean) => boolean)) => {
413
+ logPanelOpen.value = typeof value === "function" ? value(logPanelOpen.value) : value;
414
+ },
415
+ running,
416
+ runAll,
417
+ resetToStart,
418
+ stepNext,
419
+ stepBack,
420
+ canResetToStart,
421
+ canStepBack,
422
+ canStepNext,
423
+ falseSignalHalt,
424
+ allowFalseSignalPropagation,
425
+ chainComplete,
426
+ saveCurrentRun,
427
+ exportCurrentRun,
428
+ settings,
429
+ updateSettings,
430
+ savedRuns,
431
+ refreshSavedRuns,
432
+ applyRetention: enforceSavedRunLimits,
433
+ clearAllSavedRuns,
434
+ resetSession,
435
+ clearLog: resetSession,
436
+ totalSavedRunCount,
437
+ abortClock,
438
+ };
439
+ }
440
+
441
+ export type UseBlueprintDebugSessionReturn = ReturnType<typeof useBlueprintDebugSession>;