@bratsos/workflow-engine-console 1.0.0-alpha.2

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,346 @@
1
+ import { ALL_CAPABILITIES, decodeCursor, ConsoleBadRequestError, clampLimit, encodeCursor, CONSOLE_STATUSES } from './chunk-THOZTVDT.js';
2
+
3
+ // src/in-memory-read-port.ts
4
+ function toRunSummary(run) {
5
+ return {
6
+ id: run.id,
7
+ createdAt: run.createdAt,
8
+ updatedAt: run.updatedAt,
9
+ workflowId: run.workflowId,
10
+ workflowName: run.workflowName,
11
+ workflowType: run.workflowType,
12
+ status: run.status,
13
+ startedAt: run.startedAt ?? null,
14
+ completedAt: run.completedAt ?? null,
15
+ duration: run.duration ?? null,
16
+ totalCost: run.totalCost ?? 0,
17
+ totalTokens: run.totalTokens ?? 0,
18
+ priority: run.priority ?? 0,
19
+ definitionVersion: run.definitionVersion ?? null,
20
+ redriveCount: run.redriveCount ?? 0
21
+ };
22
+ }
23
+ var InMemoryConsoleReadPort = class {
24
+ capabilities;
25
+ fixtures;
26
+ now;
27
+ constructor(fixtures, options) {
28
+ this.fixtures = {
29
+ runs: fixtures?.runs ?? [],
30
+ stages: fixtures?.stages ?? [],
31
+ steps: fixtures?.steps ?? [],
32
+ annotations: fixtures?.annotations ?? [],
33
+ logs: fixtures?.logs ?? [],
34
+ events: fixtures?.events ?? [],
35
+ jobs: fixtures?.jobs ?? []
36
+ };
37
+ this.capabilities = {
38
+ ...ALL_CAPABILITIES,
39
+ ...options?.capabilities
40
+ };
41
+ this.now = options?.now ?? (() => /* @__PURE__ */ new Date());
42
+ }
43
+ async listRuns(query) {
44
+ const sorted = [...this.fixtures.runs].sort((a, b) => {
45
+ const timeDiff = b.createdAt.getTime() - a.createdAt.getTime();
46
+ if (timeDiff !== 0) return timeDiff;
47
+ return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;
48
+ });
49
+ let cursor = null;
50
+ if (query.cursor !== void 0 && query.cursor !== "") {
51
+ cursor = decodeCursor(query.cursor);
52
+ if (cursor === null) {
53
+ throw new ConsoleBadRequestError("Invalid cursor.");
54
+ }
55
+ }
56
+ const statuses = query.filters?.status;
57
+ const workflowId = query.filters?.workflowId;
58
+ const workflowType = query.filters?.workflowType;
59
+ const definitionVersion = query.filters?.definitionVersion;
60
+ const createdAfter = query.filters?.createdAfter;
61
+ const createdBefore = query.filters?.createdBefore;
62
+ const matched = sorted.filter((run) => {
63
+ if (cursor !== null) {
64
+ const runTime = run.createdAt.getTime();
65
+ const cursorTime = cursor.createdAt.getTime();
66
+ if (runTime > cursorTime) return false;
67
+ if (runTime === cursorTime && !(run.id < cursor.id)) return false;
68
+ }
69
+ if (statuses !== void 0 && statuses.length > 0 && !statuses.includes(run.status)) {
70
+ return false;
71
+ }
72
+ if (workflowId !== void 0 && run.workflowId !== workflowId) {
73
+ return false;
74
+ }
75
+ if (workflowType !== void 0 && run.workflowType !== workflowType) {
76
+ return false;
77
+ }
78
+ if (definitionVersion !== void 0 && run.definitionVersion !== definitionVersion) {
79
+ return false;
80
+ }
81
+ if (createdAfter !== void 0 && run.createdAt.getTime() < createdAfter.getTime()) {
82
+ return false;
83
+ }
84
+ if (createdBefore !== void 0 && run.createdAt.getTime() >= createdBefore.getTime()) {
85
+ return false;
86
+ }
87
+ return true;
88
+ });
89
+ const limit = clampLimit(query.limit);
90
+ const hasNextPage = matched.length > limit;
91
+ const returnedRuns = hasNextPage ? matched.slice(0, limit) : matched;
92
+ const runs = returnedRuns.map(toRunSummary);
93
+ const last = runs.at(-1);
94
+ const nextCursor = hasNextPage && last !== void 0 ? encodeCursor({ createdAt: last.createdAt, id: last.id }) : null;
95
+ return { runs, nextCursor };
96
+ }
97
+ async getRunDetail(runId, options) {
98
+ const rawRun = this.fixtures.runs.find((r) => r.id === runId);
99
+ if (!rawRun) return null;
100
+ const stages = this.fixtures.stages.filter((stage) => stage.workflowRunId === runId).slice().sort((a, b) => {
101
+ if (a.stageNumber !== b.stageNumber) {
102
+ return a.stageNumber - b.stageNumber;
103
+ }
104
+ return a.stageId < b.stageId ? -1 : a.stageId > b.stageId ? 1 : 0;
105
+ });
106
+ const stageNumberById = /* @__PURE__ */ new Map();
107
+ for (const stage of stages) {
108
+ stageNumberById.set(stage.id, stage.stageNumber);
109
+ }
110
+ const stepLimit = clampLimit(options?.stepLimit, 200);
111
+ const matchingSteps = this.fixtures.steps.filter((step) => stageNumberById.has(step.stageRecordId)).slice().sort((a, b) => {
112
+ const stageNumA = stageNumberById.get(a.stageRecordId);
113
+ const stageNumB = stageNumberById.get(b.stageRecordId);
114
+ if (stageNumA !== stageNumB) {
115
+ return stageNumA - stageNumB;
116
+ }
117
+ return a.seq - b.seq;
118
+ });
119
+ const annotationLimit = clampLimit(options?.annotationLimit, 200);
120
+ const matchingAnnotations = this.fixtures.annotations.filter((ann) => ann.workflowRunId === runId).slice().sort((a, b) => {
121
+ const timeDiff = b.createdAt.getTime() - a.createdAt.getTime();
122
+ if (timeDiff !== 0) return timeDiff;
123
+ return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;
124
+ });
125
+ const logLimit = clampLimit(options?.logLimit, 200);
126
+ const matchingLogs = this.fixtures.logs.filter((log) => log.workflowRunId === runId).slice().sort((a, b) => {
127
+ const timeDiff = b.createdAt.getTime() - a.createdAt.getTime();
128
+ if (timeDiff !== 0) return timeDiff;
129
+ return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;
130
+ });
131
+ const eventLimit = clampLimit(options?.eventLimit, 200);
132
+ const matchingEvents = this.fixtures.events.filter((evt) => evt.workflowRunId === runId).slice().sort((a, b) => a.sequence - b.sequence);
133
+ const steps = matchingSteps.slice(0, stepLimit);
134
+ const annotations = matchingAnnotations.slice(0, annotationLimit);
135
+ const logs = matchingLogs.slice(0, logLimit);
136
+ const events = matchingEvents.slice(0, eventLimit);
137
+ const truncated = {
138
+ steps: matchingSteps.length > stepLimit,
139
+ annotations: matchingAnnotations.length > annotationLimit,
140
+ logs: matchingLogs.length > logLimit,
141
+ events: matchingEvents.length > eventLimit
142
+ };
143
+ const run = {
144
+ ...toRunSummary(rawRun),
145
+ input: rawRun.input ?? null,
146
+ output: rawRun.output ?? null,
147
+ config: rawRun.config ?? null,
148
+ metadata: rawRun.metadata ?? null
149
+ };
150
+ return {
151
+ run,
152
+ stages,
153
+ steps,
154
+ annotations,
155
+ logs,
156
+ events,
157
+ truncated
158
+ };
159
+ }
160
+ async listRunEvents(runId, afterSequence, limit) {
161
+ const clampedLimit = clampLimit(limit, 200);
162
+ const floorSeq = Math.floor(afterSequence);
163
+ return this.fixtures.events.filter((evt) => evt.workflowRunId === runId && evt.sequence > floorSeq).slice().sort((a, b) => a.sequence - b.sequence).slice(0, clampedLimit);
164
+ }
165
+ async getQueueHealth() {
166
+ const countsByStatus = Object.fromEntries(
167
+ CONSOLE_STATUSES.map((status) => [status, 0])
168
+ );
169
+ let oldestPendingAt = null;
170
+ let oldestLeaseAt = null;
171
+ let overduePolls = 0;
172
+ const nowTime = this.now().getTime();
173
+ for (const job of this.fixtures.jobs) {
174
+ if (job.status in countsByStatus) {
175
+ countsByStatus[job.status] += 1;
176
+ }
177
+ if (job.status === "PENDING") {
178
+ if (oldestPendingAt === null || job.createdAt.getTime() < oldestPendingAt.getTime()) {
179
+ oldestPendingAt = job.createdAt;
180
+ }
181
+ } else if (job.status === "RUNNING") {
182
+ if (job.lockedAt != null) {
183
+ if (oldestLeaseAt === null || job.lockedAt.getTime() < oldestLeaseAt.getTime()) {
184
+ oldestLeaseAt = job.lockedAt;
185
+ }
186
+ }
187
+ } else if (job.status === "SUSPENDED") {
188
+ if (job.nextPollAt != null && job.nextPollAt.getTime() <= nowTime) {
189
+ overduePolls += 1;
190
+ }
191
+ }
192
+ }
193
+ return {
194
+ countsByStatus,
195
+ oldestPendingAt,
196
+ oldestLeaseAt,
197
+ overduePolls
198
+ };
199
+ }
200
+ async listSuspendedStages(limit) {
201
+ const clampedLimit = clampLimit(limit, 50);
202
+ const runById = /* @__PURE__ */ new Map();
203
+ for (const run of this.fixtures.runs) {
204
+ runById.set(run.id, run.workflowId);
205
+ }
206
+ const matching = [];
207
+ for (const stage of this.fixtures.stages) {
208
+ if (stage.status !== "SUSPENDED" || stage.nextPollAt == null) {
209
+ continue;
210
+ }
211
+ const workflowId = runById.get(stage.workflowRunId);
212
+ if (workflowId === void 0) {
213
+ continue;
214
+ }
215
+ matching.push({
216
+ id: stage.id,
217
+ workflowRunId: stage.workflowRunId,
218
+ workflowId,
219
+ stageId: stage.stageId,
220
+ stageName: stage.stageName,
221
+ attempt: stage.attempt,
222
+ nextPollAt: stage.nextPollAt,
223
+ pollInterval: stage.pollInterval ?? null,
224
+ maxWaitUntil: stage.maxWaitUntil ?? null
225
+ });
226
+ }
227
+ matching.sort((a, b) => a.nextPollAt.getTime() - b.nextPollAt.getTime());
228
+ return matching.slice(0, clampedLimit);
229
+ }
230
+ async listDeadLetters(limit) {
231
+ const clampedLimit = clampLimit(limit, 50);
232
+ return this.fixtures.events.filter((evt) => evt.dlqAt != null).slice().sort((a, b) => b.dlqAt.getTime() - a.dlqAt.getTime()).slice(0, clampedLimit).map((evt) => ({
233
+ id: evt.id,
234
+ workflowRunId: evt.workflowRunId,
235
+ sequence: evt.sequence,
236
+ eventType: evt.eventType,
237
+ retryCount: evt.retryCount,
238
+ occurredAt: evt.occurredAt,
239
+ dlqAt: evt.dlqAt
240
+ }));
241
+ }
242
+ async listWorkers() {
243
+ const groups = /* @__PURE__ */ new Map();
244
+ for (const job of this.fixtures.jobs) {
245
+ if (job.status !== "RUNNING" || job.workerId == null) {
246
+ continue;
247
+ }
248
+ let acc = groups.get(job.workerId);
249
+ if (!acc) {
250
+ acc = {
251
+ runningJobs: 0,
252
+ oldestLockedAt: null,
253
+ lastSeenAt: null
254
+ };
255
+ groups.set(job.workerId, acc);
256
+ }
257
+ acc.runningJobs += 1;
258
+ if (job.lockedAt != null) {
259
+ const lockedTime = job.lockedAt.getTime();
260
+ if (acc.oldestLockedAt === null || lockedTime < acc.oldestLockedAt.getTime()) {
261
+ acc.oldestLockedAt = job.lockedAt;
262
+ }
263
+ if (acc.lastSeenAt === null || lockedTime > acc.lastSeenAt.getTime()) {
264
+ acc.lastSeenAt = job.lockedAt;
265
+ }
266
+ }
267
+ }
268
+ const workerIds = Array.from(groups.keys()).sort(
269
+ (a, b) => a < b ? -1 : a > b ? 1 : 0
270
+ );
271
+ return workerIds.map((workerId) => {
272
+ const acc = groups.get(workerId);
273
+ return {
274
+ workerId,
275
+ runningJobs: acc.runningJobs,
276
+ oldestLockedAt: acc.oldestLockedAt,
277
+ lastSeenAt: acc.lastSeenAt
278
+ };
279
+ });
280
+ }
281
+ async getCosts(query) {
282
+ const now = this.now();
283
+ const to = query.to ?? now;
284
+ const defaultDays = query.by === "workflow" ? 7 : 30;
285
+ const from = query.from ?? new Date(now.getTime() - defaultDays * 24 * 60 * 60 * 1e3);
286
+ const fromTime = from.getTime();
287
+ const toTime = to.getTime();
288
+ const filtered = this.fixtures.runs.filter((run) => {
289
+ const runTime = run.createdAt.getTime();
290
+ return runTime >= fromTime && runTime < toTime;
291
+ });
292
+ if (query.by === "workflow") {
293
+ const groups = /* @__PURE__ */ new Map();
294
+ for (const run of filtered) {
295
+ let acc = groups.get(run.workflowId);
296
+ if (!acc) {
297
+ acc = { runs: 0, cost: 0, tokens: 0 };
298
+ groups.set(run.workflowId, acc);
299
+ }
300
+ acc.runs += 1;
301
+ acc.cost += run.totalCost ?? 0;
302
+ acc.tokens += run.totalTokens ?? 0;
303
+ }
304
+ const buckets = Array.from(groups.entries()).map(
305
+ ([key, acc]) => ({
306
+ key,
307
+ runs: acc.runs,
308
+ cost: acc.cost,
309
+ tokens: acc.tokens
310
+ })
311
+ );
312
+ buckets.sort((a, b) => b.cost - a.cost);
313
+ return buckets;
314
+ } else {
315
+ const groups = /* @__PURE__ */ new Map();
316
+ for (const run of filtered) {
317
+ const key = run.createdAt.toISOString().slice(0, 10);
318
+ let acc = groups.get(key);
319
+ if (!acc) {
320
+ acc = { runs: 0, cost: 0, tokens: 0 };
321
+ groups.set(key, acc);
322
+ }
323
+ acc.runs += 1;
324
+ acc.cost += run.totalCost ?? 0;
325
+ acc.tokens += run.totalTokens ?? 0;
326
+ }
327
+ const buckets = Array.from(groups.entries()).map(
328
+ ([key, acc]) => ({
329
+ key,
330
+ runs: acc.runs,
331
+ cost: acc.cost,
332
+ tokens: acc.tokens
333
+ })
334
+ );
335
+ buckets.sort((a, b) => a.key < b.key ? 1 : a.key > b.key ? -1 : 0);
336
+ return buckets;
337
+ }
338
+ }
339
+ };
340
+ function createInMemoryConsoleReadPort(fixtures, options) {
341
+ return new InMemoryConsoleReadPort(fixtures, options);
342
+ }
343
+
344
+ export { InMemoryConsoleReadPort, createInMemoryConsoleReadPort };
345
+ //# sourceMappingURL=testing.js.map
346
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/in-memory-read-port.ts"],"names":[],"mappings":"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","file":"testing.js","sourcesContent":["import { ConsoleBadRequestError } from \"./errors\";\nimport {\n ALL_CAPABILITIES,\n type AnnotationSummary,\n CONSOLE_STATUSES,\n type ConsoleCapabilities,\n type ConsoleReadPort,\n type ConsoleStatus,\n type CostBucket,\n type CostQuery,\n clampLimit,\n type DeadLetter,\n decodeCursor,\n encodeCursor,\n type LogEntry,\n type QueueHealth,\n type RunDetail,\n type RunDetailOptions,\n type RunEvent,\n type RunListPage,\n type RunListQuery,\n type RunSummary,\n type StageSummary,\n type StepSummary,\n type SuspendedStage,\n type WorkerInstance,\n} from \"./read-port\";\n\n/** Mutable fixture store. Every array defaults to empty. */\nexport interface ConsoleFixtures {\n runs?: Array<\n Omit<RunSummary, \"definitionVersion\" | \"redriveCount\"> & {\n definitionVersion?: string | null;\n redriveCount?: number;\n input?: unknown;\n output?: unknown;\n config?: unknown;\n metadata?: unknown;\n }\n >;\n stages?: StageSummary[];\n steps?: StepSummary[];\n annotations?: AnnotationSummary[];\n logs?: LogEntry[];\n events?: RunEvent[];\n /** job_queue rows, reduced to what the console reads off them. */\n jobs?: Array<{\n id: string;\n workflowRunId: string;\n stageId: string;\n status: ConsoleStatus;\n createdAt: Date;\n workerId?: string | null;\n lockedAt?: Date | null;\n nextPollAt?: Date | null;\n }>;\n}\n\nexport interface InMemoryConsoleReadPortOptions {\n now?: () => Date;\n capabilities?: Partial<ConsoleCapabilities>;\n}\n\ntype FixtureRun = NonNullable<ConsoleFixtures[\"runs\"]>[number];\n\nfunction toRunSummary(run: FixtureRun): RunSummary {\n return {\n id: run.id,\n createdAt: run.createdAt,\n updatedAt: run.updatedAt,\n workflowId: run.workflowId,\n workflowName: run.workflowName,\n workflowType: run.workflowType,\n status: run.status,\n startedAt: run.startedAt ?? null,\n completedAt: run.completedAt ?? null,\n duration: run.duration ?? null,\n totalCost: run.totalCost ?? 0,\n totalTokens: run.totalTokens ?? 0,\n priority: run.priority ?? 0,\n definitionVersion: run.definitionVersion ?? null,\n redriveCount: run.redriveCount ?? 0,\n };\n}\n\nexport class InMemoryConsoleReadPort implements ConsoleReadPort {\n readonly capabilities: ConsoleCapabilities;\n readonly fixtures: Required<ConsoleFixtures>;\n private readonly now: () => Date;\n\n constructor(\n fixtures?: ConsoleFixtures,\n options?: InMemoryConsoleReadPortOptions,\n ) {\n this.fixtures = {\n runs: fixtures?.runs ?? [],\n stages: fixtures?.stages ?? [],\n steps: fixtures?.steps ?? [],\n annotations: fixtures?.annotations ?? [],\n logs: fixtures?.logs ?? [],\n events: fixtures?.events ?? [],\n jobs: fixtures?.jobs ?? [],\n };\n this.capabilities = {\n ...ALL_CAPABILITIES,\n ...options?.capabilities,\n };\n this.now = options?.now ?? (() => new Date());\n }\n\n async listRuns(query: RunListQuery): Promise<RunListPage> {\n const sorted = [...this.fixtures.runs].sort((a, b) => {\n const timeDiff = b.createdAt.getTime() - a.createdAt.getTime();\n if (timeDiff !== 0) return timeDiff;\n return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;\n });\n\n let cursor: { createdAt: Date; id: string } | null = null;\n if (query.cursor !== undefined && query.cursor !== \"\") {\n cursor = decodeCursor(query.cursor);\n if (cursor === null) {\n throw new ConsoleBadRequestError(\"Invalid cursor.\");\n }\n }\n\n const statuses = query.filters?.status;\n const workflowId = query.filters?.workflowId;\n const workflowType = query.filters?.workflowType;\n const definitionVersion = query.filters?.definitionVersion;\n const createdAfter = query.filters?.createdAfter;\n const createdBefore = query.filters?.createdBefore;\n\n const matched = sorted.filter((run) => {\n if (cursor !== null) {\n // In SQL: (\"createdAt\", id) < (cursor.createdAt, cursor.id). Drop runs not strictly less.\n const runTime = run.createdAt.getTime();\n const cursorTime = cursor.createdAt.getTime();\n if (runTime > cursorTime) return false;\n if (runTime === cursorTime && !(run.id < cursor.id)) return false;\n }\n\n if (\n statuses !== undefined &&\n statuses.length > 0 &&\n !statuses.includes(run.status)\n ) {\n return false;\n }\n if (workflowId !== undefined && run.workflowId !== workflowId) {\n return false;\n }\n if (workflowType !== undefined && run.workflowType !== workflowType) {\n return false;\n }\n if (\n definitionVersion !== undefined &&\n run.definitionVersion !== definitionVersion\n ) {\n return false;\n }\n if (\n createdAfter !== undefined &&\n run.createdAt.getTime() < createdAfter.getTime()\n ) {\n return false;\n }\n if (\n createdBefore !== undefined &&\n run.createdAt.getTime() >= createdBefore.getTime()\n ) {\n return false;\n }\n\n return true;\n });\n\n const limit = clampLimit(query.limit);\n const hasNextPage = matched.length > limit;\n const returnedRuns = hasNextPage ? matched.slice(0, limit) : matched;\n const runs = returnedRuns.map(toRunSummary);\n\n const last = runs.at(-1);\n const nextCursor =\n hasNextPage && last !== undefined\n ? encodeCursor({ createdAt: last.createdAt, id: last.id })\n : null;\n\n return { runs, nextCursor };\n }\n\n async getRunDetail(\n runId: string,\n options?: RunDetailOptions,\n ): Promise<RunDetail | null> {\n const rawRun = this.fixtures.runs.find((r) => r.id === runId);\n if (!rawRun) return null;\n\n const stages = this.fixtures.stages\n .filter((stage) => stage.workflowRunId === runId)\n .slice()\n .sort((a, b) => {\n if (a.stageNumber !== b.stageNumber) {\n return a.stageNumber - b.stageNumber;\n }\n return a.stageId < b.stageId ? -1 : a.stageId > b.stageId ? 1 : 0;\n });\n\n const stageNumberById = new Map<string, number>();\n for (const stage of stages) {\n stageNumberById.set(stage.id, stage.stageNumber);\n }\n\n const stepLimit = clampLimit(options?.stepLimit, 200);\n const matchingSteps = this.fixtures.steps\n .filter((step) => stageNumberById.has(step.stageRecordId))\n .slice()\n .sort((a, b) => {\n const stageNumA = stageNumberById.get(a.stageRecordId)!;\n const stageNumB = stageNumberById.get(b.stageRecordId)!;\n if (stageNumA !== stageNumB) {\n return stageNumA - stageNumB;\n }\n return a.seq - b.seq;\n });\n\n const annotationLimit = clampLimit(options?.annotationLimit, 200);\n const matchingAnnotations = this.fixtures.annotations\n .filter((ann) => ann.workflowRunId === runId)\n .slice()\n .sort((a, b) => {\n const timeDiff = b.createdAt.getTime() - a.createdAt.getTime();\n if (timeDiff !== 0) return timeDiff;\n return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;\n });\n\n const logLimit = clampLimit(options?.logLimit, 200);\n const matchingLogs = this.fixtures.logs\n .filter((log) => log.workflowRunId === runId)\n .slice()\n .sort((a, b) => {\n const timeDiff = b.createdAt.getTime() - a.createdAt.getTime();\n if (timeDiff !== 0) return timeDiff;\n return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;\n });\n\n const eventLimit = clampLimit(options?.eventLimit, 200);\n const matchingEvents = this.fixtures.events\n .filter((evt) => evt.workflowRunId === runId)\n .slice()\n .sort((a, b) => a.sequence - b.sequence);\n\n const steps = matchingSteps.slice(0, stepLimit);\n const annotations = matchingAnnotations.slice(0, annotationLimit);\n const logs = matchingLogs.slice(0, logLimit);\n const events = matchingEvents.slice(0, eventLimit);\n\n const truncated = {\n steps: matchingSteps.length > stepLimit,\n annotations: matchingAnnotations.length > annotationLimit,\n logs: matchingLogs.length > logLimit,\n events: matchingEvents.length > eventLimit,\n };\n\n const run = {\n ...toRunSummary(rawRun),\n input: rawRun.input ?? null,\n output: rawRun.output ?? null,\n config: rawRun.config ?? null,\n metadata: rawRun.metadata ?? null,\n };\n\n return {\n run,\n stages,\n steps,\n annotations,\n logs,\n events,\n truncated,\n };\n }\n\n async listRunEvents(\n runId: string,\n afterSequence: number,\n limit?: number,\n ): Promise<RunEvent[]> {\n const clampedLimit = clampLimit(limit, 200);\n const floorSeq = Math.floor(afterSequence);\n return this.fixtures.events\n .filter((evt) => evt.workflowRunId === runId && evt.sequence > floorSeq)\n .slice()\n .sort((a, b) => a.sequence - b.sequence)\n .slice(0, clampedLimit);\n }\n\n async getQueueHealth(): Promise<QueueHealth> {\n const countsByStatus = Object.fromEntries(\n CONSOLE_STATUSES.map((status) => [status, 0]),\n ) as Record<ConsoleStatus, number>;\n\n let oldestPendingAt: Date | null = null;\n let oldestLeaseAt: Date | null = null;\n let overduePolls = 0;\n const nowTime = this.now().getTime();\n\n for (const job of this.fixtures.jobs) {\n if (job.status in countsByStatus) {\n countsByStatus[job.status] += 1;\n }\n if (job.status === \"PENDING\") {\n if (\n oldestPendingAt === null ||\n job.createdAt.getTime() < oldestPendingAt.getTime()\n ) {\n oldestPendingAt = job.createdAt;\n }\n } else if (job.status === \"RUNNING\") {\n if (job.lockedAt != null) {\n if (\n oldestLeaseAt === null ||\n job.lockedAt.getTime() < oldestLeaseAt.getTime()\n ) {\n oldestLeaseAt = job.lockedAt;\n }\n }\n } else if (job.status === \"SUSPENDED\") {\n if (job.nextPollAt != null && job.nextPollAt.getTime() <= nowTime) {\n overduePolls += 1;\n }\n }\n }\n\n return {\n countsByStatus,\n oldestPendingAt,\n oldestLeaseAt,\n overduePolls,\n };\n }\n\n async listSuspendedStages(limit?: number): Promise<SuspendedStage[]> {\n const clampedLimit = clampLimit(limit, 50);\n\n const runById = new Map<string, string>();\n for (const run of this.fixtures.runs) {\n runById.set(run.id, run.workflowId);\n }\n\n const matching: SuspendedStage[] = [];\n for (const stage of this.fixtures.stages) {\n if (stage.status !== \"SUSPENDED\" || stage.nextPollAt == null) {\n continue;\n }\n // Mirror the SQL INNER JOIN: skip stages whose parent run is not in fixtures.\n const workflowId = runById.get(stage.workflowRunId);\n if (workflowId === undefined) {\n continue;\n }\n matching.push({\n id: stage.id,\n workflowRunId: stage.workflowRunId,\n workflowId,\n stageId: stage.stageId,\n stageName: stage.stageName,\n attempt: stage.attempt,\n nextPollAt: stage.nextPollAt,\n pollInterval: stage.pollInterval ?? null,\n maxWaitUntil: stage.maxWaitUntil ?? null,\n });\n }\n\n matching.sort((a, b) => a.nextPollAt!.getTime() - b.nextPollAt!.getTime());\n\n return matching.slice(0, clampedLimit);\n }\n\n async listDeadLetters(limit?: number): Promise<DeadLetter[]> {\n const clampedLimit = clampLimit(limit, 50);\n return this.fixtures.events\n .filter((evt) => evt.dlqAt != null)\n .slice()\n .sort((a, b) => b.dlqAt!.getTime() - a.dlqAt!.getTime())\n .slice(0, clampedLimit)\n .map((evt) => ({\n id: evt.id,\n workflowRunId: evt.workflowRunId,\n sequence: evt.sequence,\n eventType: evt.eventType,\n retryCount: evt.retryCount,\n occurredAt: evt.occurredAt,\n dlqAt: evt.dlqAt,\n }));\n }\n\n async listWorkers(): Promise<WorkerInstance[]> {\n interface WorkerAccumulator {\n runningJobs: number;\n oldestLockedAt: Date | null;\n lastSeenAt: Date | null;\n }\n\n const groups = new Map<string, WorkerAccumulator>();\n\n for (const job of this.fixtures.jobs) {\n if (job.status !== \"RUNNING\" || job.workerId == null) {\n continue;\n }\n let acc = groups.get(job.workerId);\n if (!acc) {\n acc = {\n runningJobs: 0,\n oldestLockedAt: null,\n lastSeenAt: null,\n };\n groups.set(job.workerId, acc);\n }\n acc.runningJobs += 1;\n if (job.lockedAt != null) {\n const lockedTime = job.lockedAt.getTime();\n if (\n acc.oldestLockedAt === null ||\n lockedTime < acc.oldestLockedAt.getTime()\n ) {\n acc.oldestLockedAt = job.lockedAt;\n }\n if (acc.lastSeenAt === null || lockedTime > acc.lastSeenAt.getTime()) {\n acc.lastSeenAt = job.lockedAt;\n }\n }\n }\n\n const workerIds = Array.from(groups.keys()).sort((a, b) =>\n a < b ? -1 : a > b ? 1 : 0,\n );\n\n return workerIds.map((workerId) => {\n const acc = groups.get(workerId)!;\n return {\n workerId,\n runningJobs: acc.runningJobs,\n oldestLockedAt: acc.oldestLockedAt,\n lastSeenAt: acc.lastSeenAt,\n };\n });\n }\n\n async getCosts(query: CostQuery): Promise<CostBucket[]> {\n const now = this.now();\n const to = query.to ?? now;\n const defaultDays = query.by === \"workflow\" ? 7 : 30;\n const from =\n query.from ?? new Date(now.getTime() - defaultDays * 24 * 60 * 60 * 1000);\n\n const fromTime = from.getTime();\n const toTime = to.getTime();\n\n const filtered = this.fixtures.runs.filter((run) => {\n const runTime = run.createdAt.getTime();\n return runTime >= fromTime && runTime < toTime;\n });\n\n interface CostAccumulator {\n runs: number;\n cost: number;\n tokens: number;\n }\n\n if (query.by === \"workflow\") {\n const groups = new Map<string, CostAccumulator>();\n for (const run of filtered) {\n let acc = groups.get(run.workflowId);\n if (!acc) {\n acc = { runs: 0, cost: 0, tokens: 0 };\n groups.set(run.workflowId, acc);\n }\n acc.runs += 1;\n acc.cost += run.totalCost ?? 0;\n acc.tokens += run.totalTokens ?? 0;\n }\n\n const buckets: CostBucket[] = Array.from(groups.entries()).map(\n ([key, acc]) => ({\n key,\n runs: acc.runs,\n cost: acc.cost,\n tokens: acc.tokens,\n }),\n );\n\n buckets.sort((a, b) => b.cost - a.cost);\n return buckets;\n } else {\n const groups = new Map<string, CostAccumulator>();\n for (const run of filtered) {\n // Matches Postgres to_char(date_trunc('day', \"createdAt\"), 'YYYY-MM-DD') on UTC timestamps.\n const key = run.createdAt.toISOString().slice(0, 10);\n let acc = groups.get(key);\n if (!acc) {\n acc = { runs: 0, cost: 0, tokens: 0 };\n groups.set(key, acc);\n }\n acc.runs += 1;\n acc.cost += run.totalCost ?? 0;\n acc.tokens += run.totalTokens ?? 0;\n }\n\n const buckets: CostBucket[] = Array.from(groups.entries()).map(\n ([key, acc]) => ({\n key,\n runs: acc.runs,\n cost: acc.cost,\n tokens: acc.tokens,\n }),\n );\n\n buckets.sort((a, b) => (a.key < b.key ? 1 : a.key > b.key ? -1 : 0));\n return buckets;\n }\n }\n}\n\nexport function createInMemoryConsoleReadPort(\n fixtures?: ConsoleFixtures,\n options?: InMemoryConsoleReadPortOptions,\n): InMemoryConsoleReadPort {\n return new InMemoryConsoleReadPort(fixtures, options);\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@bratsos/workflow-engine-console",
3
+ "version": "1.0.0-alpha.2",
4
+ "description": "Optional, embeddable operational console for @bratsos/workflow-engine: a fetch handler plus a prebuilt UI you mount in your own app",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Alex Bratsos",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/bratsos/workflow-engine.git",
11
+ "directory": "packages/workflow-engine-console"
12
+ },
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "bin": {
16
+ "workflow-console": "./dist/cli.js"
17
+ },
18
+ "exports": {
19
+ "./package.json": "./package.json",
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "./node": {
26
+ "types": "./dist/node.d.ts",
27
+ "import": "./dist/node.js",
28
+ "default": "./dist/node.js"
29
+ },
30
+ "./testing": {
31
+ "types": "./dist/testing.d.ts",
32
+ "import": "./dist/testing.js",
33
+ "default": "./dist/testing.js"
34
+ }
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "sideEffects": false,
40
+ "files": [
41
+ "dist"
42
+ ],
43
+ "scripts": {
44
+ "build:ui": "node scripts/build-ui.mjs",
45
+ "build": "node scripts/build-ui.mjs && tsup",
46
+ "test": "node scripts/build-ui.mjs && vitest run",
47
+ "typecheck": "node scripts/build-ui.mjs && tsc --noEmit"
48
+ },
49
+ "peerDependencies": {
50
+ "@bratsos/workflow-engine": "workspace:*"
51
+ },
52
+ "devDependencies": {
53
+ "@bratsos/workflow-engine": "workspace:*",
54
+ "@types/node": "^22",
55
+ "esbuild": "^0.25.0",
56
+ "preact": "^10.27.2",
57
+ "tsup": "^8.5.1",
58
+ "typescript": "^5.8.3",
59
+ "vitest": "^3.2.4"
60
+ },
61
+ "engines": {
62
+ "node": ">=22.11.0"
63
+ }
64
+ }