@bratsos/workflow-engine 0.1.0 → 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-HL3OJG7W.js +1033 -0
- package/dist/chunk-HL3OJG7W.js.map +1 -0
- package/dist/{chunk-7IITBLFY.js → chunk-NYKMT46J.js} +268 -25
- package/dist/chunk-NYKMT46J.js.map +1 -0
- package/dist/chunk-SPXBCZLB.js +17 -0
- package/dist/chunk-SPXBCZLB.js.map +1 -0
- package/dist/{client-5vz5Vv4A.d.ts → client-D4PoxADF.d.ts} +3 -143
- package/dist/client.d.ts +3 -2
- package/dist/{index-DmR3E8D7.d.ts → index-DAzCfO1R.d.ts} +20 -1
- package/dist/index.d.ts +234 -601
- package/dist/index.js +46 -2034
- package/dist/index.js.map +1 -1
- package/dist/{interface-Cv22wvLG.d.ts → interface-MMqhfQQK.d.ts} +69 -2
- 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 -2
- package/dist/persistence/index.js +2 -1
- package/dist/persistence/prisma/index.d.ts +2 -2
- package/dist/persistence/prisma/index.js +2 -1
- 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 +23 -1
- package/dist/testing/index.js +156 -13
- package/dist/testing/index.js.map +1 -1
- 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
- package/dist/chunk-7IITBLFY.js.map +0 -1
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Core type definitions for Workflow System v2
|
|
5
|
+
*
|
|
6
|
+
* See WORKFLOW_SYSTEM_PROPOSAL.md for full architectural details
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface ProgressUpdate {
|
|
10
|
+
stageId: string;
|
|
11
|
+
stageName: string;
|
|
12
|
+
progress: number;
|
|
13
|
+
message: string;
|
|
14
|
+
details?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
interface StageMetrics {
|
|
17
|
+
startTime: number;
|
|
18
|
+
endTime: number;
|
|
19
|
+
duration: number;
|
|
20
|
+
itemsProcessed?: number;
|
|
21
|
+
itemsProduced?: number;
|
|
22
|
+
aiCalls?: number;
|
|
23
|
+
totalTokens?: number;
|
|
24
|
+
totalCost?: number;
|
|
25
|
+
}
|
|
26
|
+
interface EmbeddingResult {
|
|
27
|
+
id: string;
|
|
28
|
+
content: string;
|
|
29
|
+
embedding: number[];
|
|
30
|
+
similarity?: number;
|
|
31
|
+
metadata?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
interface EmbeddingInfo {
|
|
34
|
+
model: string;
|
|
35
|
+
dimensions: number;
|
|
36
|
+
results: EmbeddingResult[];
|
|
37
|
+
totalProcessed?: number;
|
|
38
|
+
averageSimilarity?: number;
|
|
39
|
+
}
|
|
40
|
+
interface StageResult<TOutput> {
|
|
41
|
+
output: TOutput;
|
|
42
|
+
metrics: StageMetrics;
|
|
43
|
+
artifacts?: Record<string, unknown>;
|
|
44
|
+
embeddings?: EmbeddingInfo;
|
|
45
|
+
}
|
|
46
|
+
declare const SuspendedStateSchema: z.ZodObject<{
|
|
47
|
+
batchId: z.ZodString;
|
|
48
|
+
statusUrl: z.ZodOptional<z.ZodString>;
|
|
49
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
50
|
+
submittedAt: z.ZodString;
|
|
51
|
+
pollInterval: z.ZodNumber;
|
|
52
|
+
maxWaitTime: z.ZodNumber;
|
|
53
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
54
|
+
}, z.core.$strip>;
|
|
55
|
+
interface SuspendedResult {
|
|
56
|
+
suspended: true;
|
|
57
|
+
state: z.infer<typeof SuspendedStateSchema>;
|
|
58
|
+
pollConfig: {
|
|
59
|
+
pollInterval: number;
|
|
60
|
+
maxWaitTime: number;
|
|
61
|
+
nextPollAt: Date;
|
|
62
|
+
};
|
|
63
|
+
metrics: StageMetrics;
|
|
64
|
+
}
|
|
65
|
+
interface CompletionCheckResult<TOutput> {
|
|
66
|
+
ready: boolean;
|
|
67
|
+
output?: TOutput;
|
|
68
|
+
error?: string;
|
|
69
|
+
nextCheckIn?: number;
|
|
70
|
+
metrics?: StageMetrics;
|
|
71
|
+
embeddings?: EmbeddingInfo;
|
|
72
|
+
}
|
|
73
|
+
type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR";
|
|
74
|
+
type StageMode = "sync" | "async-batch";
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Stage interface and context definitions
|
|
78
|
+
*
|
|
79
|
+
* Stages are the building blocks of workflows. Each stage:
|
|
80
|
+
* - Has strongly-typed input, output, and config schemas (Zod)
|
|
81
|
+
* - Can be sync or async-batch
|
|
82
|
+
* - Has access to AI helper, storage, and logging via context
|
|
83
|
+
* - Can suspend workflow for long-running batch operations
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
interface StageContext<TInput, TConfig, TWorkflowContext = Record<string, unknown>> {
|
|
87
|
+
workflowRunId: string;
|
|
88
|
+
stageId: string;
|
|
89
|
+
stageNumber: number;
|
|
90
|
+
stageName: string;
|
|
91
|
+
/** Database record ID for this stage execution (for logging to persistence) */
|
|
92
|
+
stageRecordId?: string;
|
|
93
|
+
input: TInput;
|
|
94
|
+
config: TConfig;
|
|
95
|
+
resumeState?: z.infer<typeof SuspendedStateSchema>;
|
|
96
|
+
onProgress: (update: ProgressUpdate) => void;
|
|
97
|
+
onLog: (level: LogLevel, message: string, meta?: Record<string, unknown>) => void;
|
|
98
|
+
log: (level: LogLevel, message: string, meta?: Record<string, unknown>) => void;
|
|
99
|
+
storage: StageStorage;
|
|
100
|
+
workflowContext: Partial<TWorkflowContext>;
|
|
101
|
+
}
|
|
102
|
+
interface StageStorage {
|
|
103
|
+
save<T>(key: string, data: T): Promise<void>;
|
|
104
|
+
load<T>(key: string): Promise<T>;
|
|
105
|
+
exists(key: string): Promise<boolean>;
|
|
106
|
+
delete(key: string): Promise<void>;
|
|
107
|
+
getStageKey(stageId: string, suffix?: string): string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Context passed to checkCompletion for async-batch stages.
|
|
111
|
+
* Includes identification info so stages don't need to store it in metadata.
|
|
112
|
+
*/
|
|
113
|
+
interface CheckCompletionContext<TConfig> {
|
|
114
|
+
workflowRunId: string;
|
|
115
|
+
stageId: string;
|
|
116
|
+
/** Database record ID for this stage execution (for logging to persistence) */
|
|
117
|
+
stageRecordId?: string;
|
|
118
|
+
config: TConfig;
|
|
119
|
+
onLog: (level: LogLevel, message: string, meta?: Record<string, unknown>) => void;
|
|
120
|
+
log: (level: LogLevel, message: string, meta?: Record<string, unknown>) => void;
|
|
121
|
+
storage: StageStorage;
|
|
122
|
+
}
|
|
123
|
+
interface Stage<TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny, TConfig extends z.ZodTypeAny, TWorkflowContext = Record<string, unknown>> {
|
|
124
|
+
id: string;
|
|
125
|
+
name: string;
|
|
126
|
+
description?: string;
|
|
127
|
+
/**
|
|
128
|
+
* Optional: List of stage IDs that this stage depends on.
|
|
129
|
+
* The workflow builder will validate that all dependencies are present
|
|
130
|
+
* in the workflow before this stage is executed.
|
|
131
|
+
*
|
|
132
|
+
* Example: dependencies: ["data-extraction", "guidelines"]
|
|
133
|
+
*/
|
|
134
|
+
dependencies?: string[];
|
|
135
|
+
inputSchema: TInput;
|
|
136
|
+
outputSchema: TOutput;
|
|
137
|
+
configSchema: TConfig;
|
|
138
|
+
execute: (context: StageContext<z.infer<TInput>, z.infer<TConfig>, TWorkflowContext>) => Promise<StageResult<z.infer<TOutput>> | SuspendedResult>;
|
|
139
|
+
checkCompletion?: (suspendedState: z.infer<typeof SuspendedStateSchema>, context: CheckCompletionContext<z.infer<TConfig>>) => Promise<CompletionCheckResult<z.infer<TOutput>>>;
|
|
140
|
+
mode?: StageMode;
|
|
141
|
+
estimateCost?: (input: z.infer<TInput>, config: z.infer<TConfig>) => number;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export { type CheckCompletionContext as C, type LogLevel as L, type Stage as S, type StageResult as a, type StageContext as b, SuspendedStateSchema as c, type CompletionCheckResult as d };
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as AICallLogger,
|
|
1
|
+
import { A as AICallLogger, g as CreateAICallInput, h as AIHelperStats, i as AICallRecord, J as JobQueue, E as EnqueueJobInput, D as DequeueResult, j as JobRecord, k as JobStatus, l as WorkflowPersistence, a as CreateRunInput, W as WorkflowRunRecord, U as UpdateRunInput, m as WorkflowStatus, b as CreateStageInput, c as WorkflowStageRecord, d as UpsertStageInput, e as UpdateStageInput, n as WorkflowStageStatus, f as CreateLogInput, C as CreateOutboxEventInput, O as OutboxRecord, o as SaveArtifactInput, p as WorkflowArtifactRecord, q as WorkflowLogRecord } from '../interface-MMqhfQQK.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* In-Memory AI Call Logger
|
|
@@ -184,8 +184,14 @@ declare class InMemoryWorkflowPersistence implements WorkflowPersistence {
|
|
|
184
184
|
private stages;
|
|
185
185
|
private logs;
|
|
186
186
|
private artifacts;
|
|
187
|
+
private outbox;
|
|
188
|
+
private idempotencyKeys;
|
|
189
|
+
private idempotencyInProgress;
|
|
190
|
+
private outboxSequences;
|
|
187
191
|
private stageKey;
|
|
188
192
|
private artifactKey;
|
|
193
|
+
private idempotencyCompositeKey;
|
|
194
|
+
withTransaction<T>(fn: (tx: WorkflowPersistence) => Promise<T>): Promise<T>;
|
|
189
195
|
createRun(data: CreateRunInput): Promise<WorkflowRunRecord>;
|
|
190
196
|
updateRun(id: string, data: UpdateRunInput): Promise<void>;
|
|
191
197
|
getRun(id: string): Promise<WorkflowRunRecord | null>;
|
|
@@ -210,6 +216,22 @@ declare class InMemoryWorkflowPersistence implements WorkflowPersistence {
|
|
|
210
216
|
getLastCompletedStageBefore(runId: string, executionGroup: number): Promise<WorkflowStageRecord | null>;
|
|
211
217
|
deleteStage(id: string): Promise<void>;
|
|
212
218
|
createLog(data: CreateLogInput): Promise<void>;
|
|
219
|
+
appendOutboxEvents(events: CreateOutboxEventInput[]): Promise<void>;
|
|
220
|
+
getUnpublishedOutboxEvents(limit?: number): Promise<OutboxRecord[]>;
|
|
221
|
+
markOutboxEventsPublished(ids: string[]): Promise<void>;
|
|
222
|
+
incrementOutboxRetryCount(id: string): Promise<number>;
|
|
223
|
+
moveOutboxEventToDLQ(id: string): Promise<void>;
|
|
224
|
+
replayDLQEvents(maxEvents: number): Promise<number>;
|
|
225
|
+
acquireIdempotencyKey(key: string, commandType: string): Promise<{
|
|
226
|
+
status: "acquired";
|
|
227
|
+
} | {
|
|
228
|
+
status: "replay";
|
|
229
|
+
result: unknown;
|
|
230
|
+
} | {
|
|
231
|
+
status: "in_progress";
|
|
232
|
+
}>;
|
|
233
|
+
completeIdempotencyKey(key: string, commandType: string, result: unknown): Promise<void>;
|
|
234
|
+
releaseIdempotencyKey(key: string, commandType: string): Promise<void>;
|
|
213
235
|
saveArtifact(data: SaveArtifactInput): Promise<void>;
|
|
214
236
|
loadArtifact(runId: string, key: string): Promise<unknown>;
|
|
215
237
|
hasArtifact(runId: string, key: string): Promise<boolean>;
|
package/dist/testing/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { StaleVersionError } from '../chunk-SPXBCZLB.js';
|
|
1
2
|
import { randomUUID } from 'crypto';
|
|
2
3
|
|
|
3
|
-
// src/testing/in-memory-ai-logger.ts
|
|
4
4
|
var InMemoryAICallLogger = class {
|
|
5
5
|
calls = /* @__PURE__ */ new Map();
|
|
6
6
|
recordedBatches = /* @__PURE__ */ new Set();
|
|
@@ -190,6 +190,7 @@ var InMemoryJobQueue = class {
|
|
|
190
190
|
createdAt: now,
|
|
191
191
|
updatedAt: now,
|
|
192
192
|
workflowRunId: options.workflowRunId,
|
|
193
|
+
workflowId: options.workflowId,
|
|
193
194
|
stageId: options.stageId,
|
|
194
195
|
status: "PENDING",
|
|
195
196
|
priority: options.priority ?? 5,
|
|
@@ -240,9 +241,11 @@ var InMemoryJobQueue = class {
|
|
|
240
241
|
return {
|
|
241
242
|
jobId: job.id,
|
|
242
243
|
workflowRunId: job.workflowRunId,
|
|
244
|
+
workflowId: job.workflowId,
|
|
243
245
|
stageId: job.stageId,
|
|
244
246
|
priority: job.priority,
|
|
245
247
|
attempt: job.attempt,
|
|
248
|
+
maxAttempts: job.maxAttempts,
|
|
246
249
|
payload: job.payload
|
|
247
250
|
};
|
|
248
251
|
}
|
|
@@ -426,6 +429,10 @@ var InMemoryWorkflowPersistence = class {
|
|
|
426
429
|
stages = /* @__PURE__ */ new Map();
|
|
427
430
|
logs = /* @__PURE__ */ new Map();
|
|
428
431
|
artifacts = /* @__PURE__ */ new Map();
|
|
432
|
+
outbox = [];
|
|
433
|
+
idempotencyKeys = /* @__PURE__ */ new Map();
|
|
434
|
+
idempotencyInProgress = /* @__PURE__ */ new Set();
|
|
435
|
+
outboxSequences = /* @__PURE__ */ new Map();
|
|
429
436
|
// Helper to generate composite keys for stages
|
|
430
437
|
stageKey(runId, stageId) {
|
|
431
438
|
return `${runId}:${stageId}`;
|
|
@@ -434,6 +441,12 @@ var InMemoryWorkflowPersistence = class {
|
|
|
434
441
|
artifactKey(runId, key) {
|
|
435
442
|
return `${runId}:${key}`;
|
|
436
443
|
}
|
|
444
|
+
idempotencyCompositeKey(commandType, key) {
|
|
445
|
+
return `${commandType}:${key}`;
|
|
446
|
+
}
|
|
447
|
+
async withTransaction(fn) {
|
|
448
|
+
return fn(this);
|
|
449
|
+
}
|
|
437
450
|
// ============================================================================
|
|
438
451
|
// WorkflowRun Operations
|
|
439
452
|
// ============================================================================
|
|
@@ -443,6 +456,7 @@ var InMemoryWorkflowPersistence = class {
|
|
|
443
456
|
id: data.id ?? randomUUID(),
|
|
444
457
|
createdAt: now,
|
|
445
458
|
updatedAt: now,
|
|
459
|
+
version: 1,
|
|
446
460
|
workflowId: data.workflowId,
|
|
447
461
|
workflowName: data.workflowName,
|
|
448
462
|
workflowType: data.workflowType,
|
|
@@ -465,10 +479,20 @@ var InMemoryWorkflowPersistence = class {
|
|
|
465
479
|
if (!run) {
|
|
466
480
|
throw new Error(`WorkflowRun not found: ${id}`);
|
|
467
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;
|
|
468
491
|
const updated = {
|
|
469
492
|
...run,
|
|
470
|
-
...
|
|
471
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
493
|
+
...rest,
|
|
494
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
495
|
+
version: run.version + 1
|
|
472
496
|
};
|
|
473
497
|
this.runs.set(id, updated);
|
|
474
498
|
}
|
|
@@ -492,7 +516,8 @@ var InMemoryWorkflowPersistence = class {
|
|
|
492
516
|
...run,
|
|
493
517
|
status: "RUNNING",
|
|
494
518
|
startedAt: /* @__PURE__ */ new Date(),
|
|
495
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
519
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
520
|
+
version: run.version + 1
|
|
496
521
|
};
|
|
497
522
|
this.runs.set(id, updated);
|
|
498
523
|
return true;
|
|
@@ -516,7 +541,8 @@ var InMemoryWorkflowPersistence = class {
|
|
|
516
541
|
...currentRun,
|
|
517
542
|
status: "RUNNING",
|
|
518
543
|
startedAt: /* @__PURE__ */ new Date(),
|
|
519
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
544
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
545
|
+
version: currentRun.version + 1
|
|
520
546
|
};
|
|
521
547
|
this.runs.set(claimed.id, claimed);
|
|
522
548
|
return { ...claimed };
|
|
@@ -531,6 +557,7 @@ var InMemoryWorkflowPersistence = class {
|
|
|
531
557
|
id,
|
|
532
558
|
createdAt: now,
|
|
533
559
|
updatedAt: now,
|
|
560
|
+
version: 1,
|
|
534
561
|
workflowRunId: data.workflowRunId,
|
|
535
562
|
stageId: data.stageId,
|
|
536
563
|
stageName: data.stageName,
|
|
@@ -563,7 +590,8 @@ var InMemoryWorkflowPersistence = class {
|
|
|
563
590
|
const updated = {
|
|
564
591
|
...existing,
|
|
565
592
|
...data.update,
|
|
566
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
593
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
594
|
+
version: existing.version + 1
|
|
567
595
|
};
|
|
568
596
|
this.stages.set(existing.id, updated);
|
|
569
597
|
this.stages.set(key, updated);
|
|
@@ -577,10 +605,20 @@ var InMemoryWorkflowPersistence = class {
|
|
|
577
605
|
if (!stage) {
|
|
578
606
|
throw new Error(`WorkflowStage not found: ${id}`);
|
|
579
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;
|
|
580
617
|
const updated = {
|
|
581
618
|
...stage,
|
|
582
|
-
...
|
|
583
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
619
|
+
...rest,
|
|
620
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
621
|
+
version: stage.version + 1
|
|
584
622
|
};
|
|
585
623
|
this.stages.set(id, updated);
|
|
586
624
|
this.stages.set(this.stageKey(stage.workflowRunId, stage.stageId), updated);
|
|
@@ -591,10 +629,20 @@ var InMemoryWorkflowPersistence = class {
|
|
|
591
629
|
if (!stage) {
|
|
592
630
|
throw new Error(`WorkflowStage not found: ${workflowRunId}/${stageId}`);
|
|
593
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;
|
|
594
641
|
const updated = {
|
|
595
642
|
...stage,
|
|
596
|
-
...
|
|
597
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
643
|
+
...rest,
|
|
644
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
645
|
+
version: stage.version + 1
|
|
598
646
|
};
|
|
599
647
|
this.stages.set(stage.id, updated);
|
|
600
648
|
this.stages.set(key, updated);
|
|
@@ -626,9 +674,12 @@ var InMemoryWorkflowPersistence = class {
|
|
|
626
674
|
return stages.map((s) => ({ ...s }));
|
|
627
675
|
}
|
|
628
676
|
async getSuspendedStages(beforeDate) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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 }));
|
|
632
683
|
}
|
|
633
684
|
async getFirstSuspendedStageReadyToResume(runId) {
|
|
634
685
|
const stages = await this.getStagesByRun(runId, { status: "SUSPENDED" });
|
|
@@ -678,6 +729,94 @@ var InMemoryWorkflowPersistence = class {
|
|
|
678
729
|
this.logs.set(record.id, record);
|
|
679
730
|
}
|
|
680
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
|
+
// ============================================================================
|
|
681
820
|
// WorkflowArtifact Operations
|
|
682
821
|
// ============================================================================
|
|
683
822
|
async saveArtifact(data) {
|
|
@@ -745,6 +884,10 @@ var InMemoryWorkflowPersistence = class {
|
|
|
745
884
|
this.stages.clear();
|
|
746
885
|
this.logs.clear();
|
|
747
886
|
this.artifacts.clear();
|
|
887
|
+
this.outbox = [];
|
|
888
|
+
this.idempotencyKeys.clear();
|
|
889
|
+
this.idempotencyInProgress.clear();
|
|
890
|
+
this.outboxSequences.clear();
|
|
748
891
|
}
|
|
749
892
|
/**
|
|
750
893
|
* Get all runs for inspection
|