@sprqvntrs/workflows 0.2.4
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/LICENSE +21 -0
- package/README.md +649 -0
- package/index.ts +234 -0
- package/package.json +53 -0
- package/src/coordination/lock-manager.ts +431 -0
- package/src/engine/execution-engine.ts +715 -0
- package/src/infrastructure/db-state.ts +703 -0
- package/src/infrastructure/pg-boss.ts +585 -0
- package/src/infrastructure/pgboss-queries.ts +320 -0
- package/src/infrastructure/schema.ts +342 -0
- package/src/operations/registry.ts +409 -0
- package/src/orchestrator.ts +1056 -0
- package/src/templates/registry.ts +591 -0
- package/src/testing/index.ts +476 -0
- package/src/types.ts +946 -0
- package/src/worker/index.ts +325 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,946 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for the workflow orchestration system.
|
|
3
|
+
*
|
|
4
|
+
* This module defines all the fundamental types used throughout the workflow package,
|
|
5
|
+
* including workflow templates, operations, status enums, and configuration options.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import type {
|
|
10
|
+
* WorkflowTemplate,
|
|
11
|
+
* OperationHandler,
|
|
12
|
+
* WorkflowStatus,
|
|
13
|
+
* } from '@sprqvntrs/workflows';
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Status Enums
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Possible states for a workflow during its lifecycle.
|
|
23
|
+
*
|
|
24
|
+
* State transitions:
|
|
25
|
+
* - `pending` → `active` (when worker picks up job)
|
|
26
|
+
* - `active` → `paused` (when hitting a checkpoint)
|
|
27
|
+
* - `paused` → `active` (when resumed)
|
|
28
|
+
* - `active` → `completed` (successful completion)
|
|
29
|
+
* - `active` → `failed` (unrecoverable error)
|
|
30
|
+
* - `*` → `cancelled` (manual cancellation)
|
|
31
|
+
*/
|
|
32
|
+
export type WorkflowStatus = 'pending' | 'active' | 'paused' | 'completed' | 'failed' | 'cancelled';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Possible states for an individual operation within a workflow.
|
|
36
|
+
*
|
|
37
|
+
* State transitions:
|
|
38
|
+
* - `pending` → `active` (when execution starts)
|
|
39
|
+
* - `active` → `completed` (successful completion)
|
|
40
|
+
* - `active` → `failed` (all retries exhausted)
|
|
41
|
+
* - `active` → `skipped` (conditional skip)
|
|
42
|
+
*/
|
|
43
|
+
export type OperationStatus = 'pending' | 'active' | 'completed' | 'failed' | 'skipped';
|
|
44
|
+
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// Template Definitions
|
|
47
|
+
// =============================================================================
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Configuration for pg-boss job queue behavior.
|
|
51
|
+
*
|
|
52
|
+
* These settings control how jobs are retried, expired, and retained.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* const queueConfig: QueueConfig = {
|
|
57
|
+
* retryLimit: 3,
|
|
58
|
+
* retryDelay: 10,
|
|
59
|
+
* retryBackoff: true,
|
|
60
|
+
* expireInSeconds: 3600,
|
|
61
|
+
* };
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export interface QueueConfig {
|
|
65
|
+
/**
|
|
66
|
+
* Maximum number of retry attempts for failed jobs.
|
|
67
|
+
* @default 3
|
|
68
|
+
*/
|
|
69
|
+
retryLimit?: number;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Initial delay in seconds between retry attempts.
|
|
73
|
+
* @default 5
|
|
74
|
+
*/
|
|
75
|
+
retryDelay?: number;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Whether to use exponential backoff for retries.
|
|
79
|
+
* When true, delay doubles with each attempt.
|
|
80
|
+
* @default true
|
|
81
|
+
*/
|
|
82
|
+
retryBackoff?: boolean;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Time in seconds before a job expires (considered failed if still running).
|
|
86
|
+
* @default 3600 (1 hour)
|
|
87
|
+
*/
|
|
88
|
+
expireInSeconds?: number;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Time in seconds to retain completed jobs in the database.
|
|
92
|
+
* @default 86400 (24 hours)
|
|
93
|
+
*/
|
|
94
|
+
retentionSeconds?: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Defines a single operation within a workflow stage.
|
|
99
|
+
*
|
|
100
|
+
* Operations are the smallest unit of work in a workflow. Each operation
|
|
101
|
+
* has a type that maps to a registered handler function.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* const operation: OperationTemplate = {
|
|
106
|
+
* type: 'analyze.content',
|
|
107
|
+
* timeout: 120000,
|
|
108
|
+
* maxAttempts: 3,
|
|
109
|
+
* critical: true,
|
|
110
|
+
* };
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export interface OperationTemplate {
|
|
114
|
+
/**
|
|
115
|
+
* Unique identifier for this operation type.
|
|
116
|
+
* Must match a registered operation handler.
|
|
117
|
+
* Convention: `category.action` (e.g., 'gather.data', 'analyze.content')
|
|
118
|
+
*/
|
|
119
|
+
type: string;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Maximum execution time in milliseconds before timeout.
|
|
123
|
+
* @default 30000 (30 seconds)
|
|
124
|
+
*/
|
|
125
|
+
timeout?: number;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Maximum retry attempts for this specific operation.
|
|
129
|
+
* @default 3
|
|
130
|
+
*/
|
|
131
|
+
maxAttempts?: number;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Whether failure of this operation should fail the entire workflow.
|
|
135
|
+
* When false, workflow continues even if operation fails.
|
|
136
|
+
* @default true
|
|
137
|
+
*/
|
|
138
|
+
critical?: boolean;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Optional condition function to determine if operation should run.
|
|
142
|
+
* Receives the current workflow context.
|
|
143
|
+
* Return `false` to skip this operation.
|
|
144
|
+
*/
|
|
145
|
+
condition?: (context: WorkflowContext) => boolean;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Defines a stage within a workflow.
|
|
150
|
+
*
|
|
151
|
+
* Stages are executed sequentially. Operations within a stage can run
|
|
152
|
+
* in parallel or sequentially based on the `parallel` flag.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```typescript
|
|
156
|
+
* const stage: StageTemplate = {
|
|
157
|
+
* name: 'analyze',
|
|
158
|
+
* description: 'Run all analysis operations',
|
|
159
|
+
* parallel: true,
|
|
160
|
+
* operations: [
|
|
161
|
+
* { type: 'analyze.competitors' },
|
|
162
|
+
* { type: 'analyze.market' },
|
|
163
|
+
* { type: 'analyze.financials' },
|
|
164
|
+
* ],
|
|
165
|
+
* };
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
export interface StageTemplate {
|
|
169
|
+
/**
|
|
170
|
+
* Unique name for this stage within the workflow.
|
|
171
|
+
* Used for tracking progress and error reporting.
|
|
172
|
+
*/
|
|
173
|
+
name: string;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Human-readable description of what this stage does.
|
|
177
|
+
*/
|
|
178
|
+
description?: string;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Operations to execute in this stage.
|
|
182
|
+
*/
|
|
183
|
+
operations: OperationTemplate[];
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Whether operations should run in parallel.
|
|
187
|
+
* When false, operations run sequentially in order.
|
|
188
|
+
* @default false
|
|
189
|
+
*/
|
|
190
|
+
parallel?: boolean;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Optional fix operations to run if verify operations fail.
|
|
194
|
+
* Only applicable for verify stages. Creates a fix-verify loop.
|
|
195
|
+
*/
|
|
196
|
+
fixOperations?: OperationTemplate[];
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Maximum number of fix-verify cycles before failing.
|
|
200
|
+
* Only applicable when fixOperations is defined.
|
|
201
|
+
* @default 3
|
|
202
|
+
*/
|
|
203
|
+
maxFixCycles?: number;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Optional condition function to determine if stage should run.
|
|
207
|
+
* Receives the current workflow context.
|
|
208
|
+
* Return `false` to skip this entire stage.
|
|
209
|
+
*/
|
|
210
|
+
condition?: (context: WorkflowContext) => boolean;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Defines a checkpoint where workflow can pause for manual intervention.
|
|
215
|
+
*
|
|
216
|
+
* Checkpoints allow workflows to pause at specific points, typically for
|
|
217
|
+
* human review or approval before continuing.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```typescript
|
|
221
|
+
* const checkpoint: CheckpointTemplate = {
|
|
222
|
+
* after: 'gather',
|
|
223
|
+
* status: 'data_ready',
|
|
224
|
+
* condition: (ctx) => !ctx.isAutomated,
|
|
225
|
+
* };
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
export interface CheckpointTemplate {
|
|
229
|
+
/**
|
|
230
|
+
* Stage name after which to pause.
|
|
231
|
+
*/
|
|
232
|
+
after: string;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Status string to set when paused at this checkpoint.
|
|
236
|
+
* Can be used by UI to show appropriate actions.
|
|
237
|
+
*/
|
|
238
|
+
status: string;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Optional condition to determine if checkpoint should trigger.
|
|
242
|
+
* If not provided, checkpoint always triggers.
|
|
243
|
+
*/
|
|
244
|
+
condition?: (context: WorkflowContext) => boolean;
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Optional timeout in milliseconds to auto-resume.
|
|
248
|
+
* If not provided, requires manual resume.
|
|
249
|
+
*/
|
|
250
|
+
autoResumeAfter?: number;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Coordination configuration for preventing race conditions.
|
|
255
|
+
*
|
|
256
|
+
* When multiple workflows might operate on the same entity, coordination
|
|
257
|
+
* ensures they don't interfere with each other.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```typescript
|
|
261
|
+
* const coordination: CoordinationConfig = {
|
|
262
|
+
* entityType: 'document',
|
|
263
|
+
* entityIdPath: 'documentId',
|
|
264
|
+
* criticalOperations: ['process.document', 'save.document'],
|
|
265
|
+
* };
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
export interface CoordinationConfig {
|
|
269
|
+
/**
|
|
270
|
+
* Type of entity being coordinated (e.g., 'document', 'user', 'order').
|
|
271
|
+
*/
|
|
272
|
+
entityType: string;
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Path in context to find the entity ID.
|
|
276
|
+
* Supports dot notation for nested paths.
|
|
277
|
+
*/
|
|
278
|
+
entityIdPath: string;
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Operation types that require coordination.
|
|
282
|
+
* These operations will wait for prior operations on the same entity.
|
|
283
|
+
*/
|
|
284
|
+
criticalOperations?: string[];
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Maximum time in milliseconds to wait for coordination.
|
|
288
|
+
* @default 600000 (10 minutes)
|
|
289
|
+
*/
|
|
290
|
+
coordinationTimeout?: number;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Complete workflow template definition.
|
|
295
|
+
*
|
|
296
|
+
* Templates define the structure and behavior of a workflow type.
|
|
297
|
+
* They are registered with the orchestrator and used to create workflow instances.
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```typescript
|
|
301
|
+
* const template: WorkflowTemplate = {
|
|
302
|
+
* type: 'content-generation',
|
|
303
|
+
* queue: 'default',
|
|
304
|
+
* version: '1.0.0',
|
|
305
|
+
* description: 'Generate and publish content',
|
|
306
|
+
* stages: [
|
|
307
|
+
* { name: 'gather', operations: [{ type: 'gather.data' }] },
|
|
308
|
+
* { name: 'generate', operations: [{ type: 'generate.content' }] },
|
|
309
|
+
* ],
|
|
310
|
+
* checkpoints: [
|
|
311
|
+
* { after: 'generate', status: 'content_ready' },
|
|
312
|
+
* ],
|
|
313
|
+
* };
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
export interface WorkflowTemplate {
|
|
317
|
+
/**
|
|
318
|
+
* Unique identifier for this workflow type.
|
|
319
|
+
* Used to look up the template when starting workflows.
|
|
320
|
+
*/
|
|
321
|
+
type: string;
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* pg-boss queue name for this workflow.
|
|
325
|
+
* Different queues can have different worker counts.
|
|
326
|
+
*/
|
|
327
|
+
queue: string;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Semantic version of this template.
|
|
331
|
+
* Useful for tracking template changes over time.
|
|
332
|
+
*/
|
|
333
|
+
version: string;
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Human-readable description of what this workflow does.
|
|
337
|
+
*/
|
|
338
|
+
description?: string;
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Estimated duration in seconds for UI display.
|
|
342
|
+
* Does not affect actual execution.
|
|
343
|
+
*/
|
|
344
|
+
estimatedDurationSeconds?: number;
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Queue configuration overrides for this workflow type.
|
|
348
|
+
*/
|
|
349
|
+
queueConfig?: QueueConfig;
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Ordered list of stages to execute.
|
|
353
|
+
*/
|
|
354
|
+
stages: StageTemplate[];
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Optional checkpoints for manual intervention.
|
|
358
|
+
*/
|
|
359
|
+
checkpoints?: CheckpointTemplate[];
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Optional next workflow to trigger on completion.
|
|
363
|
+
* Enables workflow chaining.
|
|
364
|
+
*/
|
|
365
|
+
nextWorkflow?: string;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Optional coordination configuration for race condition prevention.
|
|
369
|
+
*/
|
|
370
|
+
coordination?: CoordinationConfig;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// =============================================================================
|
|
374
|
+
// Operation Handler Types
|
|
375
|
+
// =============================================================================
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Result returned by an operation handler.
|
|
379
|
+
*
|
|
380
|
+
* Operations must return this structure to indicate success/failure
|
|
381
|
+
* and provide any data to merge into the workflow context.
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* ```typescript
|
|
385
|
+
* // Successful operation
|
|
386
|
+
* return {
|
|
387
|
+
* status: 'completed',
|
|
388
|
+
* data: { scrapedContent: content },
|
|
389
|
+
* };
|
|
390
|
+
*
|
|
391
|
+
* // Failed operation
|
|
392
|
+
* return {
|
|
393
|
+
* status: 'failed',
|
|
394
|
+
* reason: 'External API returned 503',
|
|
395
|
+
* };
|
|
396
|
+
* ```
|
|
397
|
+
*/
|
|
398
|
+
export interface OperationResult<TData = Record<string, unknown>> {
|
|
399
|
+
/**
|
|
400
|
+
* Outcome of the operation.
|
|
401
|
+
*/
|
|
402
|
+
status: 'completed' | 'failed';
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Data to merge into workflow context on success.
|
|
406
|
+
*/
|
|
407
|
+
data?: TData;
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Error message or reason for failure.
|
|
411
|
+
*/
|
|
412
|
+
reason?: string;
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Additional metadata about the operation execution.
|
|
416
|
+
*/
|
|
417
|
+
metadata?: Record<string, unknown>;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Context provided to operation handlers.
|
|
422
|
+
*
|
|
423
|
+
* Contains all information needed to execute an operation, including
|
|
424
|
+
* accumulated results from previous operations.
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* ```typescript
|
|
428
|
+
* const handler: OperationHandler = async (context) => {
|
|
429
|
+
* const { previousResults, workflowId, operationId } = context;
|
|
430
|
+
* const url = previousResults.websiteUrl;
|
|
431
|
+
* // ... perform operation
|
|
432
|
+
* };
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
435
|
+
export interface OperationContext<TPrevious = Record<string, unknown>> {
|
|
436
|
+
/**
|
|
437
|
+
* Unique identifier of the workflow instance.
|
|
438
|
+
*/
|
|
439
|
+
workflowId: string;
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Unique identifier of this operation instance.
|
|
443
|
+
*/
|
|
444
|
+
operationId: string;
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Type of this operation (from template).
|
|
448
|
+
*/
|
|
449
|
+
operationType: string;
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Name of the current stage.
|
|
453
|
+
*/
|
|
454
|
+
stageName: string;
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Current attempt number (1-indexed).
|
|
458
|
+
*/
|
|
459
|
+
attempt: number;
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Maximum attempts allowed for this operation.
|
|
463
|
+
*/
|
|
464
|
+
maxAttempts: number;
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Accumulated results from all previous operations.
|
|
468
|
+
* This is the merged workflow context up to this point.
|
|
469
|
+
*/
|
|
470
|
+
previousResults: TPrevious;
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Initial context provided when workflow was started.
|
|
474
|
+
*/
|
|
475
|
+
initialContext: Record<string, unknown>;
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Workflow type (from template).
|
|
479
|
+
*/
|
|
480
|
+
workflowType: string;
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Optional abort signal for cancellation.
|
|
484
|
+
*/
|
|
485
|
+
signal?: AbortSignal;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Function signature for operation handlers.
|
|
490
|
+
*
|
|
491
|
+
* Operation handlers are async functions that perform the actual work.
|
|
492
|
+
* They receive context and must return an OperationResult.
|
|
493
|
+
*
|
|
494
|
+
* @example
|
|
495
|
+
* ```typescript
|
|
496
|
+
* const scrapeWebsite: OperationHandler = async (context) => {
|
|
497
|
+
* try {
|
|
498
|
+
* const data = await scraper.scrape(context.previousResults.url);
|
|
499
|
+
* return { status: 'completed', data: { scrapedData: data } };
|
|
500
|
+
* } catch (error) {
|
|
501
|
+
* return { status: 'failed', reason: error.message };
|
|
502
|
+
* }
|
|
503
|
+
* };
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
506
|
+
export type OperationHandler<
|
|
507
|
+
TContext = Record<string, unknown>,
|
|
508
|
+
TResult = Record<string, unknown>,
|
|
509
|
+
> = (context: OperationContext<TContext>) => Promise<OperationResult<TResult>>;
|
|
510
|
+
|
|
511
|
+
// =============================================================================
|
|
512
|
+
// Workflow Context and State
|
|
513
|
+
// =============================================================================
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Accumulated workflow context.
|
|
517
|
+
*
|
|
518
|
+
* This type represents the accumulated state of a workflow,
|
|
519
|
+
* including initial context and all operation results merged together.
|
|
520
|
+
*/
|
|
521
|
+
export type WorkflowContext = Record<string, unknown>;
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Workflow record stored in the database.
|
|
525
|
+
*/
|
|
526
|
+
export interface WorkflowRecord {
|
|
527
|
+
/**
|
|
528
|
+
* Unique identifier (UUID).
|
|
529
|
+
*/
|
|
530
|
+
id: string;
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Workflow type (matches template type).
|
|
534
|
+
*/
|
|
535
|
+
type: string;
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Current status.
|
|
539
|
+
*/
|
|
540
|
+
status: WorkflowStatus;
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Accumulated context (initial + operation results).
|
|
544
|
+
*/
|
|
545
|
+
context: WorkflowContext;
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Currently executing stage name.
|
|
549
|
+
*/
|
|
550
|
+
currentStage: string | null;
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Checkpoint status if paused.
|
|
554
|
+
*/
|
|
555
|
+
checkpointStatus: string | null;
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Error message if failed.
|
|
559
|
+
*/
|
|
560
|
+
errorMessage: string | null;
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* When workflow was created.
|
|
564
|
+
*/
|
|
565
|
+
createdAt: Date;
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* When execution started.
|
|
569
|
+
*/
|
|
570
|
+
startedAt: Date | null;
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* When execution completed (success or failure).
|
|
574
|
+
*/
|
|
575
|
+
completedAt: Date | null;
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Template version used.
|
|
579
|
+
*/
|
|
580
|
+
templateVersion: string;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Operation record stored in the database.
|
|
585
|
+
*/
|
|
586
|
+
export interface OperationRecord {
|
|
587
|
+
/**
|
|
588
|
+
* Unique identifier (UUID).
|
|
589
|
+
*/
|
|
590
|
+
id: string;
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Parent workflow ID.
|
|
594
|
+
*/
|
|
595
|
+
workflowId: string;
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Operation type (matches template).
|
|
599
|
+
*/
|
|
600
|
+
type: string;
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Stage name this operation belongs to.
|
|
604
|
+
*/
|
|
605
|
+
stage: string;
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Current status.
|
|
609
|
+
*/
|
|
610
|
+
status: OperationStatus;
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Operation result data.
|
|
614
|
+
*/
|
|
615
|
+
result: Record<string, unknown> | null;
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Error message if failed.
|
|
619
|
+
*/
|
|
620
|
+
errorMessage: string | null;
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Current attempt count.
|
|
624
|
+
*/
|
|
625
|
+
attempts: number;
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Maximum allowed attempts.
|
|
629
|
+
*/
|
|
630
|
+
maxAttempts: number;
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* When operation was created.
|
|
634
|
+
*/
|
|
635
|
+
createdAt: Date;
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* When operation started executing.
|
|
639
|
+
*/
|
|
640
|
+
startedAt: Date | null;
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* When operation completed.
|
|
644
|
+
*/
|
|
645
|
+
completedAt: Date | null;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// =============================================================================
|
|
649
|
+
// Orchestrator Configuration
|
|
650
|
+
// =============================================================================
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Configuration for a single queue.
|
|
654
|
+
*
|
|
655
|
+
* @example
|
|
656
|
+
* ```typescript
|
|
657
|
+
* const queueDef: QueueDefinition = {
|
|
658
|
+
* name: 'heavy-processing',
|
|
659
|
+
* workers: 2,
|
|
660
|
+
* batchSize: 1,
|
|
661
|
+
* };
|
|
662
|
+
* ```
|
|
663
|
+
*/
|
|
664
|
+
export interface QueueDefinition {
|
|
665
|
+
/**
|
|
666
|
+
* Unique name for this queue.
|
|
667
|
+
*/
|
|
668
|
+
name: string;
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Number of concurrent workers for this queue.
|
|
672
|
+
* @default 1
|
|
673
|
+
*/
|
|
674
|
+
workers?: number;
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Number of jobs to fetch per worker poll.
|
|
678
|
+
* @default 1
|
|
679
|
+
*/
|
|
680
|
+
batchSize?: number;
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Polling interval in milliseconds.
|
|
684
|
+
* @default 2000
|
|
685
|
+
*/
|
|
686
|
+
pollingIntervalMs?: number;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Main configuration for the workflow orchestrator.
|
|
691
|
+
*
|
|
692
|
+
* @example
|
|
693
|
+
* ```typescript
|
|
694
|
+
* const config: OrchestratorConfig = {
|
|
695
|
+
* connectionString: process.env.DATABASE_URL,
|
|
696
|
+
* queues: [
|
|
697
|
+
* { name: 'default', workers: 5 },
|
|
698
|
+
* { name: 'sequential', workers: 1 },
|
|
699
|
+
* ],
|
|
700
|
+
* defaultTimeout: 30000,
|
|
701
|
+
* defaultRetryLimit: 3,
|
|
702
|
+
* };
|
|
703
|
+
* ```
|
|
704
|
+
*/
|
|
705
|
+
export interface OrchestratorConfig {
|
|
706
|
+
/**
|
|
707
|
+
* PostgreSQL connection string.
|
|
708
|
+
*/
|
|
709
|
+
connectionString: string;
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Queue definitions.
|
|
713
|
+
* At minimum, define a 'default' queue.
|
|
714
|
+
*/
|
|
715
|
+
queues: QueueDefinition[];
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Default operation timeout in milliseconds.
|
|
719
|
+
* @default 30000 (30 seconds)
|
|
720
|
+
*/
|
|
721
|
+
defaultTimeout?: number;
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Default retry limit for operations.
|
|
725
|
+
* @default 3
|
|
726
|
+
*/
|
|
727
|
+
defaultRetryLimit?: number;
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Default retry delay in seconds.
|
|
731
|
+
* @default 5
|
|
732
|
+
*/
|
|
733
|
+
defaultRetryDelay?: number;
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* pg-boss schema name.
|
|
737
|
+
* @default 'pgboss'
|
|
738
|
+
*/
|
|
739
|
+
schema?: string;
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Whether to enable debug logging.
|
|
743
|
+
* @default false
|
|
744
|
+
*/
|
|
745
|
+
debug?: boolean;
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Application name for pg-boss.
|
|
749
|
+
* Useful for monitoring in PostgreSQL.
|
|
750
|
+
*/
|
|
751
|
+
application?: string;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// =============================================================================
|
|
755
|
+
// API Types
|
|
756
|
+
// =============================================================================
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Options for starting a new workflow.
|
|
760
|
+
*
|
|
761
|
+
* @example
|
|
762
|
+
* ```typescript
|
|
763
|
+
* const options: StartWorkflowOptions = {
|
|
764
|
+
* type: 'content-generation',
|
|
765
|
+
* context: { documentId: '123', userId: 'user-456' },
|
|
766
|
+
* priority: 10,
|
|
767
|
+
* };
|
|
768
|
+
* ```
|
|
769
|
+
*/
|
|
770
|
+
export interface StartWorkflowOptions {
|
|
771
|
+
/**
|
|
772
|
+
* Workflow type (must match a registered template).
|
|
773
|
+
*/
|
|
774
|
+
type: string;
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Initial context for the workflow.
|
|
778
|
+
*/
|
|
779
|
+
context: WorkflowContext;
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Optional job priority (higher = more urgent).
|
|
783
|
+
* @default 0
|
|
784
|
+
*/
|
|
785
|
+
priority?: number;
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Optional delay before job becomes available (in seconds).
|
|
789
|
+
*/
|
|
790
|
+
startAfterSeconds?: number;
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Optional unique key to prevent duplicate workflows.
|
|
794
|
+
* If a workflow with this key exists and is not completed, start will fail.
|
|
795
|
+
*/
|
|
796
|
+
singletonKey?: string;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Result of starting a workflow.
|
|
801
|
+
*/
|
|
802
|
+
export interface StartWorkflowResult {
|
|
803
|
+
/**
|
|
804
|
+
* Unique identifier for the created workflow.
|
|
805
|
+
*/
|
|
806
|
+
workflowId: string;
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* pg-boss job ID.
|
|
810
|
+
*/
|
|
811
|
+
jobId: string;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Options for scheduling a recurring workflow.
|
|
816
|
+
*
|
|
817
|
+
* @example
|
|
818
|
+
* ```typescript
|
|
819
|
+
* const schedule: ScheduleOptions = {
|
|
820
|
+
* name: 'daily-cleanup',
|
|
821
|
+
* cron: '0 4 * * *',
|
|
822
|
+
* type: 'cleanup-workflow',
|
|
823
|
+
* context: { scope: 'all' },
|
|
824
|
+
* };
|
|
825
|
+
* ```
|
|
826
|
+
*/
|
|
827
|
+
export interface ScheduleOptions {
|
|
828
|
+
/**
|
|
829
|
+
* Unique name for this schedule.
|
|
830
|
+
*/
|
|
831
|
+
name: string;
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Cron expression (minute hour day-of-month month day-of-week).
|
|
835
|
+
*/
|
|
836
|
+
cron: string;
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Workflow type to start on each trigger.
|
|
840
|
+
*/
|
|
841
|
+
type: string;
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Context to pass to each workflow instance.
|
|
845
|
+
*/
|
|
846
|
+
context?: WorkflowContext;
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Timezone for cron evaluation.
|
|
850
|
+
* @default 'UTC'
|
|
851
|
+
*/
|
|
852
|
+
timezone?: string;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Detailed workflow status including operations.
|
|
857
|
+
*/
|
|
858
|
+
export interface WorkflowStatusDetails {
|
|
859
|
+
/**
|
|
860
|
+
* The workflow record.
|
|
861
|
+
*/
|
|
862
|
+
workflow: WorkflowRecord;
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* All operation records for this workflow.
|
|
866
|
+
*/
|
|
867
|
+
operations: OperationRecord[];
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Current progress as percentage (0-100).
|
|
871
|
+
*/
|
|
872
|
+
progress: number;
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Human-readable status message.
|
|
876
|
+
*/
|
|
877
|
+
message: string;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// =============================================================================
|
|
881
|
+
// Error Types
|
|
882
|
+
// =============================================================================
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Base error class for workflow errors.
|
|
886
|
+
*/
|
|
887
|
+
export class WorkflowError extends Error {
|
|
888
|
+
constructor(
|
|
889
|
+
message: string,
|
|
890
|
+
public readonly code: string,
|
|
891
|
+
public readonly context?: Record<string, unknown>,
|
|
892
|
+
) {
|
|
893
|
+
super(message);
|
|
894
|
+
this.name = 'WorkflowError';
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Error thrown when a template is invalid or not found.
|
|
900
|
+
*/
|
|
901
|
+
export class TemplateError extends WorkflowError {
|
|
902
|
+
constructor(message: string, context?: Record<string, unknown>) {
|
|
903
|
+
super(message, 'TEMPLATE_ERROR', context);
|
|
904
|
+
this.name = 'TemplateError';
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Error thrown when an operation fails.
|
|
910
|
+
*/
|
|
911
|
+
export class OperationError extends WorkflowError {
|
|
912
|
+
constructor(
|
|
913
|
+
message: string,
|
|
914
|
+
public readonly operationType: string,
|
|
915
|
+
public readonly operationId: string,
|
|
916
|
+
context?: Record<string, unknown>,
|
|
917
|
+
) {
|
|
918
|
+
super(message, 'OPERATION_ERROR', { ...context, operationType, operationId });
|
|
919
|
+
this.name = 'OperationError';
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Error thrown when an operation times out.
|
|
925
|
+
*/
|
|
926
|
+
export class TimeoutError extends WorkflowError {
|
|
927
|
+
constructor(
|
|
928
|
+
message: string,
|
|
929
|
+
public readonly operationType: string,
|
|
930
|
+
public readonly timeoutMs: number,
|
|
931
|
+
context?: Record<string, unknown>,
|
|
932
|
+
) {
|
|
933
|
+
super(message, 'TIMEOUT_ERROR', { ...context, operationType, timeoutMs });
|
|
934
|
+
this.name = 'TimeoutError';
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Error thrown when coordination/locking fails.
|
|
940
|
+
*/
|
|
941
|
+
export class CoordinationError extends WorkflowError {
|
|
942
|
+
constructor(message: string, context?: Record<string, unknown>) {
|
|
943
|
+
super(message, 'COORDINATION_ERROR', context);
|
|
944
|
+
this.name = 'CoordinationError';
|
|
945
|
+
}
|
|
946
|
+
}
|