@bratsos/workflow-engine 0.0.11 → 0.2.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 +270 -513
- package/dist/chunk-D7RVRRM2.js +3 -0
- package/dist/chunk-D7RVRRM2.js.map +1 -0
- package/dist/chunk-HL3OJG7W.js +1033 -0
- package/dist/chunk-HL3OJG7W.js.map +1 -0
- package/dist/chunk-MUWP5SF2.js +33 -0
- package/dist/chunk-MUWP5SF2.js.map +1 -0
- package/dist/chunk-NYKMT46J.js +1143 -0
- package/dist/chunk-NYKMT46J.js.map +1 -0
- package/dist/chunk-P4KMGCT3.js +2292 -0
- package/dist/chunk-P4KMGCT3.js.map +1 -0
- package/dist/chunk-SPXBCZLB.js +17 -0
- package/dist/chunk-SPXBCZLB.js.map +1 -0
- package/dist/cli/sync-models.d.ts +1 -0
- package/dist/cli/sync-models.js +210 -0
- package/dist/cli/sync-models.js.map +1 -0
- package/dist/client-D4PoxADF.d.ts +798 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.js +4 -0
- package/dist/client.js.map +1 -0
- package/dist/index-DAzCfO1R.d.ts +217 -0
- package/dist/index.d.ts +569 -0
- package/dist/index.js +399 -0
- package/dist/index.js.map +1 -0
- package/dist/interface-MMqhfQQK.d.ts +411 -0
- package/dist/kernel/index.d.ts +26 -0
- package/dist/kernel/index.js +3 -0
- package/dist/kernel/index.js.map +1 -0
- package/dist/kernel/testing/index.d.ts +44 -0
- package/dist/kernel/testing/index.js +85 -0
- package/dist/kernel/testing/index.js.map +1 -0
- package/dist/persistence/index.d.ts +2 -0
- package/dist/persistence/index.js +6 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/prisma/index.d.ts +37 -0
- package/dist/persistence/prisma/index.js +5 -0
- package/dist/persistence/prisma/index.js.map +1 -0
- package/dist/plugins-BCnDUwIc.d.ts +415 -0
- package/dist/ports-tU3rzPXJ.d.ts +245 -0
- package/dist/stage-BPw7m9Wx.d.ts +144 -0
- package/dist/testing/index.d.ts +264 -0
- package/dist/testing/index.js +920 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +11 -1
- package/skills/workflow-engine/SKILL.md +234 -348
- package/skills/workflow-engine/references/03-runtime-setup.md +111 -426
- package/skills/workflow-engine/references/05-persistence-setup.md +32 -0
- package/skills/workflow-engine/references/07-testing-patterns.md +141 -474
- package/skills/workflow-engine/references/08-common-patterns.md +118 -431
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
import { StaleVersionError } from '../chunk-SPXBCZLB.js';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
|
|
4
|
+
var InMemoryAICallLogger = class {
|
|
5
|
+
calls = /* @__PURE__ */ new Map();
|
|
6
|
+
recordedBatches = /* @__PURE__ */ new Set();
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Core Operations
|
|
9
|
+
// ============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* Log a single AI call (fire and forget)
|
|
12
|
+
*/
|
|
13
|
+
logCall(call) {
|
|
14
|
+
const id = randomUUID();
|
|
15
|
+
const record = {
|
|
16
|
+
id,
|
|
17
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
18
|
+
topic: call.topic,
|
|
19
|
+
callType: call.callType,
|
|
20
|
+
modelKey: call.modelKey,
|
|
21
|
+
modelId: call.modelId,
|
|
22
|
+
prompt: call.prompt,
|
|
23
|
+
response: call.response,
|
|
24
|
+
inputTokens: call.inputTokens,
|
|
25
|
+
outputTokens: call.outputTokens,
|
|
26
|
+
cost: call.cost,
|
|
27
|
+
metadata: call.metadata ?? null
|
|
28
|
+
};
|
|
29
|
+
this.calls.set(id, record);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Log batch results (for recording batch API results)
|
|
33
|
+
*/
|
|
34
|
+
async logBatchResults(batchId, results) {
|
|
35
|
+
this.recordedBatches.add(batchId);
|
|
36
|
+
for (const result of results) {
|
|
37
|
+
this.logCall({
|
|
38
|
+
...result,
|
|
39
|
+
metadata: {
|
|
40
|
+
...result.metadata,
|
|
41
|
+
batchId
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get aggregated stats for a topic prefix
|
|
48
|
+
*/
|
|
49
|
+
async getStats(topicPrefix) {
|
|
50
|
+
const matchingCalls = Array.from(this.calls.values()).filter(
|
|
51
|
+
(call) => call.topic.startsWith(topicPrefix)
|
|
52
|
+
);
|
|
53
|
+
const stats = {
|
|
54
|
+
totalCalls: matchingCalls.length,
|
|
55
|
+
totalInputTokens: 0,
|
|
56
|
+
totalOutputTokens: 0,
|
|
57
|
+
totalCost: 0,
|
|
58
|
+
perModel: {}
|
|
59
|
+
};
|
|
60
|
+
for (const call of matchingCalls) {
|
|
61
|
+
stats.totalInputTokens += call.inputTokens;
|
|
62
|
+
stats.totalOutputTokens += call.outputTokens;
|
|
63
|
+
stats.totalCost += call.cost;
|
|
64
|
+
if (!stats.perModel[call.modelKey]) {
|
|
65
|
+
stats.perModel[call.modelKey] = {
|
|
66
|
+
calls: 0,
|
|
67
|
+
inputTokens: 0,
|
|
68
|
+
outputTokens: 0,
|
|
69
|
+
cost: 0
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
stats.perModel[call.modelKey].calls++;
|
|
73
|
+
stats.perModel[call.modelKey].inputTokens += call.inputTokens;
|
|
74
|
+
stats.perModel[call.modelKey].outputTokens += call.outputTokens;
|
|
75
|
+
stats.perModel[call.modelKey].cost += call.cost;
|
|
76
|
+
}
|
|
77
|
+
return stats;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if batch results are already recorded
|
|
81
|
+
*/
|
|
82
|
+
async isRecorded(batchId) {
|
|
83
|
+
return this.recordedBatches.has(batchId);
|
|
84
|
+
}
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// Test Helpers
|
|
87
|
+
// ============================================================================
|
|
88
|
+
/**
|
|
89
|
+
* Clear all data - useful between tests
|
|
90
|
+
*/
|
|
91
|
+
clear() {
|
|
92
|
+
this.calls.clear();
|
|
93
|
+
this.recordedBatches.clear();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get all calls for inspection
|
|
97
|
+
*/
|
|
98
|
+
getAllCalls() {
|
|
99
|
+
return Array.from(this.calls.values()).map((c) => ({ ...c }));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get calls by topic for inspection
|
|
103
|
+
*/
|
|
104
|
+
getCallsByTopic(topic) {
|
|
105
|
+
return Array.from(this.calls.values()).filter((c) => c.topic === topic).map((c) => ({ ...c }));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get calls by topic prefix for inspection
|
|
109
|
+
*/
|
|
110
|
+
getCallsByTopicPrefix(prefix) {
|
|
111
|
+
return Array.from(this.calls.values()).filter((c) => c.topic.startsWith(prefix)).map((c) => ({ ...c }));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get calls by model for inspection
|
|
115
|
+
*/
|
|
116
|
+
getCallsByModel(modelKey) {
|
|
117
|
+
return Array.from(this.calls.values()).filter((c) => c.modelKey === modelKey).map((c) => ({ ...c }));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get calls by call type for inspection
|
|
121
|
+
*/
|
|
122
|
+
getCallsByType(callType) {
|
|
123
|
+
return Array.from(this.calls.values()).filter((c) => c.callType === callType).map((c) => ({ ...c }));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get total cost across all calls
|
|
127
|
+
*/
|
|
128
|
+
getTotalCost() {
|
|
129
|
+
let total = 0;
|
|
130
|
+
for (const call of this.calls.values()) {
|
|
131
|
+
total += call.cost;
|
|
132
|
+
}
|
|
133
|
+
return total;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get total tokens across all calls
|
|
137
|
+
*/
|
|
138
|
+
getTotalTokens() {
|
|
139
|
+
let input = 0;
|
|
140
|
+
let output = 0;
|
|
141
|
+
for (const call of this.calls.values()) {
|
|
142
|
+
input += call.inputTokens;
|
|
143
|
+
output += call.outputTokens;
|
|
144
|
+
}
|
|
145
|
+
return { input, output };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get call count
|
|
149
|
+
*/
|
|
150
|
+
getCallCount() {
|
|
151
|
+
return this.calls.size;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get all recorded batch IDs
|
|
155
|
+
*/
|
|
156
|
+
getRecordedBatchIds() {
|
|
157
|
+
return Array.from(this.recordedBatches);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get the last call made (useful for assertions)
|
|
161
|
+
*/
|
|
162
|
+
getLastCall() {
|
|
163
|
+
const calls = Array.from(this.calls.values());
|
|
164
|
+
if (calls.length === 0) return null;
|
|
165
|
+
calls.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
166
|
+
return { ...calls[0] };
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Assert a call was made with specific properties
|
|
170
|
+
*/
|
|
171
|
+
hasCallMatching(predicate) {
|
|
172
|
+
return Array.from(this.calls.values()).some(predicate);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
var InMemoryJobQueue = class {
|
|
176
|
+
jobs = /* @__PURE__ */ new Map();
|
|
177
|
+
workerId;
|
|
178
|
+
defaultMaxAttempts = 3;
|
|
179
|
+
constructor(workerId) {
|
|
180
|
+
this.workerId = workerId ?? `worker-${randomUUID().slice(0, 8)}`;
|
|
181
|
+
}
|
|
182
|
+
// ============================================================================
|
|
183
|
+
// Core Operations
|
|
184
|
+
// ============================================================================
|
|
185
|
+
async enqueue(options) {
|
|
186
|
+
const now = /* @__PURE__ */ new Date();
|
|
187
|
+
const id = randomUUID();
|
|
188
|
+
const job = {
|
|
189
|
+
id,
|
|
190
|
+
createdAt: now,
|
|
191
|
+
updatedAt: now,
|
|
192
|
+
workflowRunId: options.workflowRunId,
|
|
193
|
+
workflowId: options.workflowId,
|
|
194
|
+
stageId: options.stageId,
|
|
195
|
+
status: "PENDING",
|
|
196
|
+
priority: options.priority ?? 5,
|
|
197
|
+
workerId: null,
|
|
198
|
+
lockedAt: null,
|
|
199
|
+
startedAt: null,
|
|
200
|
+
completedAt: null,
|
|
201
|
+
attempt: 1,
|
|
202
|
+
maxAttempts: this.defaultMaxAttempts,
|
|
203
|
+
lastError: null,
|
|
204
|
+
nextPollAt: options.scheduledFor ?? null,
|
|
205
|
+
payload: options.payload ?? {}
|
|
206
|
+
};
|
|
207
|
+
this.jobs.set(id, job);
|
|
208
|
+
return id;
|
|
209
|
+
}
|
|
210
|
+
async enqueueParallel(jobs) {
|
|
211
|
+
const ids = [];
|
|
212
|
+
for (const job of jobs) {
|
|
213
|
+
const id = await this.enqueue(job);
|
|
214
|
+
ids.push(id);
|
|
215
|
+
}
|
|
216
|
+
return ids;
|
|
217
|
+
}
|
|
218
|
+
async dequeue() {
|
|
219
|
+
const now = /* @__PURE__ */ new Date();
|
|
220
|
+
const pendingJobs = Array.from(this.jobs.values()).filter(
|
|
221
|
+
(j) => j.status === "PENDING" && (j.nextPollAt === null || j.nextPollAt <= now)
|
|
222
|
+
).sort((a, b) => {
|
|
223
|
+
if (b.priority !== a.priority) {
|
|
224
|
+
return b.priority - a.priority;
|
|
225
|
+
}
|
|
226
|
+
return a.createdAt.getTime() - b.createdAt.getTime();
|
|
227
|
+
});
|
|
228
|
+
if (pendingJobs.length === 0) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
const job = pendingJobs[0];
|
|
232
|
+
const updated = {
|
|
233
|
+
...job,
|
|
234
|
+
status: "RUNNING",
|
|
235
|
+
workerId: this.workerId,
|
|
236
|
+
lockedAt: now,
|
|
237
|
+
startedAt: now,
|
|
238
|
+
updatedAt: now
|
|
239
|
+
};
|
|
240
|
+
this.jobs.set(job.id, updated);
|
|
241
|
+
return {
|
|
242
|
+
jobId: job.id,
|
|
243
|
+
workflowRunId: job.workflowRunId,
|
|
244
|
+
workflowId: job.workflowId,
|
|
245
|
+
stageId: job.stageId,
|
|
246
|
+
priority: job.priority,
|
|
247
|
+
attempt: job.attempt,
|
|
248
|
+
maxAttempts: job.maxAttempts,
|
|
249
|
+
payload: job.payload
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
async complete(jobId) {
|
|
253
|
+
const job = this.jobs.get(jobId);
|
|
254
|
+
if (!job) {
|
|
255
|
+
throw new Error(`Job not found: ${jobId}`);
|
|
256
|
+
}
|
|
257
|
+
const now = /* @__PURE__ */ new Date();
|
|
258
|
+
const updated = {
|
|
259
|
+
...job,
|
|
260
|
+
status: "COMPLETED",
|
|
261
|
+
completedAt: now,
|
|
262
|
+
updatedAt: now
|
|
263
|
+
};
|
|
264
|
+
this.jobs.set(jobId, updated);
|
|
265
|
+
}
|
|
266
|
+
async suspend(jobId, nextPollAt) {
|
|
267
|
+
const job = this.jobs.get(jobId);
|
|
268
|
+
if (!job) {
|
|
269
|
+
throw new Error(`Job not found: ${jobId}`);
|
|
270
|
+
}
|
|
271
|
+
const updated = {
|
|
272
|
+
...job,
|
|
273
|
+
status: "SUSPENDED",
|
|
274
|
+
nextPollAt,
|
|
275
|
+
workerId: null,
|
|
276
|
+
lockedAt: null,
|
|
277
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
278
|
+
};
|
|
279
|
+
this.jobs.set(jobId, updated);
|
|
280
|
+
}
|
|
281
|
+
async fail(jobId, error, shouldRetry = true) {
|
|
282
|
+
const job = this.jobs.get(jobId);
|
|
283
|
+
if (!job) {
|
|
284
|
+
throw new Error(`Job not found: ${jobId}`);
|
|
285
|
+
}
|
|
286
|
+
const now = /* @__PURE__ */ new Date();
|
|
287
|
+
if (shouldRetry && job.attempt < job.maxAttempts) {
|
|
288
|
+
const updated = {
|
|
289
|
+
...job,
|
|
290
|
+
status: "PENDING",
|
|
291
|
+
attempt: job.attempt + 1,
|
|
292
|
+
lastError: error,
|
|
293
|
+
workerId: null,
|
|
294
|
+
lockedAt: null,
|
|
295
|
+
updatedAt: now
|
|
296
|
+
};
|
|
297
|
+
this.jobs.set(jobId, updated);
|
|
298
|
+
} else {
|
|
299
|
+
const updated = {
|
|
300
|
+
...job,
|
|
301
|
+
status: "FAILED",
|
|
302
|
+
lastError: error,
|
|
303
|
+
completedAt: now,
|
|
304
|
+
updatedAt: now
|
|
305
|
+
};
|
|
306
|
+
this.jobs.set(jobId, updated);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
async getSuspendedJobsReadyToPoll() {
|
|
310
|
+
const now = /* @__PURE__ */ new Date();
|
|
311
|
+
return Array.from(this.jobs.values()).filter(
|
|
312
|
+
(j) => j.status === "SUSPENDED" && j.nextPollAt && j.nextPollAt <= now
|
|
313
|
+
).map((j) => ({
|
|
314
|
+
jobId: j.id,
|
|
315
|
+
stageId: j.stageId,
|
|
316
|
+
workflowRunId: j.workflowRunId
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
async releaseStaleJobs(staleThresholdMs = 6e4) {
|
|
320
|
+
const now = /* @__PURE__ */ new Date();
|
|
321
|
+
const threshold = new Date(now.getTime() - staleThresholdMs);
|
|
322
|
+
let released = 0;
|
|
323
|
+
for (const job of this.jobs.values()) {
|
|
324
|
+
if (job.status === "RUNNING" && job.lockedAt && job.lockedAt < threshold) {
|
|
325
|
+
const updated = {
|
|
326
|
+
...job,
|
|
327
|
+
status: "PENDING",
|
|
328
|
+
workerId: null,
|
|
329
|
+
lockedAt: null,
|
|
330
|
+
updatedAt: now
|
|
331
|
+
};
|
|
332
|
+
this.jobs.set(job.id, updated);
|
|
333
|
+
released++;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return released;
|
|
337
|
+
}
|
|
338
|
+
// ============================================================================
|
|
339
|
+
// Test Helpers
|
|
340
|
+
// ============================================================================
|
|
341
|
+
/**
|
|
342
|
+
* Clear all jobs - useful between tests
|
|
343
|
+
*/
|
|
344
|
+
clear() {
|
|
345
|
+
this.jobs.clear();
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Get all jobs for inspection
|
|
349
|
+
*/
|
|
350
|
+
getAllJobs() {
|
|
351
|
+
return Array.from(this.jobs.values()).map((j) => ({ ...j }));
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Get jobs by status for inspection
|
|
355
|
+
*/
|
|
356
|
+
getJobsByStatus(status) {
|
|
357
|
+
return Array.from(this.jobs.values()).filter((j) => j.status === status).map((j) => ({ ...j }));
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Get a specific job by ID
|
|
361
|
+
*/
|
|
362
|
+
getJob(jobId) {
|
|
363
|
+
const job = this.jobs.get(jobId);
|
|
364
|
+
return job ? { ...job } : null;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Get the worker ID for this queue instance
|
|
368
|
+
*/
|
|
369
|
+
getWorkerId() {
|
|
370
|
+
return this.workerId;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Set max attempts for new jobs
|
|
374
|
+
*/
|
|
375
|
+
setDefaultMaxAttempts(maxAttempts) {
|
|
376
|
+
this.defaultMaxAttempts = maxAttempts;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Simulate a worker crash by releasing a job's lock without completing it
|
|
380
|
+
*/
|
|
381
|
+
simulateCrash(jobId) {
|
|
382
|
+
const job = this.jobs.get(jobId);
|
|
383
|
+
if (job && job.status === "RUNNING") ;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Move a suspended job back to pending (for manual resume testing)
|
|
387
|
+
*/
|
|
388
|
+
resumeJob(jobId) {
|
|
389
|
+
const job = this.jobs.get(jobId);
|
|
390
|
+
if (job && job.status === "SUSPENDED") {
|
|
391
|
+
const updated = {
|
|
392
|
+
...job,
|
|
393
|
+
status: "PENDING",
|
|
394
|
+
nextPollAt: null,
|
|
395
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
396
|
+
};
|
|
397
|
+
this.jobs.set(jobId, updated);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Set lockedAt for testing stale job scenarios
|
|
402
|
+
*/
|
|
403
|
+
setJobLockedAt(jobId, lockedAt) {
|
|
404
|
+
const job = this.jobs.get(jobId);
|
|
405
|
+
if (job) {
|
|
406
|
+
const updated = {
|
|
407
|
+
...job,
|
|
408
|
+
lockedAt
|
|
409
|
+
};
|
|
410
|
+
this.jobs.set(jobId, updated);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Set nextPollAt for testing suspended job polling
|
|
415
|
+
*/
|
|
416
|
+
setJobNextPollAt(jobId, nextPollAt) {
|
|
417
|
+
const job = this.jobs.get(jobId);
|
|
418
|
+
if (job) {
|
|
419
|
+
const updated = {
|
|
420
|
+
...job,
|
|
421
|
+
nextPollAt
|
|
422
|
+
};
|
|
423
|
+
this.jobs.set(jobId, updated);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
var InMemoryWorkflowPersistence = class {
|
|
428
|
+
runs = /* @__PURE__ */ new Map();
|
|
429
|
+
stages = /* @__PURE__ */ new Map();
|
|
430
|
+
logs = /* @__PURE__ */ new Map();
|
|
431
|
+
artifacts = /* @__PURE__ */ new Map();
|
|
432
|
+
outbox = [];
|
|
433
|
+
idempotencyKeys = /* @__PURE__ */ new Map();
|
|
434
|
+
idempotencyInProgress = /* @__PURE__ */ new Set();
|
|
435
|
+
outboxSequences = /* @__PURE__ */ new Map();
|
|
436
|
+
// Helper to generate composite keys for stages
|
|
437
|
+
stageKey(runId, stageId) {
|
|
438
|
+
return `${runId}:${stageId}`;
|
|
439
|
+
}
|
|
440
|
+
// Helper to generate composite keys for artifacts
|
|
441
|
+
artifactKey(runId, key) {
|
|
442
|
+
return `${runId}:${key}`;
|
|
443
|
+
}
|
|
444
|
+
idempotencyCompositeKey(commandType, key) {
|
|
445
|
+
return `${commandType}:${key}`;
|
|
446
|
+
}
|
|
447
|
+
async withTransaction(fn) {
|
|
448
|
+
return fn(this);
|
|
449
|
+
}
|
|
450
|
+
// ============================================================================
|
|
451
|
+
// WorkflowRun Operations
|
|
452
|
+
// ============================================================================
|
|
453
|
+
async createRun(data) {
|
|
454
|
+
const now = /* @__PURE__ */ new Date();
|
|
455
|
+
const record = {
|
|
456
|
+
id: data.id ?? randomUUID(),
|
|
457
|
+
createdAt: now,
|
|
458
|
+
updatedAt: now,
|
|
459
|
+
version: 1,
|
|
460
|
+
workflowId: data.workflowId,
|
|
461
|
+
workflowName: data.workflowName,
|
|
462
|
+
workflowType: data.workflowType,
|
|
463
|
+
status: "PENDING",
|
|
464
|
+
startedAt: null,
|
|
465
|
+
completedAt: null,
|
|
466
|
+
duration: null,
|
|
467
|
+
input: data.input,
|
|
468
|
+
output: null,
|
|
469
|
+
config: data.config ?? {},
|
|
470
|
+
totalCost: 0,
|
|
471
|
+
totalTokens: 0,
|
|
472
|
+
priority: data.priority ?? 5
|
|
473
|
+
};
|
|
474
|
+
this.runs.set(record.id, record);
|
|
475
|
+
return { ...record };
|
|
476
|
+
}
|
|
477
|
+
async updateRun(id, data) {
|
|
478
|
+
const run = this.runs.get(id);
|
|
479
|
+
if (!run) {
|
|
480
|
+
throw new Error(`WorkflowRun not found: ${id}`);
|
|
481
|
+
}
|
|
482
|
+
if (data.expectedVersion !== void 0 && run.version !== data.expectedVersion) {
|
|
483
|
+
throw new StaleVersionError(
|
|
484
|
+
"WorkflowRun",
|
|
485
|
+
id,
|
|
486
|
+
data.expectedVersion,
|
|
487
|
+
run.version
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
const { expectedVersion: _, ...rest } = data;
|
|
491
|
+
const updated = {
|
|
492
|
+
...run,
|
|
493
|
+
...rest,
|
|
494
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
495
|
+
version: run.version + 1
|
|
496
|
+
};
|
|
497
|
+
this.runs.set(id, updated);
|
|
498
|
+
}
|
|
499
|
+
async getRun(id) {
|
|
500
|
+
const run = this.runs.get(id);
|
|
501
|
+
return run ? { ...run } : null;
|
|
502
|
+
}
|
|
503
|
+
async getRunStatus(id) {
|
|
504
|
+
const run = this.runs.get(id);
|
|
505
|
+
return run?.status ?? null;
|
|
506
|
+
}
|
|
507
|
+
async getRunsByStatus(status) {
|
|
508
|
+
return Array.from(this.runs.values()).filter((run) => run.status === status).map((run) => ({ ...run }));
|
|
509
|
+
}
|
|
510
|
+
async claimPendingRun(id) {
|
|
511
|
+
const run = this.runs.get(id);
|
|
512
|
+
if (!run || run.status !== "PENDING") {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
const updated = {
|
|
516
|
+
...run,
|
|
517
|
+
status: "RUNNING",
|
|
518
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
519
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
520
|
+
version: run.version + 1
|
|
521
|
+
};
|
|
522
|
+
this.runs.set(id, updated);
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
async claimNextPendingRun() {
|
|
526
|
+
const pendingRuns = Array.from(this.runs.values()).filter((run) => run.status === "PENDING").sort((a, b) => {
|
|
527
|
+
if (a.priority !== b.priority) {
|
|
528
|
+
return b.priority - a.priority;
|
|
529
|
+
}
|
|
530
|
+
return a.createdAt.getTime() - b.createdAt.getTime();
|
|
531
|
+
});
|
|
532
|
+
if (pendingRuns.length === 0) {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
const runToClaim = pendingRuns[0];
|
|
536
|
+
const currentRun = this.runs.get(runToClaim.id);
|
|
537
|
+
if (!currentRun || currentRun.status !== "PENDING") {
|
|
538
|
+
return this.claimNextPendingRun();
|
|
539
|
+
}
|
|
540
|
+
const claimed = {
|
|
541
|
+
...currentRun,
|
|
542
|
+
status: "RUNNING",
|
|
543
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
544
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
545
|
+
version: currentRun.version + 1
|
|
546
|
+
};
|
|
547
|
+
this.runs.set(claimed.id, claimed);
|
|
548
|
+
return { ...claimed };
|
|
549
|
+
}
|
|
550
|
+
// ============================================================================
|
|
551
|
+
// WorkflowStage Operations
|
|
552
|
+
// ============================================================================
|
|
553
|
+
async createStage(data) {
|
|
554
|
+
const now = /* @__PURE__ */ new Date();
|
|
555
|
+
const id = randomUUID();
|
|
556
|
+
const record = {
|
|
557
|
+
id,
|
|
558
|
+
createdAt: now,
|
|
559
|
+
updatedAt: now,
|
|
560
|
+
version: 1,
|
|
561
|
+
workflowRunId: data.workflowRunId,
|
|
562
|
+
stageId: data.stageId,
|
|
563
|
+
stageName: data.stageName,
|
|
564
|
+
stageNumber: data.stageNumber,
|
|
565
|
+
executionGroup: data.executionGroup,
|
|
566
|
+
status: data.status ?? "PENDING",
|
|
567
|
+
startedAt: data.startedAt ?? null,
|
|
568
|
+
completedAt: null,
|
|
569
|
+
duration: null,
|
|
570
|
+
inputData: data.inputData ?? null,
|
|
571
|
+
outputData: null,
|
|
572
|
+
config: data.config ?? null,
|
|
573
|
+
suspendedState: null,
|
|
574
|
+
resumeData: null,
|
|
575
|
+
nextPollAt: null,
|
|
576
|
+
pollInterval: null,
|
|
577
|
+
maxWaitUntil: null,
|
|
578
|
+
metrics: null,
|
|
579
|
+
embeddingInfo: null,
|
|
580
|
+
errorMessage: null
|
|
581
|
+
};
|
|
582
|
+
this.stages.set(id, record);
|
|
583
|
+
this.stages.set(this.stageKey(data.workflowRunId, data.stageId), record);
|
|
584
|
+
return { ...record };
|
|
585
|
+
}
|
|
586
|
+
async upsertStage(data) {
|
|
587
|
+
const key = this.stageKey(data.workflowRunId, data.stageId);
|
|
588
|
+
const existing = this.stages.get(key);
|
|
589
|
+
if (existing) {
|
|
590
|
+
const updated = {
|
|
591
|
+
...existing,
|
|
592
|
+
...data.update,
|
|
593
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
594
|
+
version: existing.version + 1
|
|
595
|
+
};
|
|
596
|
+
this.stages.set(existing.id, updated);
|
|
597
|
+
this.stages.set(key, updated);
|
|
598
|
+
return { ...updated };
|
|
599
|
+
} else {
|
|
600
|
+
return this.createStage(data.create);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
async updateStage(id, data) {
|
|
604
|
+
const stage = this.stages.get(id);
|
|
605
|
+
if (!stage) {
|
|
606
|
+
throw new Error(`WorkflowStage not found: ${id}`);
|
|
607
|
+
}
|
|
608
|
+
if (data.expectedVersion !== void 0 && stage.version !== data.expectedVersion) {
|
|
609
|
+
throw new StaleVersionError(
|
|
610
|
+
"WorkflowStage",
|
|
611
|
+
id,
|
|
612
|
+
data.expectedVersion,
|
|
613
|
+
stage.version
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
const { expectedVersion: _, ...rest } = data;
|
|
617
|
+
const updated = {
|
|
618
|
+
...stage,
|
|
619
|
+
...rest,
|
|
620
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
621
|
+
version: stage.version + 1
|
|
622
|
+
};
|
|
623
|
+
this.stages.set(id, updated);
|
|
624
|
+
this.stages.set(this.stageKey(stage.workflowRunId, stage.stageId), updated);
|
|
625
|
+
}
|
|
626
|
+
async updateStageByRunAndStageId(workflowRunId, stageId, data) {
|
|
627
|
+
const key = this.stageKey(workflowRunId, stageId);
|
|
628
|
+
const stage = this.stages.get(key);
|
|
629
|
+
if (!stage) {
|
|
630
|
+
throw new Error(`WorkflowStage not found: ${workflowRunId}/${stageId}`);
|
|
631
|
+
}
|
|
632
|
+
if (data.expectedVersion !== void 0 && stage.version !== data.expectedVersion) {
|
|
633
|
+
throw new StaleVersionError(
|
|
634
|
+
"WorkflowStage",
|
|
635
|
+
`${workflowRunId}/${stageId}`,
|
|
636
|
+
data.expectedVersion,
|
|
637
|
+
stage.version
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
const { expectedVersion: _, ...rest } = data;
|
|
641
|
+
const updated = {
|
|
642
|
+
...stage,
|
|
643
|
+
...rest,
|
|
644
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
645
|
+
version: stage.version + 1
|
|
646
|
+
};
|
|
647
|
+
this.stages.set(stage.id, updated);
|
|
648
|
+
this.stages.set(key, updated);
|
|
649
|
+
}
|
|
650
|
+
async getStage(runId, stageId) {
|
|
651
|
+
const key = this.stageKey(runId, stageId);
|
|
652
|
+
const stage = this.stages.get(key);
|
|
653
|
+
return stage ? { ...stage } : null;
|
|
654
|
+
}
|
|
655
|
+
async getStageById(id) {
|
|
656
|
+
const stage = this.stages.get(id);
|
|
657
|
+
return stage ? { ...stage } : null;
|
|
658
|
+
}
|
|
659
|
+
async getStagesByRun(runId, options) {
|
|
660
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
661
|
+
let stages = Array.from(this.stages.values()).filter((s) => {
|
|
662
|
+
if (s.workflowRunId !== runId) return false;
|
|
663
|
+
if (seenIds.has(s.id)) return false;
|
|
664
|
+
seenIds.add(s.id);
|
|
665
|
+
return true;
|
|
666
|
+
});
|
|
667
|
+
if (options?.status) {
|
|
668
|
+
stages = stages.filter((s) => s.status === options.status);
|
|
669
|
+
}
|
|
670
|
+
stages.sort((a, b) => {
|
|
671
|
+
const diff = a.stageNumber - b.stageNumber;
|
|
672
|
+
return options?.orderBy === "desc" ? -diff : diff;
|
|
673
|
+
});
|
|
674
|
+
return stages.map((s) => ({ ...s }));
|
|
675
|
+
}
|
|
676
|
+
async getSuspendedStages(beforeDate) {
|
|
677
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
678
|
+
return Array.from(this.stages.values()).filter((s) => {
|
|
679
|
+
if (seenIds.has(s.id)) return false;
|
|
680
|
+
seenIds.add(s.id);
|
|
681
|
+
return s.status === "SUSPENDED" && s.nextPollAt !== null && s.nextPollAt <= beforeDate;
|
|
682
|
+
}).map((s) => ({ ...s }));
|
|
683
|
+
}
|
|
684
|
+
async getFirstSuspendedStageReadyToResume(runId) {
|
|
685
|
+
const stages = await this.getStagesByRun(runId, { status: "SUSPENDED" });
|
|
686
|
+
const now = /* @__PURE__ */ new Date();
|
|
687
|
+
const ready = stages.find((s) => s.nextPollAt && s.nextPollAt <= now);
|
|
688
|
+
return ready ?? null;
|
|
689
|
+
}
|
|
690
|
+
async getFirstFailedStage(runId) {
|
|
691
|
+
const stages = await this.getStagesByRun(runId, { status: "FAILED" });
|
|
692
|
+
return stages[0] ?? null;
|
|
693
|
+
}
|
|
694
|
+
async getLastCompletedStage(runId) {
|
|
695
|
+
const stages = await this.getStagesByRun(runId, {
|
|
696
|
+
status: "COMPLETED",
|
|
697
|
+
orderBy: "desc"
|
|
698
|
+
});
|
|
699
|
+
return stages[0] ?? null;
|
|
700
|
+
}
|
|
701
|
+
async getLastCompletedStageBefore(runId, executionGroup) {
|
|
702
|
+
const stages = await this.getStagesByRun(runId, {
|
|
703
|
+
status: "COMPLETED",
|
|
704
|
+
orderBy: "desc"
|
|
705
|
+
});
|
|
706
|
+
const before = stages.filter((s) => s.executionGroup < executionGroup);
|
|
707
|
+
return before[0] ?? null;
|
|
708
|
+
}
|
|
709
|
+
async deleteStage(id) {
|
|
710
|
+
const stage = this.stages.get(id);
|
|
711
|
+
if (stage) {
|
|
712
|
+
this.stages.delete(id);
|
|
713
|
+
this.stages.delete(this.stageKey(stage.workflowRunId, stage.stageId));
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
// ============================================================================
|
|
717
|
+
// WorkflowLog Operations
|
|
718
|
+
// ============================================================================
|
|
719
|
+
async createLog(data) {
|
|
720
|
+
const record = {
|
|
721
|
+
id: randomUUID(),
|
|
722
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
723
|
+
workflowRunId: data.workflowRunId ?? null,
|
|
724
|
+
workflowStageId: data.workflowStageId ?? null,
|
|
725
|
+
level: data.level,
|
|
726
|
+
message: data.message,
|
|
727
|
+
metadata: data.metadata ?? null
|
|
728
|
+
};
|
|
729
|
+
this.logs.set(record.id, record);
|
|
730
|
+
}
|
|
731
|
+
// ============================================================================
|
|
732
|
+
// Outbox Operations
|
|
733
|
+
// ============================================================================
|
|
734
|
+
async appendOutboxEvents(events) {
|
|
735
|
+
for (const event of events) {
|
|
736
|
+
const currentSeq = this.outboxSequences.get(event.workflowRunId) ?? 0;
|
|
737
|
+
const nextSeq = currentSeq + 1;
|
|
738
|
+
this.outboxSequences.set(event.workflowRunId, nextSeq);
|
|
739
|
+
const record = {
|
|
740
|
+
id: randomUUID(),
|
|
741
|
+
workflowRunId: event.workflowRunId,
|
|
742
|
+
sequence: nextSeq,
|
|
743
|
+
eventType: event.eventType,
|
|
744
|
+
payload: event.payload,
|
|
745
|
+
causationId: event.causationId,
|
|
746
|
+
occurredAt: event.occurredAt,
|
|
747
|
+
publishedAt: null,
|
|
748
|
+
retryCount: 0,
|
|
749
|
+
dlqAt: null
|
|
750
|
+
};
|
|
751
|
+
this.outbox.push(record);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
async getUnpublishedOutboxEvents(limit) {
|
|
755
|
+
const effectiveLimit = limit ?? 100;
|
|
756
|
+
return this.outbox.filter((r) => r.publishedAt === null && r.dlqAt === null).sort((a, b) => {
|
|
757
|
+
const runCmp = a.workflowRunId.localeCompare(b.workflowRunId);
|
|
758
|
+
if (runCmp !== 0) return runCmp;
|
|
759
|
+
return a.sequence - b.sequence;
|
|
760
|
+
}).slice(0, effectiveLimit).map((r) => ({ ...r }));
|
|
761
|
+
}
|
|
762
|
+
async markOutboxEventsPublished(ids) {
|
|
763
|
+
const idSet = new Set(ids);
|
|
764
|
+
for (const record of this.outbox) {
|
|
765
|
+
if (idSet.has(record.id)) {
|
|
766
|
+
record.publishedAt = /* @__PURE__ */ new Date();
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
async incrementOutboxRetryCount(id) {
|
|
771
|
+
const record = this.outbox.find((r) => r.id === id);
|
|
772
|
+
if (!record) throw new Error(`Outbox event not found: ${id}`);
|
|
773
|
+
record.retryCount++;
|
|
774
|
+
return record.retryCount;
|
|
775
|
+
}
|
|
776
|
+
async moveOutboxEventToDLQ(id) {
|
|
777
|
+
const record = this.outbox.find((r) => r.id === id);
|
|
778
|
+
if (!record) throw new Error(`Outbox event not found: ${id}`);
|
|
779
|
+
record.dlqAt = /* @__PURE__ */ new Date();
|
|
780
|
+
}
|
|
781
|
+
async replayDLQEvents(maxEvents) {
|
|
782
|
+
const dlqEvents = this.outbox.filter((r) => r.dlqAt !== null).slice(0, maxEvents);
|
|
783
|
+
for (const record of dlqEvents) {
|
|
784
|
+
record.dlqAt = null;
|
|
785
|
+
record.retryCount = 0;
|
|
786
|
+
}
|
|
787
|
+
return dlqEvents.length;
|
|
788
|
+
}
|
|
789
|
+
// ============================================================================
|
|
790
|
+
// Idempotency Operations
|
|
791
|
+
// ============================================================================
|
|
792
|
+
async acquireIdempotencyKey(key, commandType) {
|
|
793
|
+
const compositeKey = this.idempotencyCompositeKey(commandType, key);
|
|
794
|
+
const record = this.idempotencyKeys.get(compositeKey);
|
|
795
|
+
if (record) {
|
|
796
|
+
return { status: "replay", result: record.result };
|
|
797
|
+
}
|
|
798
|
+
if (this.idempotencyInProgress.has(compositeKey)) {
|
|
799
|
+
return { status: "in_progress" };
|
|
800
|
+
}
|
|
801
|
+
this.idempotencyInProgress.add(compositeKey);
|
|
802
|
+
return { status: "acquired" };
|
|
803
|
+
}
|
|
804
|
+
async completeIdempotencyKey(key, commandType, result) {
|
|
805
|
+
const compositeKey = this.idempotencyCompositeKey(commandType, key);
|
|
806
|
+
this.idempotencyInProgress.delete(compositeKey);
|
|
807
|
+
this.idempotencyKeys.set(compositeKey, {
|
|
808
|
+
key,
|
|
809
|
+
commandType,
|
|
810
|
+
result,
|
|
811
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
async releaseIdempotencyKey(key, commandType) {
|
|
815
|
+
this.idempotencyInProgress.delete(
|
|
816
|
+
this.idempotencyCompositeKey(commandType, key)
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
// ============================================================================
|
|
820
|
+
// WorkflowArtifact Operations
|
|
821
|
+
// ============================================================================
|
|
822
|
+
async saveArtifact(data) {
|
|
823
|
+
const now = /* @__PURE__ */ new Date();
|
|
824
|
+
const key = this.artifactKey(data.workflowRunId, data.key);
|
|
825
|
+
const existing = this.artifacts.get(key);
|
|
826
|
+
const record = {
|
|
827
|
+
id: existing?.id ?? randomUUID(),
|
|
828
|
+
createdAt: existing?.createdAt ?? now,
|
|
829
|
+
updatedAt: now,
|
|
830
|
+
workflowRunId: data.workflowRunId,
|
|
831
|
+
workflowStageId: data.workflowStageId ?? null,
|
|
832
|
+
key: data.key,
|
|
833
|
+
type: data.type,
|
|
834
|
+
data: data.data,
|
|
835
|
+
size: data.size,
|
|
836
|
+
metadata: data.metadata ?? null
|
|
837
|
+
};
|
|
838
|
+
this.artifacts.set(key, record);
|
|
839
|
+
}
|
|
840
|
+
async loadArtifact(runId, key) {
|
|
841
|
+
const artifact = this.artifacts.get(this.artifactKey(runId, key));
|
|
842
|
+
if (!artifact) {
|
|
843
|
+
throw new Error(`Artifact not found: ${runId}/${key}`);
|
|
844
|
+
}
|
|
845
|
+
return artifact.data;
|
|
846
|
+
}
|
|
847
|
+
async hasArtifact(runId, key) {
|
|
848
|
+
return this.artifacts.has(this.artifactKey(runId, key));
|
|
849
|
+
}
|
|
850
|
+
async deleteArtifact(runId, key) {
|
|
851
|
+
this.artifacts.delete(this.artifactKey(runId, key));
|
|
852
|
+
}
|
|
853
|
+
async listArtifacts(runId) {
|
|
854
|
+
return Array.from(this.artifacts.values()).filter((a) => a.workflowRunId === runId).map((a) => ({ ...a }));
|
|
855
|
+
}
|
|
856
|
+
async getStageIdForArtifact(runId, stageId) {
|
|
857
|
+
const stage = await this.getStage(runId, stageId);
|
|
858
|
+
return stage?.id ?? null;
|
|
859
|
+
}
|
|
860
|
+
// ============================================================================
|
|
861
|
+
// Stage Output Convenience Method
|
|
862
|
+
// ============================================================================
|
|
863
|
+
async saveStageOutput(runId, workflowType, stageId, output) {
|
|
864
|
+
const key = `workflow-v2/${workflowType}/${runId}/${stageId}/output.json`;
|
|
865
|
+
const stageDbId = await this.getStageIdForArtifact(runId, stageId);
|
|
866
|
+
await this.saveArtifact({
|
|
867
|
+
workflowRunId: runId,
|
|
868
|
+
workflowStageId: stageDbId ?? void 0,
|
|
869
|
+
key,
|
|
870
|
+
type: "STAGE_OUTPUT",
|
|
871
|
+
data: output,
|
|
872
|
+
size: JSON.stringify(output).length
|
|
873
|
+
});
|
|
874
|
+
return key;
|
|
875
|
+
}
|
|
876
|
+
// ============================================================================
|
|
877
|
+
// Test Helpers
|
|
878
|
+
// ============================================================================
|
|
879
|
+
/**
|
|
880
|
+
* Clear all data - useful between tests
|
|
881
|
+
*/
|
|
882
|
+
clear() {
|
|
883
|
+
this.runs.clear();
|
|
884
|
+
this.stages.clear();
|
|
885
|
+
this.logs.clear();
|
|
886
|
+
this.artifacts.clear();
|
|
887
|
+
this.outbox = [];
|
|
888
|
+
this.idempotencyKeys.clear();
|
|
889
|
+
this.idempotencyInProgress.clear();
|
|
890
|
+
this.outboxSequences.clear();
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Get all runs for inspection
|
|
894
|
+
*/
|
|
895
|
+
getAllRuns() {
|
|
896
|
+
return Array.from(this.runs.values()).map((r) => ({ ...r }));
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Get all stages for inspection
|
|
900
|
+
*/
|
|
901
|
+
getAllStages() {
|
|
902
|
+
return Array.from(this.stages.values()).filter((s) => this.stages.get(s.id) === s).map((s) => ({ ...s }));
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Get all logs for inspection
|
|
906
|
+
*/
|
|
907
|
+
getAllLogs() {
|
|
908
|
+
return Array.from(this.logs.values()).map((l) => ({ ...l }));
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Get all artifacts for inspection
|
|
912
|
+
*/
|
|
913
|
+
getAllArtifacts() {
|
|
914
|
+
return Array.from(this.artifacts.values()).map((a) => ({ ...a }));
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
export { InMemoryAICallLogger, InMemoryJobQueue, InMemoryWorkflowPersistence };
|
|
919
|
+
//# sourceMappingURL=index.js.map
|
|
920
|
+
//# sourceMappingURL=index.js.map
|