@duckflux/core 0.6.8 → 0.7.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.
@@ -0,0 +1,114 @@
1
+ import { join } from "node:path";
2
+ import type { StepTrace } from "../../model/index";
3
+ import type { TraceFinalizeMeta, TraceOpenMeta, TraceWriter } from "../index";
4
+
5
+ function toJson(value: unknown): string | null {
6
+ if (value === undefined || value === null) return null;
7
+ if (typeof value === "string") return value;
8
+ return JSON.stringify(value);
9
+ }
10
+
11
+ export class SqliteTraceWriter implements TraceWriter {
12
+ private dir: string;
13
+ private db: import("bun:sqlite").Database | null = null;
14
+ private executionId = "";
15
+
16
+ constructor(dir: string) {
17
+ this.dir = dir;
18
+ }
19
+
20
+ async open(meta: TraceOpenMeta): Promise<void> {
21
+ this.executionId = meta.id;
22
+ const filePath = join(this.dir, `${meta.id}.sqlite`);
23
+
24
+ const { Database } = await import("bun:sqlite");
25
+ this.db = new Database(filePath);
26
+
27
+ this.db.exec(`
28
+ CREATE TABLE IF NOT EXISTS executions (
29
+ id TEXT PRIMARY KEY,
30
+ workflow_id TEXT,
31
+ workflow_name TEXT,
32
+ workflow_version TEXT,
33
+ started_at TEXT NOT NULL,
34
+ finished_at TEXT,
35
+ duration_ms INTEGER,
36
+ status TEXT NOT NULL,
37
+ inputs TEXT,
38
+ output TEXT
39
+ );
40
+
41
+ CREATE TABLE IF NOT EXISTS steps (
42
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
43
+ execution_id TEXT NOT NULL REFERENCES executions(id),
44
+ seq INTEGER NOT NULL,
45
+ name TEXT NOT NULL,
46
+ type TEXT NOT NULL,
47
+ started_at TEXT,
48
+ finished_at TEXT,
49
+ duration_ms INTEGER,
50
+ status TEXT NOT NULL,
51
+ input TEXT,
52
+ output TEXT,
53
+ error TEXT,
54
+ retries INTEGER,
55
+ loop_index INTEGER
56
+ );
57
+ `);
58
+
59
+ const insert = this.db.prepare(`
60
+ INSERT INTO executions (id, workflow_id, workflow_name, workflow_version, started_at, status, inputs)
61
+ VALUES (?, ?, ?, ?, ?, 'running', ?)
62
+ `);
63
+ insert.run(
64
+ meta.id,
65
+ meta.workflowId ?? null,
66
+ meta.workflowName ?? null,
67
+ meta.workflowVersion !== undefined ? String(meta.workflowVersion) : null,
68
+ meta.startedAt,
69
+ toJson(meta.inputs),
70
+ );
71
+ }
72
+
73
+ writeStep(step: StepTrace): void {
74
+ if (!this.db) return;
75
+ const insert = this.db.prepare(`
76
+ INSERT INTO steps
77
+ (execution_id, seq, name, type, started_at, finished_at, duration_ms, status, input, output, error, retries, loop_index)
78
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
79
+ `);
80
+ insert.run(
81
+ this.executionId,
82
+ step.seq,
83
+ step.name,
84
+ step.type,
85
+ step.startedAt ?? null,
86
+ step.finishedAt ?? null,
87
+ step.duration ?? null,
88
+ step.status,
89
+ toJson(step.input),
90
+ toJson(step.output),
91
+ step.error ?? null,
92
+ step.retries ?? null,
93
+ step.loopIndex ?? null,
94
+ );
95
+ }
96
+
97
+ async finalize(meta: TraceFinalizeMeta): Promise<void> {
98
+ if (!this.db) return;
99
+ const update = this.db.prepare(`
100
+ UPDATE executions
101
+ SET status = ?, output = ?, finished_at = ?, duration_ms = ?
102
+ WHERE id = ?
103
+ `);
104
+ update.run(
105
+ meta.status,
106
+ toJson(meta.output),
107
+ meta.finishedAt,
108
+ Math.round(meta.duration),
109
+ this.executionId,
110
+ );
111
+ this.db.close();
112
+ this.db = null;
113
+ }
114
+ }
@@ -0,0 +1,82 @@
1
+ import { appendFile, readFile, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import type { StepTrace } from "../../model/index";
4
+ import type { TraceFinalizeMeta, TraceOpenMeta, TraceWriter } from "../index";
5
+
6
+ function serializeValue(value: unknown): string {
7
+ if (value === undefined || value === null) return "none";
8
+ if (typeof value === "string") return value;
9
+ return JSON.stringify(value, null, 2);
10
+ }
11
+
12
+ function formatStep(step: StepTrace): string {
13
+ const lines: string[] = [
14
+ `## [${step.seq}] ${step.name} (${step.type})`,
15
+ `startedAt: ${step.startedAt}`,
16
+ ];
17
+ if (step.finishedAt) lines.push(`finishedAt: ${step.finishedAt}`);
18
+ if (step.duration !== undefined) lines.push(`duration: ${step.duration}ms`);
19
+ lines.push(`status: ${step.status}`);
20
+ if (step.loopIndex !== undefined) lines.push(`loopIndex: ${step.loopIndex}`);
21
+ if (step.retries !== undefined && step.retries > 0) lines.push(`retries: ${step.retries}`);
22
+ lines.push(`input: ${serializeValue(step.input)}`);
23
+ lines.push(`output: ${serializeValue(step.output)}`);
24
+ if (step.error) lines.push(`error: ${step.error}`);
25
+ lines.push("");
26
+ return lines.join("\n");
27
+ }
28
+
29
+ export class TxtTraceWriter implements TraceWriter {
30
+ private dir: string;
31
+ private filePath = "";
32
+
33
+ constructor(dir: string) {
34
+ this.dir = dir;
35
+ }
36
+
37
+ async open(meta: TraceOpenMeta): Promise<void> {
38
+ this.filePath = join(this.dir, `${meta.id}.txt`);
39
+
40
+ const versionStr = meta.workflowVersion !== undefined ? ` (v${meta.workflowVersion})` : "";
41
+ const workflowLabel = meta.workflowName ?? meta.workflowId ?? "unnamed";
42
+
43
+ const header = [
44
+ "# execution",
45
+ `id: ${meta.id}`,
46
+ `workflow: ${workflowLabel}${versionStr}`,
47
+ `startedAt: ${meta.startedAt}`,
48
+ "status: running",
49
+ "",
50
+ "# inputs",
51
+ serializeValue(meta.inputs),
52
+ "",
53
+ "# steps",
54
+ "",
55
+ ].join("\n");
56
+
57
+ await writeFile(this.filePath, header, "utf-8");
58
+ }
59
+
60
+ async writeStep(step: StepTrace): Promise<void> {
61
+ if (!this.filePath) return;
62
+ await appendFile(this.filePath, formatStep(step), "utf-8");
63
+ }
64
+
65
+ async finalize(meta: TraceFinalizeMeta): Promise<void> {
66
+ if (!this.filePath) return;
67
+
68
+ // Append output section
69
+ const outputSection = [
70
+ "# output",
71
+ serializeValue(meta.output),
72
+ "",
73
+ ].join("\n");
74
+ await appendFile(this.filePath, outputSection, "utf-8");
75
+
76
+ // Rewrite the status: running line with final values
77
+ const content = await readFile(this.filePath, "utf-8");
78
+ const updated = content
79
+ .replace(/^status: running$/m, `status: ${meta.status}\nfinishedAt: ${meta.finishedAt}\nduration: ${meta.duration}ms`);
80
+ await writeFile(this.filePath, updated, "utf-8");
81
+ }
82
+ }