@spfn/workflow 0.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,545 @@
1
+ import * as _sinclair_typebox from '@sinclair/typebox';
2
+ import { TSchema, Static } from '@sinclair/typebox';
3
+ import { JobDef, InferJobInput, InferJobOutput } from '@spfn/core/job';
4
+ import { WorkflowExecution } from './entities/workflow-execution.js';
5
+ export { NewWorkflowExecution, workflowExecutions } from './entities/workflow-execution.js';
6
+ import { WorkflowStepExecution } from './entities/workflow-step-execution.js';
7
+ export { NewWorkflowStepExecution, workflowStepExecutions } from './entities/workflow-step-execution.js';
8
+ export { W as WorkflowStatus, a as WorkflowStepStatus } from './status-JJY5KGcN.js';
9
+ import 'drizzle-orm/pg-core';
10
+
11
+ /**
12
+ * Workflow Builder Types
13
+ */
14
+
15
+ /**
16
+ * Workflow context passed to step mappers
17
+ */
18
+ interface WorkflowContext<TInput, TResults extends Record<string, unknown>> {
19
+ /**
20
+ * Original workflow input
21
+ */
22
+ input: TInput;
23
+ /**
24
+ * Results from previous steps
25
+ */
26
+ results: TResults;
27
+ /**
28
+ * Execution metadata
29
+ */
30
+ execution: {
31
+ id: string;
32
+ workflowName: string;
33
+ startedAt: Date;
34
+ };
35
+ }
36
+ /**
37
+ * Step mapper function - maps workflow context to step input
38
+ */
39
+ type StepMapper<TInput, TResults extends Record<string, unknown>, TStepInput> = (ctx: WorkflowContext<TInput, TResults>) => TStepInput;
40
+ /**
41
+ * Step definition in a workflow
42
+ */
43
+ interface WorkflowStepDef<TStepInput = unknown, TStepOutput = unknown> {
44
+ /**
45
+ * Step name (unique within workflow)
46
+ */
47
+ name: string;
48
+ /**
49
+ * Job definition
50
+ */
51
+ job: JobDef<TStepInput, TStepOutput>;
52
+ /**
53
+ * Input mapper function
54
+ */
55
+ mapper: StepMapper<unknown, Record<string, unknown>, TStepInput>;
56
+ /**
57
+ * Step type
58
+ */
59
+ type: 'sequential' | 'parallel';
60
+ /**
61
+ * Parallel group name (for parallel steps)
62
+ */
63
+ parallelGroup?: string;
64
+ }
65
+ /**
66
+ * Notification provider interface
67
+ */
68
+ interface NotificationProvider {
69
+ name: string;
70
+ notify(event: WorkflowEvent): Promise<void>;
71
+ }
72
+ /**
73
+ * Workflow event types for notification
74
+ */
75
+ type WorkflowEventType = 'started' | 'completed' | 'failed' | 'cancelled' | 'step.started' | 'step.completed' | 'step.failed';
76
+ /**
77
+ * Workflow event for notifications
78
+ */
79
+ interface WorkflowEvent {
80
+ type: WorkflowEventType;
81
+ workflowName: string;
82
+ executionId: string;
83
+ stepName?: string;
84
+ stepIndex?: number;
85
+ input?: unknown;
86
+ output?: unknown;
87
+ error?: string;
88
+ timestamp: Date;
89
+ }
90
+ /**
91
+ * Notification configuration
92
+ */
93
+ interface NotifyConfig {
94
+ /**
95
+ * Events to notify on
96
+ */
97
+ on: WorkflowEventType[];
98
+ /**
99
+ * Condition for notification
100
+ */
101
+ when?: (event: WorkflowEvent) => boolean;
102
+ /**
103
+ * Notification providers
104
+ */
105
+ providers: NotificationProvider[];
106
+ }
107
+ /**
108
+ * Workflow definition
109
+ */
110
+ interface WorkflowDef<TName extends string = string, TInput = unknown> {
111
+ /**
112
+ * Workflow name (unique identifier)
113
+ */
114
+ name: TName;
115
+ /**
116
+ * Input schema
117
+ */
118
+ inputSchema?: TSchema;
119
+ /**
120
+ * Steps in execution order
121
+ */
122
+ steps: WorkflowStepDef[];
123
+ /**
124
+ * Can resume from failure point
125
+ */
126
+ resumable: boolean;
127
+ /**
128
+ * Enable rollback on failure
129
+ */
130
+ rollbackEnabled: boolean;
131
+ /**
132
+ * Notification configurations
133
+ */
134
+ notifyConfigs: NotifyConfig[];
135
+ /**
136
+ * Type inference helper
137
+ */
138
+ _input: TInput;
139
+ }
140
+ /**
141
+ * Infer workflow input type
142
+ */
143
+ type InferWorkflowInput<T> = T extends WorkflowDef<string, infer TInput> ? TInput : never;
144
+
145
+ /**
146
+ * Workflow builder with fluent API and type inference
147
+ */
148
+ declare class WorkflowBuilder<TName extends string, TInput = void, TResults extends Record<string, unknown> = Record<string, never>> {
149
+ private readonly _name;
150
+ private _inputSchema?;
151
+ private _steps;
152
+ private _resumable;
153
+ private _rollbackEnabled;
154
+ private _notifyConfigs;
155
+ constructor(name: TName);
156
+ /**
157
+ * Define input schema
158
+ */
159
+ input<TSchema extends _sinclair_typebox.TSchema>(schema: TSchema): WorkflowBuilder<TName, Static<TSchema>, TResults>;
160
+ /**
161
+ * Add a sequential step
162
+ */
163
+ pipe<TJob extends JobDef<any, any>, TStepName extends string = TJob['name']>(job: TJob, mapper: StepMapper<TInput, TResults, InferJobInput<TJob>>): WorkflowBuilder<TName, TInput, TResults & {
164
+ [K in TStepName]: InferJobOutput<TJob>;
165
+ }>;
166
+ /**
167
+ * Add parallel steps
168
+ */
169
+ parallel<TParallel extends Record<string, [JobDef<any, any>, StepMapper<TInput, TResults, any>]>>(steps: TParallel): WorkflowBuilder<TName, TInput, TResults & {
170
+ [K in keyof TParallel]: InferJobOutput<TParallel[K][0]>;
171
+ }>;
172
+ /**
173
+ * Enable/disable resumable (restart from failure point)
174
+ */
175
+ resumable(enabled?: boolean): this;
176
+ /**
177
+ * Enable/disable rollback on failure
178
+ */
179
+ rollback(enabled?: boolean): this;
180
+ /**
181
+ * Configure notifications (chainable — each call adds a separate config)
182
+ */
183
+ notify(config: NotifyConfig): this;
184
+ /**
185
+ * Build the workflow definition
186
+ */
187
+ build(): WorkflowDef<TName, TInput>;
188
+ }
189
+ /**
190
+ * Create a new workflow definition
191
+ *
192
+ * @example
193
+ * ```typescript
194
+ * const provisionTenant = workflow('provision-tenant')
195
+ * .input(Type.Object({
196
+ * tenantId: Type.String(),
197
+ * plan: Type.String(),
198
+ * }))
199
+ * .resumable(true)
200
+ * .pipe(createPodIdentity, (ctx) => ({
201
+ * tenantId: ctx.input.tenantId,
202
+ * }))
203
+ * .parallel({
204
+ * appRepo: [createAppRepo, (ctx) => ({ tenantId: ctx.input.tenantId })],
205
+ * gitopsRepo: [createGitopsRepo, (ctx) => ({ tenantId: ctx.input.tenantId })],
206
+ * })
207
+ * .pipe(notifyComplete, (ctx) => ({
208
+ * appRepoUrl: ctx.results.appRepo.repoUrl,
209
+ * gitopsRepoUrl: ctx.results.gitopsRepo.repoUrl,
210
+ * }))
211
+ * .build();
212
+ * ```
213
+ */
214
+ declare function workflow<TName extends string>(name: TName): WorkflowBuilder<TName>;
215
+
216
+ /**
217
+ * Workflow Engine Types
218
+ */
219
+
220
+ /**
221
+ * Logger interface for workflow engine
222
+ */
223
+ interface WorkflowLogger {
224
+ info(message: string, ...args: unknown[]): void;
225
+ error(message: string, ...args: unknown[]): void;
226
+ warn(message: string, ...args: unknown[]): void;
227
+ debug(message: string, ...args: unknown[]): void;
228
+ }
229
+ /**
230
+ * Default console logger
231
+ */
232
+ declare const defaultLogger: WorkflowLogger;
233
+ /**
234
+ * Workflow engine configuration
235
+ */
236
+ interface WorkflowEngineConfig {
237
+ /**
238
+ * Database instance (drizzle)
239
+ */
240
+ db: unknown;
241
+ /**
242
+ * Storage for large outputs (optional)
243
+ */
244
+ storage?: OutputStorage;
245
+ /**
246
+ * Large output threshold in bytes (default: 1MB)
247
+ */
248
+ largeOutputThreshold?: number;
249
+ /**
250
+ * Custom logger (optional, defaults to console)
251
+ */
252
+ logger?: WorkflowLogger;
253
+ /**
254
+ * Enable input schema validation (default: true)
255
+ */
256
+ validateInput?: boolean;
257
+ }
258
+ /**
259
+ * Output storage interface for large data
260
+ */
261
+ interface OutputStorage {
262
+ /**
263
+ * Upload data and return URL reference
264
+ */
265
+ upload(data: unknown): Promise<string>;
266
+ /**
267
+ * Download data from URL reference
268
+ */
269
+ download(url: string): Promise<unknown>;
270
+ }
271
+ /**
272
+ * Execution result from start()
273
+ */
274
+ interface ExecutionResult {
275
+ /**
276
+ * Execution ID
277
+ */
278
+ id: string;
279
+ /**
280
+ * Workflow name
281
+ */
282
+ workflowName: string;
283
+ /**
284
+ * Initial status
285
+ */
286
+ status: 'pending';
287
+ }
288
+ /**
289
+ * Execution status with details
290
+ */
291
+ interface ExecutionStatus extends WorkflowExecution {
292
+ /**
293
+ * Step executions
294
+ */
295
+ steps: WorkflowStepExecution[];
296
+ }
297
+ /**
298
+ * Cancel options
299
+ */
300
+ interface CancelOptions {
301
+ /**
302
+ * Execute rollback after cancel
303
+ */
304
+ rollback?: boolean;
305
+ }
306
+ /**
307
+ * List filter options
308
+ */
309
+ interface ListOptions {
310
+ /**
311
+ * Filter by workflow name
312
+ */
313
+ workflowName?: string;
314
+ /**
315
+ * Filter by status
316
+ */
317
+ status?: string;
318
+ /**
319
+ * Limit results
320
+ */
321
+ limit?: number;
322
+ /**
323
+ * Offset for pagination
324
+ */
325
+ offset?: number;
326
+ }
327
+ /**
328
+ * Workflow engine interface
329
+ */
330
+ interface WorkflowEngine<TWorkflows extends WorkflowDef<string, unknown>[]> {
331
+ /**
332
+ * Start a workflow execution
333
+ */
334
+ start<TName extends TWorkflows[number]['name']>(name: TName, input: ExtractWorkflowInput<TWorkflows, TName>): Promise<ExecutionResult>;
335
+ /**
336
+ * Get execution status
337
+ */
338
+ get(executionId: string): Promise<ExecutionStatus | null>;
339
+ /**
340
+ * Get step output
341
+ */
342
+ getStepOutput(executionId: string, stepName: string): Promise<unknown>;
343
+ /**
344
+ * List executions
345
+ */
346
+ list(options?: ListOptions): Promise<ExecutionStatus[]>;
347
+ /**
348
+ * Retry failed execution
349
+ */
350
+ retry(executionId: string): Promise<ExecutionResult>;
351
+ /**
352
+ * Cancel execution
353
+ */
354
+ cancel(executionId: string, options?: CancelOptions): Promise<void>;
355
+ /**
356
+ * Subscribe to execution events
357
+ */
358
+ subscribe(executionId: string, callback: (event: WorkflowEvent) => void): () => void;
359
+ }
360
+ /**
361
+ * Extract input type from workflow by name
362
+ */
363
+ type ExtractWorkflowInput<TWorkflows extends WorkflowDef<string, unknown>[], TName extends string> = TWorkflows extends (infer W)[] ? W extends WorkflowDef<TName, infer TInput> ? TInput : never : never;
364
+
365
+ /**
366
+ * Workflow Engine Implementation
367
+ *
368
+ * Orchestrates workflow execution using @spfn/core Job and Events
369
+ */
370
+
371
+ /**
372
+ * Create a workflow engine
373
+ *
374
+ * @example
375
+ * ```typescript
376
+ * const engine = createWorkflowEngine({
377
+ * workflows: [provisionTenant, deprovisionTenant],
378
+ * db: database,
379
+ * });
380
+ *
381
+ * const execution = await engine.start('provision-tenant', {
382
+ * tenantId: 'abc',
383
+ * plan: 'pro',
384
+ * });
385
+ * ```
386
+ */
387
+ declare function createWorkflowEngine<TWorkflows extends WorkflowDef<string, unknown>[]>(options: {
388
+ workflows: TWorkflows;
389
+ } & WorkflowEngineConfig): WorkflowEngine<TWorkflows>;
390
+
391
+ /**
392
+ * Built-in Notification Providers
393
+ *
394
+ * Only consoleProvider is provided by default.
395
+ * For email, SMS, Slack, etc., implement your own provider using @spfn/notification.
396
+ *
397
+ * @example
398
+ * ```typescript
399
+ * import { sendEmail } from '@spfn/notification/server';
400
+ * import type { NotificationProvider } from '@spfn/workflow';
401
+ *
402
+ * const emailProvider: NotificationProvider = {
403
+ * name: 'email',
404
+ * async notify(event) {
405
+ * await sendEmail({
406
+ * to: 'admin@example.com',
407
+ * subject: `[Workflow] ${event.workflowName}: ${event.type}`,
408
+ * text: formatEventAsText(event),
409
+ * });
410
+ * },
411
+ * };
412
+ * ```
413
+ */
414
+
415
+ /**
416
+ * Console notification provider
417
+ *
418
+ * Logs workflow events to console. This is the only built-in provider.
419
+ *
420
+ * @example
421
+ * ```typescript
422
+ * workflow('provision')
423
+ * .notify({
424
+ * on: ['failed'],
425
+ * providers: [consoleProvider],
426
+ * });
427
+ * ```
428
+ */
429
+ declare const consoleProvider: NotificationProvider;
430
+ /**
431
+ * Helper: Format workflow event as plain text
432
+ *
433
+ * Use this when implementing custom notification providers.
434
+ *
435
+ * @example
436
+ * ```typescript
437
+ * import { formatEventAsText } from '@spfn/workflow';
438
+ * import { sendEmail } from '@spfn/notification/server';
439
+ *
440
+ * const emailProvider: NotificationProvider = {
441
+ * name: 'email',
442
+ * async notify(event) {
443
+ * await sendEmail({
444
+ * to: 'admin@example.com',
445
+ * subject: `[Workflow] ${event.workflowName}: ${event.type}`,
446
+ * text: formatEventAsText(event),
447
+ * });
448
+ * },
449
+ * };
450
+ * ```
451
+ */
452
+ declare function formatEventAsText(event: WorkflowEvent): string;
453
+
454
+ /**
455
+ * Workflow Router
456
+ *
457
+ * Defines a collection of workflows for server registration
458
+ */
459
+
460
+ /**
461
+ * Workflow router configuration options
462
+ */
463
+ interface WorkflowRouterConfig {
464
+ /**
465
+ * Large output threshold in bytes
466
+ * Outputs larger than this will be stored in external storage
467
+ * @default 1024 * 1024 (1MB)
468
+ */
469
+ largeOutputThreshold?: number;
470
+ /**
471
+ * Storage for large outputs (optional)
472
+ */
473
+ storage?: OutputStorage;
474
+ /**
475
+ * Custom logger (optional, defaults to console)
476
+ */
477
+ logger?: WorkflowLogger;
478
+ /**
479
+ * Enable input schema validation (default: true)
480
+ */
481
+ validateInput?: boolean;
482
+ }
483
+ /**
484
+ * Workflow router instance
485
+ *
486
+ * Contains workflow definitions and provides access to the engine
487
+ */
488
+ interface WorkflowRouter<TWorkflows extends WorkflowDef[]> {
489
+ /**
490
+ * Registered workflows
491
+ */
492
+ readonly workflows: TWorkflows;
493
+ /**
494
+ * Internal workflows reference
495
+ */
496
+ readonly _workflows: TWorkflows;
497
+ /**
498
+ * Workflow engine instance (available after initialization)
499
+ */
500
+ readonly engine: WorkflowEngine<TWorkflows>;
501
+ /**
502
+ * Check if engine is initialized
503
+ */
504
+ readonly isInitialized: boolean;
505
+ /**
506
+ * Initialize the workflow engine
507
+ * Called internally by @spfn/core server
508
+ *
509
+ * @internal
510
+ */
511
+ _init: (db: WorkflowEngineConfig['db'], options?: WorkflowRouterConfig) => void;
512
+ }
513
+ /**
514
+ * Define a workflow router for server registration
515
+ *
516
+ * @example
517
+ * ```typescript
518
+ * import { workflow, defineWorkflowRouter } from '@spfn/workflow';
519
+ *
520
+ * const provisionTenant = workflow('provision-tenant')
521
+ * .input(Type.Object({ tenantId: Type.String() }))
522
+ * .pipe(createResources, ctx => ({ tenantId: ctx.input.tenantId }))
523
+ * .build();
524
+ *
525
+ * export const workflowRouter = defineWorkflowRouter([
526
+ * provisionTenant,
527
+ * deprovisionTenant,
528
+ * ]);
529
+ *
530
+ * // In server.config.ts
531
+ * export default defineServerConfig()
532
+ * .workflows(workflowRouter)
533
+ * .build();
534
+ *
535
+ * // Usage
536
+ * await workflowRouter.engine.start('provision-tenant', { tenantId: 'abc' });
537
+ * ```
538
+ */
539
+ declare function defineWorkflowRouter<TWorkflows extends WorkflowDef[]>(workflows: TWorkflows): WorkflowRouter<TWorkflows>;
540
+ /**
541
+ * Type guard to check if value is a WorkflowRouter
542
+ */
543
+ declare function isWorkflowRouter(value: unknown): value is WorkflowRouter<WorkflowDef[]>;
544
+
545
+ export { type CancelOptions, type ExecutionResult, type ExecutionStatus, type InferWorkflowInput, type ListOptions, type NotificationProvider, type NotifyConfig, type OutputStorage, type StepMapper, WorkflowBuilder, type WorkflowContext, type WorkflowDef, type WorkflowEngine, type WorkflowEngineConfig, type WorkflowEvent, type WorkflowEventType, WorkflowExecution, type WorkflowLogger, type WorkflowRouter, type WorkflowRouterConfig, type WorkflowStepDef, WorkflowStepExecution, consoleProvider, createWorkflowEngine, defaultLogger, defineWorkflowRouter, formatEventAsText, isWorkflowRouter, workflow };