@classytic/streamline 1.0.0 → 2.0.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 +66 -23
- package/dist/{container-BzpIMrrj.mjs → container-BSxj_Or4.mjs} +294 -165
- package/dist/context-DMkusPr_.mjs +224 -0
- package/dist/{errors-BqunvWPz.mjs → errors-Ba7ZziTN.mjs} +1 -16
- package/dist/{events-C0sZINZq.d.mts → events-DC0ddZZ9.d.mts} +1 -1
- package/dist/index.d.mts +179 -51
- package/dist/index.mjs +140 -43
- package/dist/integrations/fastify.d.mts +1 -1
- package/dist/integrations/fastify.mjs +1 -1
- package/dist/rolldown-runtime-95iHPtFO.mjs +18 -0
- package/dist/telemetry/index.d.mts +1 -1
- package/dist/telemetry/index.mjs +1 -1
- package/dist/{types-DG85_LzF.d.mts → types-DjgzSrNY.d.mts} +111 -1
- package/package.json +11 -10
- /package/dist/{events-B5aTz7kD.mjs → events-CpesEn3I.mjs} +0 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-95iHPtFO.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/logger.ts
|
|
4
|
+
var Logger = class {
|
|
5
|
+
minLevel = "info";
|
|
6
|
+
setLevel(level) {
|
|
7
|
+
this.minLevel = level;
|
|
8
|
+
}
|
|
9
|
+
debug(message, context) {
|
|
10
|
+
this.log("debug", message, context);
|
|
11
|
+
}
|
|
12
|
+
info(message, context) {
|
|
13
|
+
this.log("info", message, context);
|
|
14
|
+
}
|
|
15
|
+
warn(message, context) {
|
|
16
|
+
this.log("warn", message, context);
|
|
17
|
+
}
|
|
18
|
+
error(message, error, context) {
|
|
19
|
+
const errorContext = error instanceof Error ? {
|
|
20
|
+
error: {
|
|
21
|
+
message: error.message,
|
|
22
|
+
stack: error.stack
|
|
23
|
+
},
|
|
24
|
+
...context
|
|
25
|
+
} : {
|
|
26
|
+
error,
|
|
27
|
+
...context
|
|
28
|
+
};
|
|
29
|
+
this.log("error", message, errorContext);
|
|
30
|
+
}
|
|
31
|
+
log(level, message, context) {
|
|
32
|
+
if (!this.shouldLog(level)) return;
|
|
33
|
+
const logEntry = {
|
|
34
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35
|
+
level: level.toUpperCase(),
|
|
36
|
+
message,
|
|
37
|
+
...context
|
|
38
|
+
};
|
|
39
|
+
this.getLogFunction(level)(JSON.stringify(logEntry));
|
|
40
|
+
}
|
|
41
|
+
shouldLog(level) {
|
|
42
|
+
const levels = [
|
|
43
|
+
"debug",
|
|
44
|
+
"info",
|
|
45
|
+
"warn",
|
|
46
|
+
"error"
|
|
47
|
+
];
|
|
48
|
+
return levels.indexOf(level) >= levels.indexOf(this.minLevel);
|
|
49
|
+
}
|
|
50
|
+
getLogFunction(level) {
|
|
51
|
+
switch (level) {
|
|
52
|
+
case "error": return console.error;
|
|
53
|
+
case "warn": return console.warn;
|
|
54
|
+
case "info": return console.info;
|
|
55
|
+
default: return console.debug;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const logger = new Logger();
|
|
60
|
+
if (process.env.NODE_ENV === "development") logger.setLevel("debug");
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/execution/context.ts
|
|
64
|
+
var context_exports = /* @__PURE__ */ __exportAll({
|
|
65
|
+
GotoSignal: () => GotoSignal,
|
|
66
|
+
StepContextImpl: () => StepContextImpl,
|
|
67
|
+
WaitSignal: () => WaitSignal
|
|
68
|
+
});
|
|
69
|
+
var WaitSignal = class extends Error {
|
|
70
|
+
constructor(type, reason, data) {
|
|
71
|
+
super(reason);
|
|
72
|
+
this.type = type;
|
|
73
|
+
this.reason = reason;
|
|
74
|
+
this.data = data;
|
|
75
|
+
this.name = "WaitSignal";
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Signal thrown by ctx.goto() to jump execution to a different step.
|
|
80
|
+
* Caught by the engine to update currentStepId.
|
|
81
|
+
*/
|
|
82
|
+
var GotoSignal = class extends Error {
|
|
83
|
+
constructor(targetStepId) {
|
|
84
|
+
super(`goto:${targetStepId}`);
|
|
85
|
+
this.targetStepId = targetStepId;
|
|
86
|
+
this.name = "GotoSignal";
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var StepContextImpl = class {
|
|
90
|
+
signal;
|
|
91
|
+
constructor(runId, stepId, context, input, attempt, run, repository, eventBus, signal, signalStore) {
|
|
92
|
+
this.runId = runId;
|
|
93
|
+
this.stepId = stepId;
|
|
94
|
+
this.context = context;
|
|
95
|
+
this.input = input;
|
|
96
|
+
this.attempt = attempt;
|
|
97
|
+
this.run = run;
|
|
98
|
+
this.repository = repository;
|
|
99
|
+
this.eventBus = eventBus;
|
|
100
|
+
this.signalStore = signalStore;
|
|
101
|
+
this.signal = signal ?? new AbortController().signal;
|
|
102
|
+
}
|
|
103
|
+
async set(key, value) {
|
|
104
|
+
if (this.signal.aborted) throw new Error(`Cannot update context: workflow ${this.runId} has been cancelled`);
|
|
105
|
+
this.context[key] = value;
|
|
106
|
+
this.run.context[key] = value;
|
|
107
|
+
if ((await this.repository.updateOne({
|
|
108
|
+
_id: this.runId,
|
|
109
|
+
status: { $ne: "cancelled" }
|
|
110
|
+
}, { $set: {
|
|
111
|
+
[`context.${String(key)}`]: value,
|
|
112
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
113
|
+
} }, { bypassTenant: true })).modifiedCount === 0) throw new Error(`Cannot update context: workflow ${this.runId} may have been cancelled`);
|
|
114
|
+
}
|
|
115
|
+
getOutput(stepId) {
|
|
116
|
+
return this.run.steps.find((s) => s.stepId === stepId)?.output;
|
|
117
|
+
}
|
|
118
|
+
async wait(reason, data) {
|
|
119
|
+
throw new WaitSignal("human", reason, data);
|
|
120
|
+
}
|
|
121
|
+
async waitFor(eventName, reason) {
|
|
122
|
+
throw new WaitSignal("event", reason || `Waiting for ${eventName}`, { eventName });
|
|
123
|
+
}
|
|
124
|
+
async sleep(ms) {
|
|
125
|
+
const resumeAt = new Date(Date.now() + ms);
|
|
126
|
+
throw new WaitSignal("timer", `Sleep ${ms}ms`, { resumeAt });
|
|
127
|
+
}
|
|
128
|
+
async heartbeat() {
|
|
129
|
+
if (this.signal.aborted) return;
|
|
130
|
+
try {
|
|
131
|
+
await this.repository.updateOne({
|
|
132
|
+
_id: this.runId,
|
|
133
|
+
status: { $ne: "cancelled" }
|
|
134
|
+
}, { lastHeartbeat: /* @__PURE__ */ new Date() }, { bypassTenant: true });
|
|
135
|
+
} catch {}
|
|
136
|
+
}
|
|
137
|
+
emit(eventName, data) {
|
|
138
|
+
const payload = {
|
|
139
|
+
runId: this.runId,
|
|
140
|
+
stepId: this.stepId,
|
|
141
|
+
data
|
|
142
|
+
};
|
|
143
|
+
this.eventBus.emit(eventName, payload);
|
|
144
|
+
this.signalStore?.publish(`streamline:event:${eventName}`, payload);
|
|
145
|
+
}
|
|
146
|
+
log(message, data) {
|
|
147
|
+
logger.info(message, {
|
|
148
|
+
runId: this.runId,
|
|
149
|
+
stepId: this.stepId,
|
|
150
|
+
attempt: this.attempt,
|
|
151
|
+
...data !== void 0 && { data }
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async startChildWorkflow(workflowId, input) {
|
|
155
|
+
throw new WaitSignal("childWorkflow", `Waiting for child workflow: ${workflowId}`, {
|
|
156
|
+
childWorkflowId: workflowId,
|
|
157
|
+
childInput: input,
|
|
158
|
+
parentRunId: this.runId,
|
|
159
|
+
parentStepId: this.stepId
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
async goto(targetStepId) {
|
|
163
|
+
throw new GotoSignal(targetStepId);
|
|
164
|
+
}
|
|
165
|
+
async scatter(tasks, options) {
|
|
166
|
+
const taskIds = Object.keys(tasks);
|
|
167
|
+
const concurrency = options?.concurrency ?? Infinity;
|
|
168
|
+
const checkpoint = this.getCheckpoint() ?? {};
|
|
169
|
+
const results = {};
|
|
170
|
+
for (const id of taskIds) {
|
|
171
|
+
const saved = checkpoint[id];
|
|
172
|
+
if (saved?.done) results[id] = saved.value;
|
|
173
|
+
}
|
|
174
|
+
const pending = taskIds.filter((id) => !checkpoint[id]?.done);
|
|
175
|
+
if (pending.length === 0) return results;
|
|
176
|
+
const executing = /* @__PURE__ */ new Set();
|
|
177
|
+
for (const id of pending) {
|
|
178
|
+
if (this.signal.aborted) break;
|
|
179
|
+
const taskFn = tasks[id];
|
|
180
|
+
let promise;
|
|
181
|
+
promise = (async () => {
|
|
182
|
+
try {
|
|
183
|
+
const value = await taskFn();
|
|
184
|
+
results[id] = value;
|
|
185
|
+
checkpoint[id] = {
|
|
186
|
+
done: true,
|
|
187
|
+
value
|
|
188
|
+
};
|
|
189
|
+
} catch (err) {
|
|
190
|
+
throw err;
|
|
191
|
+
} finally {
|
|
192
|
+
executing.delete(promise);
|
|
193
|
+
await this.checkpoint(checkpoint);
|
|
194
|
+
}
|
|
195
|
+
})();
|
|
196
|
+
executing.add(promise);
|
|
197
|
+
if (executing.size >= concurrency) await Promise.race(executing).catch(() => {});
|
|
198
|
+
}
|
|
199
|
+
const failures = (await Promise.allSettled(executing)).filter((r) => r.status === "rejected");
|
|
200
|
+
if (failures.length > 0) throw failures[0].reason;
|
|
201
|
+
return results;
|
|
202
|
+
}
|
|
203
|
+
async checkpoint(value) {
|
|
204
|
+
if (this.signal.aborted) return;
|
|
205
|
+
const stepIndex = this.run.steps.findIndex((s) => s.stepId === this.stepId);
|
|
206
|
+
if (stepIndex === -1) return;
|
|
207
|
+
await this.repository.updateOne({
|
|
208
|
+
_id: this.runId,
|
|
209
|
+
status: { $ne: "cancelled" }
|
|
210
|
+
}, { $set: {
|
|
211
|
+
[`steps.${stepIndex}.output`]: { __checkpoint: value },
|
|
212
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
213
|
+
lastHeartbeat: /* @__PURE__ */ new Date()
|
|
214
|
+
} }, { bypassTenant: true });
|
|
215
|
+
const step = this.run.steps[stepIndex];
|
|
216
|
+
if (step) step.output = { __checkpoint: value };
|
|
217
|
+
}
|
|
218
|
+
getCheckpoint() {
|
|
219
|
+
return (this.run.steps.find((s) => s.stepId === this.stepId)?.output)?.__checkpoint;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
//#endregion
|
|
224
|
+
export { logger as a, context_exports as i, StepContextImpl as n, WaitSignal as r, GotoSignal as t };
|
|
@@ -1,20 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __exportAll = (all, no_symbols) => {
|
|
4
|
-
let target = {};
|
|
5
|
-
for (var name in all) {
|
|
6
|
-
__defProp(target, name, {
|
|
7
|
-
get: all[name],
|
|
8
|
-
enumerable: true
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
if (!no_symbols) {
|
|
12
|
-
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
13
|
-
}
|
|
14
|
-
return target;
|
|
15
|
-
};
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-95iHPtFO.mjs";
|
|
16
2
|
|
|
17
|
-
//#endregion
|
|
18
3
|
//#region src/utils/errors.ts
|
|
19
4
|
var errors_exports = /* @__PURE__ */ __exportAll({
|
|
20
5
|
DataCorruptionError: () => DataCorruptionError,
|
|
@@ -89,4 +89,4 @@ declare class WorkflowEventBus extends EventEmitter {
|
|
|
89
89
|
*/
|
|
90
90
|
declare const globalEventBus: WorkflowEventBus;
|
|
91
91
|
//#endregion
|
|
92
|
-
export {
|
|
92
|
+
export { StepEventPayload as a, WorkflowCompletedPayload as c, WorkflowFailedPayload as d, WorkflowResumedPayload as f, StepCompletedPayload as i, WorkflowEventBus as l, EngineErrorPayload as n, StepFailedPayload as o, globalEventBus as p, EventPayloadMap as r, StepRetryPayload as s, BaseEventPayload as t, WorkflowEventName as u };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as WorkflowHandlers, a as SchedulingInfo, c as StepError, d as StepState, f as StepStatus, g as WorkflowEventPayload, h as WorkflowDefinition, i as RunStatus, l as StepHandler, m as WaitingFor, n as InferHandlersContext, o as Step, p as TypedHandlers, r as RecurrencePattern, s as StepContext, t as InferContext, u as StepIds, v as WorkflowRun } from "./types-
|
|
2
|
-
import { n as
|
|
1
|
+
import { _ as WorkflowHandlers, a as SchedulingInfo, c as StepError, d as StepState, f as StepStatus, g as WorkflowEventPayload, h as WorkflowDefinition, i as RunStatus, l as StepHandler, m as WaitingFor, n as InferHandlersContext, o as Step, p as TypedHandlers, r as RecurrencePattern, s as StepContext, t as InferContext, u as StepIds, v as WorkflowRun } from "./types-DjgzSrNY.mjs";
|
|
2
|
+
import { a as StepEventPayload, c as WorkflowCompletedPayload, d as WorkflowFailedPayload, f as WorkflowResumedPayload, i as StepCompletedPayload, l as WorkflowEventBus, n as EngineErrorPayload, o as StepFailedPayload, p as globalEventBus, r as EventPayloadMap, s as StepRetryPayload, t as BaseEventPayload, u as WorkflowEventName } from "./events-DC0ddZZ9.mjs";
|
|
3
3
|
import { FilterQuery, Plugin, Repository } from "@classytic/mongokit";
|
|
4
4
|
import mongoose from "mongoose";
|
|
5
5
|
|
|
@@ -206,6 +206,12 @@ interface SmartSchedulerConfig {
|
|
|
206
206
|
staleCheckInterval: number;
|
|
207
207
|
/** Threshold for stale workflow detection */
|
|
208
208
|
staleThreshold: number;
|
|
209
|
+
/**
|
|
210
|
+
* Max workflows executing simultaneously.
|
|
211
|
+
* When the limit is reached, the scheduler skips processing new workflows until slots free up.
|
|
212
|
+
* @default Infinity (no limit — current behavior)
|
|
213
|
+
*/
|
|
214
|
+
maxConcurrentExecutions: number;
|
|
209
215
|
}
|
|
210
216
|
declare class SmartScheduler {
|
|
211
217
|
private repository;
|
|
@@ -347,6 +353,38 @@ declare class WorkflowCache {
|
|
|
347
353
|
}
|
|
348
354
|
//#endregion
|
|
349
355
|
//#region src/core/container.d.ts
|
|
356
|
+
/**
|
|
357
|
+
* Pluggable signal store for durable cross-process event delivery.
|
|
358
|
+
*
|
|
359
|
+
* Default: in-memory (process-local). For durable signals across workers,
|
|
360
|
+
* plug in Redis, Kafka, BullMQ, or any pub/sub backend.
|
|
361
|
+
*
|
|
362
|
+
* Streamline never depends on these — users bring their own adapter.
|
|
363
|
+
*
|
|
364
|
+
* @example Redis adapter (user-provided)
|
|
365
|
+
* ```typescript
|
|
366
|
+
* import Redis from 'ioredis';
|
|
367
|
+
*
|
|
368
|
+
* const redis = new Redis();
|
|
369
|
+
* const signalStore: SignalStore = {
|
|
370
|
+
* publish: (channel, data) => redis.publish(channel, JSON.stringify(data)),
|
|
371
|
+
* subscribe: (channel, handler) => {
|
|
372
|
+
* const sub = redis.duplicate();
|
|
373
|
+
* sub.subscribe(channel);
|
|
374
|
+
* sub.on('message', (ch, msg) => handler(JSON.parse(msg)));
|
|
375
|
+
* return () => { sub.unsubscribe(channel); sub.disconnect(); };
|
|
376
|
+
* },
|
|
377
|
+
* };
|
|
378
|
+
*
|
|
379
|
+
* const container = createContainer({ signalStore });
|
|
380
|
+
* ```
|
|
381
|
+
*/
|
|
382
|
+
interface SignalStore {
|
|
383
|
+
/** Publish a signal to a named channel */
|
|
384
|
+
publish(channel: string, data: unknown): Promise<void> | void;
|
|
385
|
+
/** Subscribe to a channel. Returns an unsubscribe function. */
|
|
386
|
+
subscribe(channel: string, handler: (data: unknown) => void): (() => void) | Promise<() => void>;
|
|
387
|
+
}
|
|
350
388
|
/**
|
|
351
389
|
* Container holding all shared dependencies for a workflow engine instance.
|
|
352
390
|
*
|
|
@@ -369,6 +407,8 @@ interface StreamlineContainer {
|
|
|
369
407
|
eventBus: WorkflowEventBus;
|
|
370
408
|
/** In-memory cache for active workflows */
|
|
371
409
|
cache: WorkflowCache;
|
|
410
|
+
/** Pluggable signal store for durable cross-process event delivery */
|
|
411
|
+
signalStore: SignalStore;
|
|
372
412
|
}
|
|
373
413
|
/**
|
|
374
414
|
* Options for creating a container
|
|
@@ -393,6 +433,12 @@ interface ContainerOptions {
|
|
|
393
433
|
* If undefined: creates a new isolated cache
|
|
394
434
|
*/
|
|
395
435
|
cache?: WorkflowCache;
|
|
436
|
+
/**
|
|
437
|
+
* Custom signal store for durable cross-process event delivery.
|
|
438
|
+
* - If SignalStore: uses the provided instance (e.g., Redis, Kafka, BullMQ adapter)
|
|
439
|
+
* - If undefined: uses default in-memory store (process-local)
|
|
440
|
+
*/
|
|
441
|
+
signalStore?: SignalStore;
|
|
396
442
|
}
|
|
397
443
|
/**
|
|
398
444
|
* Create a new container with configurable dependencies.
|
|
@@ -448,11 +494,27 @@ declare class HookRegistry {
|
|
|
448
494
|
}
|
|
449
495
|
/** Global hook registry instance */
|
|
450
496
|
declare const hookRegistry: HookRegistry;
|
|
497
|
+
/**
|
|
498
|
+
* Global registry mapping workflowId → engine.
|
|
499
|
+
* Populated by createWorkflow(). Enables ctx.startChildWorkflow() to find
|
|
500
|
+
* and start child workflows by ID without the caller needing a reference.
|
|
501
|
+
*/
|
|
502
|
+
declare class WorkflowRegistryGlobal {
|
|
503
|
+
private engines;
|
|
504
|
+
register(workflowId: string, engine: WorkflowEngine<unknown>): void;
|
|
505
|
+
getEngine(workflowId: string): WorkflowEngine<unknown> | undefined;
|
|
506
|
+
}
|
|
507
|
+
declare const workflowRegistry: WorkflowRegistryGlobal;
|
|
451
508
|
interface WorkflowEngineOptions {
|
|
452
509
|
/** Auto-execute workflow after start (default: true) */
|
|
453
510
|
autoExecute?: boolean;
|
|
454
511
|
/** Custom scheduler configuration */
|
|
455
512
|
scheduler?: Partial<SmartSchedulerConfig>;
|
|
513
|
+
/**
|
|
514
|
+
* Compensation handlers for saga pattern rollback.
|
|
515
|
+
* Keyed by stepId. Called in reverse order when a later step fails.
|
|
516
|
+
*/
|
|
517
|
+
compensationHandlers?: WorkflowHandlers<unknown>;
|
|
456
518
|
}
|
|
457
519
|
/**
|
|
458
520
|
* Core workflow execution engine.
|
|
@@ -509,6 +571,11 @@ declare class WorkflowEngine<TContext = Record<string, unknown>> {
|
|
|
509
571
|
* @returns The updated workflow run
|
|
510
572
|
*/
|
|
511
573
|
execute(runId: string): Promise<WorkflowRun<TContext>>;
|
|
574
|
+
/**
|
|
575
|
+
* Run saga compensation handlers for completed steps in reverse order.
|
|
576
|
+
* Called automatically when a workflow fails and compensation handlers are registered.
|
|
577
|
+
*/
|
|
578
|
+
private runCompensation;
|
|
512
579
|
private getOrThrow;
|
|
513
580
|
private shouldContinueExecution;
|
|
514
581
|
private executeNextStep;
|
|
@@ -607,8 +674,74 @@ declare class WorkflowEngine<TContext = Record<string, unknown>> {
|
|
|
607
674
|
}
|
|
608
675
|
//#endregion
|
|
609
676
|
//#region src/workflow/define.d.ts
|
|
677
|
+
/**
|
|
678
|
+
* Per-step configuration for overriding timeout, retries, and conditions.
|
|
679
|
+
*
|
|
680
|
+
* `TContext` flows from `WorkflowConfig` — no manual annotation needed.
|
|
681
|
+
*
|
|
682
|
+
* @example
|
|
683
|
+
* ```typescript
|
|
684
|
+
* const workflow = createWorkflow('pipeline', {
|
|
685
|
+
* steps: {
|
|
686
|
+
* clone: {
|
|
687
|
+
* handler: async (ctx) => { ... },
|
|
688
|
+
* timeout: 120_000,
|
|
689
|
+
* retries: 5,
|
|
690
|
+
* },
|
|
691
|
+
* review: {
|
|
692
|
+
* handler: async (ctx) => { ... },
|
|
693
|
+
* timeout: 300_000,
|
|
694
|
+
* skipIf: (ctx) => ctx.autoApproved, // ctx is your TContext
|
|
695
|
+
* },
|
|
696
|
+
* },
|
|
697
|
+
* });
|
|
698
|
+
* ```
|
|
699
|
+
*/
|
|
700
|
+
interface StepConfig<TOutput = unknown, TContext = Record<string, unknown>> {
|
|
701
|
+
handler: StepHandler<TOutput, TContext>;
|
|
702
|
+
timeout?: number;
|
|
703
|
+
retries?: number;
|
|
704
|
+
condition?: (context: TContext, run: WorkflowRun) => boolean | Promise<boolean>;
|
|
705
|
+
skipIf?: (context: TContext) => boolean | Promise<boolean>;
|
|
706
|
+
runIf?: (context: TContext) => boolean | Promise<boolean>;
|
|
707
|
+
/**
|
|
708
|
+
* Compensation handler (saga pattern rollback).
|
|
709
|
+
*
|
|
710
|
+
* Called in reverse order when a later step fails permanently.
|
|
711
|
+
* Only runs for steps that completed successfully (status: 'done').
|
|
712
|
+
*
|
|
713
|
+
* @example
|
|
714
|
+
* ```typescript
|
|
715
|
+
* charge: {
|
|
716
|
+
* handler: async (ctx) => stripe.charges.create({ amount: 100 }),
|
|
717
|
+
* onCompensate: async (ctx) => {
|
|
718
|
+
* const chargeId = ctx.getOutput<{ id: string }>('charge')?.id;
|
|
719
|
+
* await stripe.refunds.create({ charge: chargeId });
|
|
720
|
+
* },
|
|
721
|
+
* },
|
|
722
|
+
* ```
|
|
723
|
+
*/
|
|
724
|
+
onCompensate?: StepHandler<unknown, TContext>;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Configuration for `createWorkflow()`.
|
|
728
|
+
*
|
|
729
|
+
* Steps can be plain async handlers or `StepConfig` objects with per-step
|
|
730
|
+
* timeout, retries, and conditions. Mix freely — `TContext` infers everywhere.
|
|
731
|
+
*
|
|
732
|
+
* @example
|
|
733
|
+
* ```typescript
|
|
734
|
+
* const config: WorkflowConfig<MyContext> = {
|
|
735
|
+
* steps: {
|
|
736
|
+
* fast: async (ctx) => ctx.context.value * 2,
|
|
737
|
+
* slow: { handler: async (ctx) => heavyWork(), timeout: 120_000 },
|
|
738
|
+
* },
|
|
739
|
+
* context: (input) => ({ value: input.n }),
|
|
740
|
+
* };
|
|
741
|
+
* ```
|
|
742
|
+
*/
|
|
610
743
|
interface WorkflowConfig<TContext, TInput = unknown> {
|
|
611
|
-
steps: Record<string, StepHandler<unknown, TContext>>;
|
|
744
|
+
steps: Record<string, StepHandler<unknown, TContext> | StepConfig<unknown, TContext>>;
|
|
612
745
|
context?: (input: TInput) => TContext;
|
|
613
746
|
version?: string;
|
|
614
747
|
defaults?: {
|
|
@@ -619,13 +752,25 @@ interface WorkflowConfig<TContext, TInput = unknown> {
|
|
|
619
752
|
/** Optional custom container for dependency injection */
|
|
620
753
|
container?: StreamlineContainer;
|
|
621
754
|
}
|
|
622
|
-
/** Options for waitFor
|
|
755
|
+
/** Options for `Workflow.waitFor()` */
|
|
623
756
|
interface WaitForOptions {
|
|
624
|
-
/** Poll interval in ms
|
|
757
|
+
/** Poll interval in ms @default 1000 */
|
|
625
758
|
pollInterval?: number;
|
|
626
|
-
/** Maximum time to wait in ms
|
|
759
|
+
/** Maximum time to wait in ms @default undefined (no timeout) */
|
|
627
760
|
timeout?: number;
|
|
628
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* A running workflow instance returned by `createWorkflow()`.
|
|
764
|
+
*
|
|
765
|
+
* Exported so consumers can type variables without the `ReturnType<>` workaround.
|
|
766
|
+
*
|
|
767
|
+
* @example
|
|
768
|
+
* ```typescript
|
|
769
|
+
* import { createWorkflow, type Workflow } from '@classytic/streamline';
|
|
770
|
+
*
|
|
771
|
+
* export const myWorkflow: Workflow<MyCtx, MyInput> = createWorkflow('my', { ... });
|
|
772
|
+
* ```
|
|
773
|
+
*/
|
|
629
774
|
interface Workflow<TContext, TInput = unknown> {
|
|
630
775
|
start: (input: TInput, meta?: Record<string, unknown>) => Promise<WorkflowRun<TContext>>;
|
|
631
776
|
get: (runId: string) => Promise<WorkflowRun<TContext> | null>;
|
|
@@ -634,22 +779,6 @@ interface Workflow<TContext, TInput = unknown> {
|
|
|
634
779
|
cancel: (runId: string) => Promise<WorkflowRun<TContext>>;
|
|
635
780
|
pause: (runId: string) => Promise<WorkflowRun<TContext>>;
|
|
636
781
|
rewindTo: (runId: string, stepId: string) => Promise<WorkflowRun<TContext>>;
|
|
637
|
-
/**
|
|
638
|
-
* Wait for a workflow to complete (reach a terminal state).
|
|
639
|
-
* Polls the workflow status until it's done, failed, or cancelled.
|
|
640
|
-
*
|
|
641
|
-
* @param runId - Workflow run ID to wait for
|
|
642
|
-
* @param options - Poll interval and timeout settings
|
|
643
|
-
* @returns The completed workflow run
|
|
644
|
-
* @throws {Error} If timeout is exceeded or workflow not found
|
|
645
|
-
*
|
|
646
|
-
* @example
|
|
647
|
-
* ```typescript
|
|
648
|
-
* const run = await workflow.start({ data: 'test' });
|
|
649
|
-
* const completed = await workflow.waitFor(run._id);
|
|
650
|
-
* console.log(completed.status); // 'done' | 'failed' | 'cancelled'
|
|
651
|
-
* ```
|
|
652
|
-
*/
|
|
653
782
|
waitFor: (runId: string, options?: WaitForOptions) => Promise<WorkflowRun<TContext>>;
|
|
654
783
|
shutdown: () => void;
|
|
655
784
|
definition: WorkflowDefinition<TContext>;
|
|
@@ -675,12 +804,17 @@ interface Workflow<TContext, TInput = unknown> {
|
|
|
675
804
|
* await orderProcess.start({ id: '123', email: 'user@example.com' });
|
|
676
805
|
* ```
|
|
677
806
|
*
|
|
678
|
-
* @example
|
|
807
|
+
* @example Per-step configuration
|
|
679
808
|
* ```typescript
|
|
680
|
-
* const
|
|
681
|
-
*
|
|
682
|
-
*
|
|
683
|
-
*
|
|
809
|
+
* const pipeline = createWorkflow('ci-pipeline', {
|
|
810
|
+
* steps: {
|
|
811
|
+
* clone: { handler: async (ctx) => { ... }, timeout: 120_000 },
|
|
812
|
+
* build: { handler: async (ctx) => { ... }, retries: 5 },
|
|
813
|
+
* deploy: {
|
|
814
|
+
* handler: async (ctx) => { ... },
|
|
815
|
+
* skipIf: (ctx) => !ctx.shouldDeploy,
|
|
816
|
+
* },
|
|
817
|
+
* },
|
|
684
818
|
* });
|
|
685
819
|
* ```
|
|
686
820
|
*/
|
|
@@ -714,37 +848,24 @@ interface HookResult {
|
|
|
714
848
|
* Create a hook that pauses workflow until external input.
|
|
715
849
|
* The token includes a crypto-random suffix for security.
|
|
716
850
|
*
|
|
717
|
-
* IMPORTANT: Pass the returned token to ctx.wait() to enable token validation:
|
|
718
|
-
* ```typescript
|
|
719
|
-
* const hook = createHook(ctx, 'approval');
|
|
720
|
-
* return ctx.wait(hook.token, { hookToken: hook.token }); // Token stored for validation
|
|
721
|
-
* ```
|
|
722
|
-
*
|
|
723
851
|
* @example
|
|
724
852
|
* ```typescript
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
* const hook = createHook(ctx, 'waiting-for-approval');
|
|
728
|
-
* console.log('Resume with token:', hook.token);
|
|
729
|
-
* return ctx.wait(hook.token, { hookToken: hook.token }); // Token validated on resume
|
|
730
|
-
* }
|
|
731
|
-
*
|
|
732
|
-
* // From API route
|
|
733
|
-
* await resumeHook('token-123', { approved: true });
|
|
853
|
+
* const hook = createHook(ctx, 'waiting-for-approval');
|
|
854
|
+
* return ctx.wait(hook.token, { hookToken: hook.token });
|
|
734
855
|
* ```
|
|
735
856
|
*/
|
|
736
857
|
declare function createHook(ctx: StepContext, reason: string, options?: HookOptions): HookResult;
|
|
737
858
|
/**
|
|
738
859
|
* Resume a paused workflow by hook token.
|
|
739
860
|
*
|
|
740
|
-
*
|
|
741
|
-
*
|
|
861
|
+
* **Durable**: Works across process restarts and multi-worker deployments.
|
|
862
|
+
* - Fast path: Uses in-memory hookRegistry if the engine is in this process.
|
|
863
|
+
* - Fallback: Looks up the workflow in MongoDB and resumes via atomic DB operations.
|
|
742
864
|
*
|
|
743
|
-
*
|
|
865
|
+
* Security: Validates the token against the stored hookToken if present.
|
|
744
866
|
*
|
|
745
867
|
* @example
|
|
746
868
|
* ```typescript
|
|
747
|
-
* // API route handler
|
|
748
869
|
* app.post('/hooks/:token', async (req, res) => {
|
|
749
870
|
* const result = await resumeHook(req.params.token, req.body);
|
|
750
871
|
* res.json({ success: true, runId: result.runId, status: result.run.status });
|
|
@@ -760,7 +881,6 @@ declare function resumeHook(token: string, payload: unknown): Promise<{
|
|
|
760
881
|
*
|
|
761
882
|
* @example
|
|
762
883
|
* ```typescript
|
|
763
|
-
* // Slack bot - same channel always gets same token
|
|
764
884
|
* const token = hookToken('slack', channelId);
|
|
765
885
|
* const hook = createHook(ctx, 'slack-message', { token });
|
|
766
886
|
* ```
|
|
@@ -769,10 +889,18 @@ declare function hookToken(...parts: string[]): string;
|
|
|
769
889
|
//#endregion
|
|
770
890
|
//#region src/execution/context.d.ts
|
|
771
891
|
declare class WaitSignal extends Error {
|
|
772
|
-
type: 'human' | 'webhook' | 'timer' | 'event';
|
|
892
|
+
type: 'human' | 'webhook' | 'timer' | 'event' | 'childWorkflow';
|
|
773
893
|
reason: string;
|
|
774
894
|
data?: unknown | undefined;
|
|
775
|
-
constructor(type: 'human' | 'webhook' | 'timer' | 'event', reason: string, data?: unknown | undefined);
|
|
895
|
+
constructor(type: 'human' | 'webhook' | 'timer' | 'event' | 'childWorkflow', reason: string, data?: unknown | undefined);
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Signal thrown by ctx.goto() to jump execution to a different step.
|
|
899
|
+
* Caught by the engine to update currentStepId.
|
|
900
|
+
*/
|
|
901
|
+
declare class GotoSignal extends Error {
|
|
902
|
+
targetStepId: string;
|
|
903
|
+
constructor(targetStepId: string);
|
|
776
904
|
}
|
|
777
905
|
//#endregion
|
|
778
906
|
//#region src/storage/run.model.d.ts
|
|
@@ -1016,7 +1144,7 @@ declare function getWorkflowProgress(run: WorkflowRun): WorkflowProgress;
|
|
|
1016
1144
|
declare function getStepUIStates(run: WorkflowRun): StepUIState[];
|
|
1017
1145
|
declare function getWaitingInfo(run: WorkflowRun): {
|
|
1018
1146
|
stepId: string;
|
|
1019
|
-
type: "human" | "webhook" | "timer" | "event";
|
|
1147
|
+
type: "human" | "webhook" | "timer" | "event" | "childWorkflow";
|
|
1020
1148
|
reason: string;
|
|
1021
1149
|
resumeAt: Date | undefined;
|
|
1022
1150
|
eventName: string | undefined;
|
|
@@ -1586,4 +1714,4 @@ declare class MaxRetriesExceededError extends WorkflowError {
|
|
|
1586
1714
|
constructor(stepId: string, attempts: number, runId?: string);
|
|
1587
1715
|
}
|
|
1588
1716
|
//#endregion
|
|
1589
|
-
export { COMPUTED, type CacheHealthStatus, CommonQueries, type ConditionalStep, type ContainerOptions, DataCorruptionError, ErrorCode, type ErrorCode as ErrorCodeType, type ExecuteParallelOptions, type GetScheduledWorkflowsOptions, InferContext, InferHandlersContext, InvalidStateError, MaxRetriesExceededError, RUN_STATUS, RUN_STATUS_VALUES, RecurrencePattern, RunStatus, STEP_STATUS, STEP_STATUS_VALUES, type ScheduleWorkflowOptions, type SchedulerStats, SchedulingInfo, SchedulingService, type SchedulingServiceConfig, type SmartSchedulerConfig, Step, type StepContext, StepError, StepHandler, StepIds, StepNotFoundError, StepState, StepStatus, type StepTimeline, StepTimeoutError, type StepUIState, type StreamlineContainer, type TenantFilterOptions, type TimezoneCalculationResult, TimezoneHandler, TypedHandlers, WaitSignal, WaitingFor, WorkflowCache, WorkflowDefinition, type WorkflowDefinitionDoc, WorkflowDefinitionModel, WorkflowEngine, type WorkflowEngineOptions, WorkflowError, WorkflowEventBus, type WorkflowEventName, WorkflowEventPayload, WorkflowHandlers, WorkflowNotFoundError, type WorkflowProgress, WorkflowQueryBuilder, type WorkflowRepositoryConfig, type WorkflowRun, WorkflowRunModel, type WorkflowRunRepository, canRewindTo, conditions, createCondition, createContainer, createHook, createWorkflow, createWorkflowRepository, deriveRunStatus, executeParallel, getExecutionPath, getStepTimeline, getStepUIStates, getWaitingInfo, getWorkflowProgress, globalEventBus, hookRegistry, hookToken, isConditionalStep, isRunStatus, isStepStatus, isStreamlineContainer, isTerminalState, isValidRunTransition, isValidStepTransition, resumeHook, shouldSkipStep, singleTenantPlugin, tenantFilterPlugin, timezoneHandler, workflowDefinitionRepository, workflowRunRepository };
|
|
1717
|
+
export { type BaseEventPayload, COMPUTED, type CacheHealthStatus, CommonQueries, type ConditionalStep, type ContainerOptions, DataCorruptionError, type EngineErrorPayload, ErrorCode, type ErrorCode as ErrorCodeType, type EventPayloadMap, type ExecuteParallelOptions, type GetScheduledWorkflowsOptions, GotoSignal, type HookOptions, type HookResult, InferContext, InferHandlersContext, InvalidStateError, MaxRetriesExceededError, RUN_STATUS, RUN_STATUS_VALUES, RecurrencePattern, RunStatus, STEP_STATUS, STEP_STATUS_VALUES, type ScheduleWorkflowOptions, type SchedulerStats, SchedulingInfo, SchedulingService, type SchedulingServiceConfig, type SignalStore, type SmartSchedulerConfig, Step, type StepCompletedPayload, type StepConfig, type StepContext, StepError, type StepEventPayload, type StepFailedPayload, StepHandler, StepIds, StepNotFoundError, type StepRetryPayload, StepState, StepStatus, type StepTimeline, StepTimeoutError, type StepUIState, type StreamlineContainer, type TenantFilterOptions, type TimezoneCalculationResult, TimezoneHandler, TypedHandlers, type WaitForOptions, WaitSignal, WaitingFor, type Workflow, WorkflowCache, type WorkflowCompletedPayload, type WorkflowConfig, WorkflowDefinition, type WorkflowDefinitionDoc, WorkflowDefinitionModel, WorkflowEngine, type WorkflowEngineOptions, WorkflowError, WorkflowEventBus, type WorkflowEventName, WorkflowEventPayload, type WorkflowFailedPayload, WorkflowHandlers, WorkflowNotFoundError, type WorkflowProgress, WorkflowQueryBuilder, type WorkflowRepositoryConfig, type WorkflowResumedPayload, type WorkflowRun, WorkflowRunModel, type WorkflowRunRepository, canRewindTo, conditions, createCondition, createContainer, createHook, createWorkflow, createWorkflowRepository, deriveRunStatus, executeParallel, getExecutionPath, getStepTimeline, getStepUIStates, getWaitingInfo, getWorkflowProgress, globalEventBus, hookRegistry, hookToken, isConditionalStep, isRunStatus, isStepStatus, isStreamlineContainer, isTerminalState, isValidRunTransition, isValidStepTransition, resumeHook, shouldSkipStep, singleTenantPlugin, tenantFilterPlugin, timezoneHandler, workflowDefinitionRepository, workflowRegistry, workflowRunRepository };
|