@bratsos/workflow-engine 0.5.1 → 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.
- package/README.md +65 -12
- package/dist/{chunk-RZY5YRGL.js → chunk-2HEV5ZJL.js} +2 -2
- package/dist/chunk-2HEV5ZJL.js.map +1 -0
- package/dist/{chunk-WQPZ6KON.js → chunk-5C7LRNM7.js} +280 -93
- package/dist/chunk-5C7LRNM7.js.map +1 -0
- package/dist/{chunk-PHLNTR5Z.js → chunk-Q2XDO3UF.js} +28 -7
- package/dist/chunk-Q2XDO3UF.js.map +1 -0
- package/dist/{chunk-ZYMT2PAO.js → chunk-WWK2SPN7.js} +16 -37
- package/dist/chunk-WWK2SPN7.js.map +1 -0
- package/dist/{client-oLD5ilXp.d.ts → client-DYs5wlHp.d.ts} +17 -99
- package/dist/client.d.ts +4 -3
- package/dist/client.js +1 -1
- package/dist/events-D_P24UaY.d.ts +105 -0
- package/dist/{index-CVkkGnxx.d.ts → index-aNuJ2QgN.d.ts} +11 -1
- package/dist/index.d.ts +184 -32
- package/dist/index.js +41 -9
- package/dist/index.js.map +1 -1
- package/dist/{interface-TsryH4d7.d.ts → interface-BeEPzTFy.d.ts} +9 -3
- package/dist/kernel/index.d.ts +6 -5
- package/dist/kernel/index.js +2 -1
- package/dist/kernel/testing/index.d.ts +3 -2
- package/dist/persistence/index.d.ts +2 -2
- package/dist/persistence/index.js +2 -2
- package/dist/persistence/prisma/index.d.ts +2 -2
- package/dist/persistence/prisma/index.js +2 -2
- package/dist/{plugins-C94AT8Wr.d.ts → plugins-Cl0WVVrE.d.ts} +9 -6
- package/dist/{ports-855bktyD.d.ts → ports-swhiWFw4.d.ts} +5 -106
- package/dist/{stage-BPw7m9Wx.d.ts → stage-_7BKqqUG.d.ts} +2 -2
- package/dist/testing/index.d.ts +2 -1
- package/dist/testing/index.js +25 -6
- package/dist/testing/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/workflow-engine/SKILL.md +30 -11
- package/skills/workflow-engine/references/02-workflow-builder.md +2 -0
- package/skills/workflow-engine/references/03-runtime-setup.md +1 -1
- package/skills/workflow-engine/references/08-common-patterns.md +2 -1
- package/skills/workflow-engine/references/09-troubleshooting.md +4 -3
- package/dist/chunk-PHLNTR5Z.js.map +0 -1
- package/dist/chunk-RZY5YRGL.js.map +0 -1
- package/dist/chunk-WQPZ6KON.js.map +0 -1
- package/dist/chunk-ZYMT2PAO.js.map +0 -1
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ A **type-safe, distributed workflow engine** for AI-orchestrated processes. Feat
|
|
|
24
24
|
- [Common Patterns](#common-patterns)
|
|
25
25
|
- [Accessing Previous Stage Output](#accessing-previous-stage-output)
|
|
26
26
|
- [Parallel Execution](#parallel-execution)
|
|
27
|
+
- [Stage ID Utilities](#stage-id-utilities)
|
|
27
28
|
- [AI Integration](#ai-integration)
|
|
28
29
|
- [Long-Running Batch Jobs](#long-running-batch-jobs)
|
|
29
30
|
- [Config Presets](#config-presets)
|
|
@@ -110,6 +111,7 @@ model WorkflowRun {
|
|
|
110
111
|
id String @id @default(cuid())
|
|
111
112
|
createdAt DateTime @default(now())
|
|
112
113
|
updatedAt DateTime @updatedAt
|
|
114
|
+
version Int @default(1)
|
|
113
115
|
workflowId String
|
|
114
116
|
workflowName String
|
|
115
117
|
workflowType String
|
|
@@ -137,6 +139,7 @@ model WorkflowStage {
|
|
|
137
139
|
id String @id @default(cuid())
|
|
138
140
|
createdAt DateTime @default(now())
|
|
139
141
|
updatedAt DateTime @updatedAt
|
|
142
|
+
version Int @default(1)
|
|
140
143
|
workflowRunId String
|
|
141
144
|
workflowRun WorkflowRun @relation(fields: [workflowRunId], references: [id], onDelete: Cascade)
|
|
142
145
|
stageId String
|
|
@@ -219,7 +222,7 @@ model JobQueue {
|
|
|
219
222
|
stageId String
|
|
220
223
|
status Status @default(PENDING)
|
|
221
224
|
priority Int @default(5)
|
|
222
|
-
attempt Int @default(
|
|
225
|
+
attempt Int @default(0)
|
|
223
226
|
maxAttempts Int @default(3)
|
|
224
227
|
workerId String?
|
|
225
228
|
lockedAt DateTime?
|
|
@@ -404,17 +407,27 @@ A stage is the atomic unit of work. Every stage has typed input, output, and con
|
|
|
404
407
|
|
|
405
408
|
### Workflows
|
|
406
409
|
|
|
407
|
-
|
|
410
|
+
Workflows are built as a linear pipeline of **execution groups**. Each group contains one or more stages. Sequential stages (`.pipe()`) form single-stage groups. Parallel stages (`.parallel()`) form multi-stage groups where all stages run concurrently.
|
|
408
411
|
|
|
409
412
|
```typescript
|
|
410
413
|
new WorkflowBuilder(id, name, description, inputSchema, outputSchema)
|
|
411
|
-
.pipe(stageA) //
|
|
412
|
-
.pipe(stageB) //
|
|
413
|
-
.parallel([stageC, stageD]) //
|
|
414
|
-
.pipe(stageE) //
|
|
414
|
+
.pipe(stageA) // Group 0: stageA runs first
|
|
415
|
+
.pipe(stageB) // Group 1: stageB runs after stageA
|
|
416
|
+
.parallel([stageC, stageD]) // Group 2: stageC and stageD run concurrently
|
|
417
|
+
.pipe(stageE) // Group 3: stageE runs after both complete
|
|
415
418
|
.build();
|
|
416
419
|
```
|
|
417
420
|
|
|
421
|
+
The output of each execution group is stored in the workflow context keyed by stage ID. For parallel groups, the merged output is an object keyed by each stage's ID:
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
// After group 2 completes, stageE receives:
|
|
425
|
+
ctx.require("stageC") // output of stageC
|
|
426
|
+
ctx.require("stageD") // output of stageD
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
When a workflow completes, the final execution group's output is persisted in `WorkflowRun.output` and included in the `workflow:completed` event.
|
|
430
|
+
|
|
418
431
|
### Kernel
|
|
419
432
|
|
|
420
433
|
The `Kernel` is a pure command dispatcher. All operations are expressed as typed commands:
|
|
@@ -504,16 +517,42 @@ export const analyzeStage = defineStage({
|
|
|
504
517
|
|
|
505
518
|
### Parallel Execution
|
|
506
519
|
|
|
520
|
+
Parallel stages run concurrently in the same execution group. Their outputs are keyed by stage ID in the workflow context:
|
|
521
|
+
|
|
507
522
|
```typescript
|
|
508
523
|
const workflow = new WorkflowBuilder(/* ... */)
|
|
509
524
|
.pipe(extractStage)
|
|
510
525
|
.parallel([
|
|
511
|
-
sentimentAnalysisStage,
|
|
512
|
-
keywordExtractionStage,
|
|
513
|
-
languageDetectionStage,
|
|
526
|
+
sentimentAnalysisStage, // id: "sentiment"
|
|
527
|
+
keywordExtractionStage, // id: "keywords"
|
|
528
|
+
languageDetectionStage, // id: "language"
|
|
514
529
|
])
|
|
515
530
|
.pipe(aggregateResultsStage)
|
|
516
531
|
.build();
|
|
532
|
+
|
|
533
|
+
// In aggregateResultsStage:
|
|
534
|
+
async execute(ctx) {
|
|
535
|
+
const sentiment = ctx.require("sentiment"); // output of sentimentAnalysisStage
|
|
536
|
+
const keywords = ctx.require("keywords"); // output of keywordExtractionStage
|
|
537
|
+
const language = ctx.require("language"); // output of languageDetectionStage
|
|
538
|
+
// ...
|
|
539
|
+
}
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
### Stage ID Utilities
|
|
543
|
+
|
|
544
|
+
Use `createStageIds` or `defineStageIds` for type-safe stage ID constants with autocomplete:
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
import { createStageIds, defineStageIds } from "@bratsos/workflow-engine";
|
|
548
|
+
|
|
549
|
+
// From an existing workflow
|
|
550
|
+
const STAGES = createStageIds(myWorkflow);
|
|
551
|
+
STAGES.EXTRACT_TEXT // "extract-text" (autocomplete + type-safe)
|
|
552
|
+
STAGES.SUMMARIZE // "summarize"
|
|
553
|
+
|
|
554
|
+
// Or define upfront
|
|
555
|
+
const STAGES = defineStageIds(["extract-text", "summarize"] as const);
|
|
517
556
|
```
|
|
518
557
|
|
|
519
558
|
### AI Integration
|
|
@@ -558,7 +597,12 @@ export const batchStage = defineAsyncBatchStage({
|
|
|
558
597
|
const batch = await submitBatch(ctx.input.prompts);
|
|
559
598
|
return {
|
|
560
599
|
suspended: true,
|
|
561
|
-
state: {
|
|
600
|
+
state: {
|
|
601
|
+
batchId: batch.id,
|
|
602
|
+
submittedAt: new Date().toISOString(),
|
|
603
|
+
pollInterval: 3600000,
|
|
604
|
+
maxWaitTime: 86400000,
|
|
605
|
+
},
|
|
562
606
|
pollConfig: { pollInterval: 3600000, maxWaitTime: 86400000, nextPollAt: new Date(Date.now() + 3600000) },
|
|
563
607
|
};
|
|
564
608
|
},
|
|
@@ -640,11 +684,12 @@ async execute(ctx) {
|
|
|
640
684
|
| `run.create` | Create a new workflow run | `idempotencyKey`, `workflowId`, `input`, `config?`, `priority?` |
|
|
641
685
|
| `run.claimPending` | Claim pending runs for processing | `workerId`, `maxClaims?` |
|
|
642
686
|
| `run.transition` | Advance to next stage group | `workflowRunId` |
|
|
643
|
-
| `run.cancel` | Cancel a running workflow | `workflowRunId`, `reason?` |
|
|
644
|
-
| `run.rerunFrom` | Rerun from a specific stage | `workflowRunId`, `fromStageId` |
|
|
687
|
+
| `run.cancel` | Cancel a running workflow (cascades to stages + jobs) | `workflowRunId`, `reason?` |
|
|
688
|
+
| `run.rerunFrom` | Rerun from a specific stage (cleans up artifacts) | `workflowRunId`, `fromStageId` |
|
|
645
689
|
| `job.execute` | Execute a single stage (multi-phase transactions) | `idempotencyKey?`, `workflowRunId`, `workflowId`, `stageId`, `config` |
|
|
646
690
|
| `stage.pollSuspended` | Poll suspended stages (per-stage transactions) | `maxChecks?` (returns `resumedWorkflowRunIds`) |
|
|
647
691
|
| `lease.reapStale` | Release stale job leases | `staleThresholdMs` |
|
|
692
|
+
| `run.reapStuck` | Fail runs stuck RUNNING with no activity | `stuckThresholdMs?` |
|
|
648
693
|
| `outbox.flush` | Publish pending events | `maxEvents?` |
|
|
649
694
|
| `plugin.replayDLQ` | Replay dead-letter queue events | `maxEvents?` |
|
|
650
695
|
|
|
@@ -657,6 +702,11 @@ Transaction behavior:
|
|
|
657
702
|
- `job.execute` uses multi-phase transactions: Phase 1 commits `RUNNING` status immediately, Phase 2 runs `stageDef.execute()` outside any transaction, Phase 3 commits the final status. This avoids holding a database connection during long-running stage execution.
|
|
658
703
|
- `stage.pollSuspended` uses per-stage transactions: `checkCompletion()` runs outside any transaction (external HTTP calls to batch providers), then DB updates + outbox events are committed in a short transaction per stage. This prevents P2028 timeout errors when batch APIs are slow.
|
|
659
704
|
|
|
705
|
+
Cancellation semantics:
|
|
706
|
+
- `run.cancel` is **authoritative**: it marks the run as `CANCELLED`, cascades to all non-terminal stages (setting them to `CANCELLED` and clearing `nextPollAt`), and cancels all queued/suspended jobs via `jobTransport.cancelByRun()`.
|
|
707
|
+
- `stage.pollSuspended` skips stages whose run has been cancelled.
|
|
708
|
+
- `job.execute` re-checks run status after stage execution. If the run was cancelled during execution, the result is discarded and a `ghost: true` flag is returned. Hosts use this flag to prevent retries.
|
|
709
|
+
|
|
660
710
|
### Node Host Config
|
|
661
711
|
|
|
662
712
|
| Option | Type | Default | Description |
|
|
@@ -703,6 +753,9 @@ import { createPrismaWorkflowPersistence, createPrismaJobQueue, createPrismaAICa
|
|
|
703
753
|
// AI Helper
|
|
704
754
|
import { createAIHelper, type AIHelper } from "@bratsos/workflow-engine";
|
|
705
755
|
|
|
756
|
+
// Stage ID utilities
|
|
757
|
+
import { createStageIds, defineStageIds, isValidStageId, assertValidStageId } from "@bratsos/workflow-engine";
|
|
758
|
+
|
|
706
759
|
// Testing
|
|
707
760
|
import { InMemoryWorkflowPersistence, InMemoryJobQueue } from "@bratsos/workflow-engine/testing";
|
|
708
761
|
import { FakeClock, InMemoryBlobStore, CollectingEventSink, NoopScheduler } from "@bratsos/workflow-engine/kernel/testing";
|
|
@@ -13,5 +13,5 @@ var StaleVersionError = class extends Error {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export { StaleVersionError };
|
|
16
|
-
//# sourceMappingURL=chunk-
|
|
17
|
-
//# sourceMappingURL=chunk-
|
|
16
|
+
//# sourceMappingURL=chunk-2HEV5ZJL.js.map
|
|
17
|
+
//# sourceMappingURL=chunk-2HEV5ZJL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/persistence/interface.ts"],"names":[],"mappings":";AAkDO,IAAM,iBAAA,GAAN,cAAgC,KAAA,CAAM;AAAA,EAC3C,WAAA,CACkB,MAAA,EACA,EAAA,EACA,QAAA,EACA,MAAA,EAChB;AACA,IAAA,KAAA;AAAA,MACE,oBAAoB,MAAM,CAAA,CAAA,EAAI,EAAE,CAAA,WAAA,EAAc,QAAQ,SAAS,MAAM,CAAA;AAAA,KACvE;AAPgB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,EAAA,GAAA,EAAA;AACA,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAKhB,IAAA,IAAA,CAAK,IAAA,GAAO,mBAAA;AAAA,EACd;AACF","file":"chunk-2HEV5ZJL.js","sourcesContent":["/**\n * Persistence Interfaces for Workflow Engine\n *\n * These interfaces abstract database operations to enable:\n * - Testing with mock implementations\n * - Future extraction into @bratsos/workflow-engine package\n * - Alternative database backends\n *\n * Implementations:\n * - PrismaWorkflowPersistence (default, in ./prisma/)\n * - InMemoryPersistence (for testing)\n */\n\n// ============================================================================\n// Unified Status Type\n// ============================================================================\n\n/**\n * Unified status type for workflows, stages, and jobs.\n *\n * - PENDING: Not started yet\n * - RUNNING: Currently executing\n * - SUSPENDED: Paused, waiting for external event (e.g., batch job completion)\n * - COMPLETED: Finished successfully\n * - FAILED: Finished with error\n * - CANCELLED: Manually stopped by user\n * - SKIPPED: Stage-specific - bypassed due to condition\n */\nexport type Status =\n | \"PENDING\"\n | \"RUNNING\"\n | \"SUSPENDED\"\n | \"COMPLETED\"\n | \"FAILED\"\n | \"CANCELLED\"\n | \"SKIPPED\";\n\n/** @deprecated Use Status instead */\nexport type WorkflowStatus = Status;\n\n/** @deprecated Use Status instead */\nexport type WorkflowStageStatus = Status;\n\n/** @deprecated Use Status instead. Note: PROCESSING is now RUNNING. */\nexport type JobStatus = Status;\n\nexport type LogLevel = \"DEBUG\" | \"INFO\" | \"WARN\" | \"ERROR\";\n\nexport type ArtifactType = \"STAGE_OUTPUT\" | \"ARTIFACT\" | \"METADATA\";\n\nexport class StaleVersionError extends Error {\n constructor(\n public readonly entity: string,\n public readonly id: string,\n public readonly expected: number,\n public readonly actual: number,\n ) {\n super(\n `Stale version on ${entity} ${id}: expected ${expected}, got ${actual}`,\n );\n this.name = \"StaleVersionError\";\n }\n}\n\n// ============================================================================\n// Record Types (minimal fields needed by the workflow engine)\n// ============================================================================\n\nexport interface WorkflowRunRecord {\n id: string;\n createdAt: Date;\n updatedAt: Date;\n version: number;\n workflowId: string;\n workflowName: string;\n workflowType: string;\n status: WorkflowStatus;\n startedAt: Date | null;\n completedAt: Date | null;\n duration: number | null;\n input: unknown;\n output: unknown | null;\n config: unknown;\n totalCost: number;\n totalTokens: number;\n priority: number;\n metadata: unknown | null;\n}\n\nexport interface WorkflowStageRecord {\n id: string;\n createdAt: Date;\n updatedAt: Date;\n version: number;\n workflowRunId: string;\n stageId: string;\n stageName: string;\n stageNumber: number;\n executionGroup: number;\n status: WorkflowStageStatus;\n startedAt: Date | null;\n completedAt: Date | null;\n duration: number | null;\n inputData: unknown | null;\n outputData: unknown | null;\n config: unknown | null;\n suspendedState: unknown | null;\n resumeData: unknown | null;\n nextPollAt: Date | null;\n pollInterval: number | null;\n maxWaitUntil: Date | null;\n metrics: unknown | null;\n embeddingInfo: unknown | null;\n errorMessage: string | null;\n}\n\nexport interface WorkflowLogRecord {\n id: string;\n createdAt: Date;\n workflowStageId: string | null;\n workflowRunId: string | null;\n level: LogLevel;\n message: string;\n metadata: unknown | null;\n}\n\nexport interface WorkflowArtifactRecord {\n id: string;\n createdAt: Date;\n updatedAt: Date;\n workflowRunId: string;\n workflowStageId: string | null;\n key: string;\n type: ArtifactType;\n data: unknown;\n size: number;\n metadata: unknown | null;\n}\n\n// ============================================================================\n// Outbox and Idempotency Record Types (for kernel transactional outbox)\n// ============================================================================\n\nexport interface OutboxRecord {\n id: string;\n workflowRunId: string;\n sequence: number;\n eventType: string;\n payload: unknown;\n causationId: string;\n occurredAt: Date;\n publishedAt: Date | null;\n retryCount: number;\n dlqAt: Date | null;\n}\n\nexport interface CreateOutboxEventInput {\n workflowRunId: string;\n eventType: string;\n payload: unknown;\n causationId: string;\n occurredAt: Date;\n}\n\nexport interface IdempotencyRecord {\n key: string;\n commandType: string;\n result: unknown;\n createdAt: Date;\n}\n\n// ============================================================================\n// AI Call Record Types\n// ============================================================================\n\nexport interface AICallRecord {\n id: string;\n createdAt: Date;\n topic: string;\n callType: string;\n modelKey: string;\n modelId: string;\n prompt: string;\n response: string;\n inputTokens: number;\n outputTokens: number;\n cost: number;\n metadata: unknown | null;\n}\n\nexport interface JobRecord {\n id: string;\n createdAt: Date;\n updatedAt: Date;\n workflowRunId: string;\n workflowId: string;\n stageId: string;\n status: JobStatus;\n priority: number;\n workerId: string | null;\n lockedAt: Date | null;\n startedAt: Date | null;\n completedAt: Date | null;\n attempt: number;\n maxAttempts: number;\n lastError: string | null;\n nextPollAt: Date | null;\n payload: Record<string, unknown>;\n}\n\n// ============================================================================\n// Input Types (for creating/updating records)\n// ============================================================================\n\nexport interface CreateRunInput {\n id?: string;\n workflowId: string;\n workflowName: string;\n workflowType: string;\n input: unknown;\n config?: unknown;\n priority?: number;\n /** Optional metadata stored as JSON on the run record. NOT spread into Prisma fields. */\n metadata?: Record<string, unknown>;\n}\n\nexport interface UpdateRunInput {\n status?: WorkflowStatus;\n startedAt?: Date;\n completedAt?: Date | null;\n duration?: number | null;\n output?: unknown;\n totalCost?: number;\n totalTokens?: number;\n expectedVersion?: number;\n}\n\nexport interface CreateStageInput {\n workflowRunId: string;\n stageId: string;\n stageName: string;\n stageNumber: number;\n executionGroup: number;\n status?: WorkflowStageStatus;\n startedAt?: Date;\n config?: unknown;\n inputData?: unknown;\n}\n\nexport interface UpdateStageInput {\n status?: WorkflowStageStatus;\n startedAt?: Date;\n completedAt?: Date;\n duration?: number;\n outputData?: unknown;\n config?: unknown;\n suspendedState?: unknown;\n resumeData?: unknown;\n nextPollAt?: Date | null;\n pollInterval?: number;\n maxWaitUntil?: Date;\n metrics?: unknown;\n embeddingInfo?: unknown;\n artifacts?: unknown;\n errorMessage?: string;\n expectedVersion?: number;\n}\n\nexport interface UpsertStageInput {\n workflowRunId: string;\n stageId: string;\n create: CreateStageInput;\n update: UpdateStageInput;\n}\n\nexport interface CreateLogInput {\n workflowRunId?: string;\n workflowStageId?: string;\n level: LogLevel;\n message: string;\n metadata?: unknown;\n}\n\nexport interface SaveArtifactInput {\n workflowRunId: string;\n workflowStageId?: string;\n key: string;\n type: ArtifactType;\n data: unknown;\n size: number;\n metadata?: unknown;\n}\n\nexport interface CreateAICallInput {\n topic: string;\n callType: string;\n modelKey: string;\n modelId: string;\n prompt: string;\n response: string;\n inputTokens: number;\n outputTokens: number;\n cost: number;\n metadata?: unknown;\n}\n\nexport interface EnqueueJobInput {\n workflowRunId: string;\n workflowId: string;\n stageId: string;\n priority?: number;\n payload?: Record<string, unknown>;\n scheduledFor?: Date;\n}\n\nexport interface DequeueResult {\n jobId: string;\n workflowRunId: string;\n workflowId: string;\n stageId: string;\n priority: number;\n attempt: number;\n maxAttempts: number;\n payload: Record<string, unknown>;\n}\n\n// ============================================================================\n// WorkflowPersistence Interface\n// ============================================================================\n\nexport interface WorkflowPersistence {\n /** Execute operations within a transaction boundary. */\n withTransaction<T>(fn: (tx: WorkflowPersistence) => Promise<T>): Promise<T>;\n\n // WorkflowRun operations\n createRun(data: CreateRunInput): Promise<WorkflowRunRecord>;\n updateRun(id: string, data: UpdateRunInput): Promise<void>;\n getRun(id: string): Promise<WorkflowRunRecord | null>;\n getRunStatus(id: string): Promise<WorkflowStatus | null>;\n getRunsByStatus(status: WorkflowStatus): Promise<WorkflowRunRecord[]>;\n getStuckRuns(stuckSince: Date): Promise<WorkflowRunRecord[]>;\n\n /**\n * Atomically claim a pending workflow run for processing.\n * Uses atomic update with WHERE status = 'PENDING' to prevent race conditions.\n *\n * @param id - The workflow run ID to claim\n * @returns true if successfully claimed, false if already claimed by another worker\n */\n claimPendingRun(id: string): Promise<boolean>;\n\n /**\n * Atomically find and claim the next pending workflow run.\n * Uses FOR UPDATE SKIP LOCKED pattern (in Postgres) to prevent race conditions\n * when multiple workers try to claim workflows simultaneously.\n *\n * Priority ordering: higher priority first, then oldest (FIFO within same priority).\n *\n * @returns The claimed workflow run (now with status RUNNING), or null if no pending runs\n */\n claimNextPendingRun(): Promise<WorkflowRunRecord | null>;\n\n // WorkflowStage operations\n createStage(data: CreateStageInput): Promise<WorkflowStageRecord>;\n upsertStage(data: UpsertStageInput): Promise<WorkflowStageRecord>;\n updateStage(id: string, data: UpdateStageInput): Promise<void>;\n updateStageByRunAndStageId(\n workflowRunId: string,\n stageId: string,\n data: UpdateStageInput,\n ): Promise<void>;\n getStage(runId: string, stageId: string): Promise<WorkflowStageRecord | null>;\n getStageById(id: string): Promise<WorkflowStageRecord | null>;\n getStagesByRun(\n runId: string,\n options?: { status?: WorkflowStageStatus; orderBy?: \"asc\" | \"desc\" },\n ): Promise<WorkflowStageRecord[]>;\n getSuspendedStages(beforeDate: Date): Promise<WorkflowStageRecord[]>;\n getFirstSuspendedStageReadyToResume(\n runId: string,\n ): Promise<WorkflowStageRecord | null>;\n getFirstFailedStage(runId: string): Promise<WorkflowStageRecord | null>;\n getLastCompletedStage(runId: string): Promise<WorkflowStageRecord | null>;\n getLastCompletedStageBefore(\n runId: string,\n executionGroup: number,\n ): Promise<WorkflowStageRecord | null>;\n deleteStage(id: string): Promise<void>;\n\n // WorkflowLog operations\n createLog(data: CreateLogInput): Promise<void>;\n\n // WorkflowArtifact operations (for StageStorage)\n saveArtifact(data: SaveArtifactInput): Promise<void>;\n loadArtifact(runId: string, key: string): Promise<unknown>;\n hasArtifact(runId: string, key: string): Promise<boolean>;\n deleteArtifact(runId: string, key: string): Promise<void>;\n listArtifacts(runId: string): Promise<WorkflowArtifactRecord[]>;\n getStageIdForArtifact(runId: string, stageId: string): Promise<string | null>;\n\n // Stage output convenience methods (replaces separate StageStorage)\n saveStageOutput(\n runId: string,\n workflowType: string,\n stageId: string,\n output: unknown,\n ): Promise<string>;\n\n // Outbox DLQ operations\n /** Increment retry count for a failed outbox event. Returns new count. */\n incrementOutboxRetryCount(id: string): Promise<number>;\n\n /** Move an outbox event to DLQ (sets dlqAt). */\n moveOutboxEventToDLQ(id: string): Promise<void>;\n\n /** Reset DLQ events so they can be reprocessed by outbox.flush. Returns count reset. */\n replayDLQEvents(maxEvents: number): Promise<number>;\n\n // Outbox operations\n /** Write events to the outbox. Sequences are auto-assigned per workflowRunId. */\n appendOutboxEvents(events: CreateOutboxEventInput[]): Promise<void>;\n\n /** Read unpublished events ordered by (workflowRunId, sequence). */\n getUnpublishedOutboxEvents(limit?: number): Promise<OutboxRecord[]>;\n\n /** Mark events as published. */\n markOutboxEventsPublished(ids: string[]): Promise<void>;\n\n // Idempotency operations\n /** Atomically acquire an idempotency key for command execution. */\n acquireIdempotencyKey(\n key: string,\n commandType: string,\n ): Promise<\n | { status: \"acquired\" }\n | { status: \"replay\"; result: unknown }\n | { status: \"in_progress\" }\n >;\n\n /** Mark an idempotency key as completed and cache the command result. */\n completeIdempotencyKey(\n key: string,\n commandType: string,\n result: unknown,\n ): Promise<void>;\n\n /** Release an in-progress idempotency key after command failure. */\n releaseIdempotencyKey(key: string, commandType: string): Promise<void>;\n}\n\n// ============================================================================\n// AICallLogger Interface\n// ============================================================================\n\nexport interface AIHelperStats {\n totalCalls: number;\n totalInputTokens: number;\n totalOutputTokens: number;\n totalCost: number;\n perModel: Record<\n string,\n { calls: number; inputTokens: number; outputTokens: number; cost: number }\n >;\n}\n\nexport interface AICallLogger {\n /**\n * Log a single AI call (fire and forget)\n */\n logCall(call: CreateAICallInput): void;\n\n /**\n * Log batch results (for recording batch API results)\n */\n logBatchResults(batchId: string, results: CreateAICallInput[]): Promise<void>;\n\n /**\n * Get aggregated stats for a topic prefix\n */\n getStats(topicPrefix: string): Promise<AIHelperStats>;\n\n /**\n * Check if batch results are already recorded\n */\n isRecorded(batchId: string): Promise<boolean>;\n}\n\n// ============================================================================\n// JobQueue Interface\n// ============================================================================\n\nexport interface JobQueue {\n /**\n * Add a new job to the queue\n */\n enqueue(options: EnqueueJobInput): Promise<string>;\n\n /**\n * Enqueue multiple stages in parallel (same execution group)\n */\n enqueueParallel(jobs: EnqueueJobInput[]): Promise<string[]>;\n\n /**\n * Atomically dequeue the next available job\n */\n dequeue(): Promise<DequeueResult | null>;\n\n /**\n * Mark job as completed\n */\n complete(jobId: string): Promise<void>;\n\n /**\n * Mark job as suspended (for async-batch)\n */\n suspend(jobId: string, nextPollAt: Date): Promise<void>;\n\n /**\n * Mark job as failed\n */\n fail(jobId: string, error: string, shouldRetry?: boolean): Promise<void>;\n\n /**\n * Get suspended jobs that are ready to be checked\n */\n getSuspendedJobsReadyToPoll(): Promise<\n Array<{ jobId: string; stageId: string; workflowRunId: string }>\n >;\n\n /**\n * Release stale locks (for crashed workers)\n */\n releaseStaleJobs(staleThresholdMs?: number): Promise<number>;\n\n /**\n * Cancel all pending/suspended jobs for a workflow run.\n * Returns count of cancelled jobs.\n */\n cancelByRun(workflowRunId: string): Promise<number>;\n}\n\n// ============================================================================\n// Default Implementations (lazy loaded to avoid circular deps)\n// ============================================================================\n\n// Re-export from prisma implementations for convenience\n// These will be the default implementations used when no custom persistence is provided\n"]}
|