ai-workflows 2.1.3 → 2.4.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +14 -1
- package/README.md +2 -0
- package/dist/barrier.d.ts +6 -0
- package/dist/barrier.d.ts.map +1 -1
- package/dist/barrier.js +45 -7
- package/dist/barrier.js.map +1 -1
- package/dist/cascade-context.d.ts.map +1 -1
- package/dist/cascade-context.js +25 -25
- package/dist/cascade-context.js.map +1 -1
- package/dist/cascade-executor.d.ts.map +1 -1
- package/dist/cascade-executor.js +1 -1
- package/dist/cascade-executor.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +23 -7
- package/dist/context.js.map +1 -1
- package/dist/cron-parser.d.ts +65 -0
- package/dist/cron-parser.d.ts.map +1 -0
- package/dist/cron-parser.js +294 -0
- package/dist/cron-parser.js.map +1 -0
- package/dist/cron-scheduler.d.ts +117 -0
- package/dist/cron-scheduler.d.ts.map +1 -0
- package/dist/cron-scheduler.js +176 -0
- package/dist/cron-scheduler.js.map +1 -0
- package/dist/database-context.d.ts +184 -0
- package/dist/database-context.d.ts.map +1 -0
- package/dist/database-context.js +428 -0
- package/dist/database-context.js.map +1 -0
- package/dist/digital-objects-adapter.d.ts +159 -0
- package/dist/digital-objects-adapter.d.ts.map +1 -0
- package/dist/digital-objects-adapter.js +229 -0
- package/dist/digital-objects-adapter.js.map +1 -0
- package/dist/durable-execution-cloudflare.d.ts +427 -0
- package/dist/durable-execution-cloudflare.d.ts.map +1 -0
- package/dist/durable-execution-cloudflare.js +510 -0
- package/dist/durable-execution-cloudflare.js.map +1 -0
- package/dist/durable-execution.d.ts +482 -0
- package/dist/durable-execution.d.ts.map +1 -0
- package/dist/durable-execution.js +594 -0
- package/dist/durable-execution.js.map +1 -0
- package/dist/durable-workflow.d.ts +176 -0
- package/dist/durable-workflow.d.ts.map +1 -0
- package/dist/durable-workflow.js +552 -0
- package/dist/durable-workflow.js.map +1 -0
- package/dist/graph/topological-sort.d.ts.map +1 -1
- package/dist/graph/topological-sort.js +5 -5
- package/dist/graph/topological-sort.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +101 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +115 -0
- package/dist/logger.js.map +1 -0
- package/dist/on.d.ts.map +1 -1
- package/dist/on.js +3 -3
- package/dist/on.js.map +1 -1
- package/dist/runtime.d.ts +169 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +275 -0
- package/dist/runtime.js.map +1 -0
- package/dist/send.d.ts.map +1 -1
- package/dist/send.js +4 -3
- package/dist/send.js.map +1 -1
- package/dist/telemetry.d.ts +150 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +388 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/timer-registry.d.ts +25 -0
- package/dist/timer-registry.d.ts.map +1 -1
- package/dist/timer-registry.js +42 -8
- package/dist/timer-registry.js.map +1 -1
- package/dist/types.d.ts +17 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/worker/durable-step.d.ts +481 -0
- package/dist/worker/durable-step.d.ts.map +1 -0
- package/dist/worker/durable-step.js +606 -0
- package/dist/worker/durable-step.js.map +1 -0
- package/dist/worker/index.d.ts +106 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +124 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/state-adapter.d.ts +230 -0
- package/dist/worker/state-adapter.d.ts.map +1 -0
- package/dist/worker/state-adapter.js +409 -0
- package/dist/worker/state-adapter.js.map +1 -0
- package/dist/worker/topological-executor.d.ts +282 -0
- package/dist/worker/topological-executor.d.ts.map +1 -0
- package/dist/worker/topological-executor.js +396 -0
- package/dist/worker/topological-executor.js.map +1 -0
- package/dist/worker/workflow-builder.d.ts +286 -0
- package/dist/worker/workflow-builder.d.ts.map +1 -0
- package/dist/worker/workflow-builder.js +565 -0
- package/dist/worker/workflow-builder.js.map +1 -0
- package/dist/worker.d.ts +800 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +2428 -0
- package/dist/worker.js.map +1 -0
- package/dist/workflow-builder.d.ts +287 -0
- package/dist/workflow-builder.d.ts.map +1 -0
- package/dist/workflow-builder.js +762 -0
- package/dist/workflow-builder.js.map +1 -0
- package/dist/workflow.d.ts +14 -30
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +132 -292
- package/dist/workflow.js.map +1 -1
- package/examples/01-ecommerce-order-pipeline.ts +358 -0
- package/examples/02-content-moderation-cascade.ts +454 -0
- package/examples/03-scheduled-reporting-dependencies.ts +479 -0
- package/examples/04-database-persistence.ts +518 -0
- package/examples/README.md +173 -0
- package/package.json +30 -13
- package/src/__tests__/digital-objects-adapter.test.ts +274 -0
- package/src/__tests__/durable-workflow.test.ts +297 -0
- package/src/barrier.ts +48 -7
- package/src/cascade-context.ts +36 -29
- package/src/cascade-executor.ts +3 -2
- package/src/context.ts +41 -12
- package/src/cron-parser.ts +347 -0
- package/src/cron-scheduler.ts +239 -0
- package/src/database-context.ts +658 -0
- package/src/digital-objects-adapter.ts +351 -0
- package/src/durable-execution-cloudflare.ts +855 -0
- package/src/durable-execution.ts +1042 -0
- package/src/durable-workflow.ts +717 -0
- package/src/graph/topological-sort.ts +6 -8
- package/src/index.ts +69 -0
- package/src/logger.ts +148 -0
- package/src/on.ts +8 -9
- package/src/runtime.ts +436 -0
- package/src/send.ts +4 -5
- package/src/telemetry.ts +577 -0
- package/src/timer-registry.ts +44 -10
- package/src/types.ts +32 -17
- package/src/worker/durable-step.ts +976 -0
- package/src/worker/index.ts +216 -0
- package/src/worker/state-adapter.ts +589 -0
- package/src/worker/topological-executor.ts +625 -0
- package/src/worker/workflow-builder.ts +871 -0
- package/src/worker.ts +2906 -0
- package/src/workflow-builder.ts +1068 -0
- package/src/workflow.ts +188 -351
- package/test/barrier-join.test.ts +32 -24
- package/test/cascade-executor.test.ts +9 -16
- package/test/cron-parser.test.ts +314 -0
- package/test/cron-scheduler.test.ts +291 -0
- package/test/database-context.test.ts +770 -0
- package/test/db-provider-adapter.test.ts +862 -0
- package/test/durable-execution-cloudflare.test.ts +606 -0
- package/test/durable-execution-in-process.test.ts +286 -0
- package/test/durable-execution.test.ts +247 -0
- package/test/e2e/workflow-scenarios.e2e.test.ts +1039 -0
- package/test/integration.test.ts +442 -0
- package/test/rpc-surface.test.ts +946 -0
- package/test/runtime.test.ts +262 -0
- package/test/schedule-timer-cleanup.test.ts +30 -21
- package/test/send-race-conditions.test.ts +30 -40
- package/test/worker/durable-cascade.test.ts +1117 -0
- package/test/worker/durable-step.test.ts +723 -0
- package/test/worker/topological-executor.test.ts +1240 -0
- package/test/worker/workflow-builder.test.ts +1067 -0
- package/test/worker.test.ts +608 -0
- package/test/workflow-builder.test.ts +1670 -0
- package/test/workflow-cron.test.ts +256 -0
- package/test/workflow-state-adapter.test.ts +923 -0
- package/test/workflow.test.ts +25 -22
- package/tsconfig.json +3 -1
- package/vitest.config.ts +38 -1
- package/vitest.workers.config.ts +44 -0
- package/wrangler.jsonc +22 -0
- package/.turbo/turbo-test.log +0 -169
- package/LICENSE +0 -21
- package/src/context.js +0 -83
- package/src/every.js +0 -267
- package/src/index.js +0 -71
- package/src/on.js +0 -79
- package/src/send.js +0 -111
- package/src/types.js +0 -4
- package/src/workflow.js +0 -455
- package/test/context.test.js +0 -116
- package/test/every.test.js +0 -282
- package/test/on.test.js +0 -80
- package/test/send.test.js +0 -89
- package/test/workflow.test.js +0 -224
- package/vitest.config.js +0 -7
package/src/barrier.ts
CHANGED
|
@@ -113,16 +113,18 @@ export class Barrier<T = unknown> {
|
|
|
113
113
|
private _timeoutId: ReturnType<typeof setTimeout> | null = null
|
|
114
114
|
private _cancelled = false
|
|
115
115
|
private _cancelError: Error | null = null
|
|
116
|
+
private _abortHandler: (() => void) | null = null
|
|
116
117
|
|
|
117
118
|
constructor(expectedCount: number, options: BarrierOptions<T> = {}) {
|
|
118
119
|
this._expected = expectedCount
|
|
119
120
|
this._options = options
|
|
120
121
|
|
|
121
|
-
// Set up abort signal listener
|
|
122
|
+
// Set up abort signal listener with proper cleanup tracking
|
|
122
123
|
if (options.signal) {
|
|
123
|
-
|
|
124
|
+
this._abortHandler = () => {
|
|
124
125
|
this.cancel(new Error('Operation aborted'))
|
|
125
|
-
}
|
|
126
|
+
}
|
|
127
|
+
options.signal.addEventListener('abort', this._abortHandler, { once: true })
|
|
126
128
|
}
|
|
127
129
|
}
|
|
128
130
|
|
|
@@ -210,6 +212,7 @@ export class Barrier<T = unknown> {
|
|
|
210
212
|
*/
|
|
211
213
|
reset(): void {
|
|
212
214
|
this._clearTimeout()
|
|
215
|
+
this._clearAbortHandler()
|
|
213
216
|
this._arrived = []
|
|
214
217
|
this._waitResolve = null
|
|
215
218
|
this._waitReject = null
|
|
@@ -224,6 +227,7 @@ export class Barrier<T = unknown> {
|
|
|
224
227
|
this._cancelled = true
|
|
225
228
|
this._cancelError = error
|
|
226
229
|
this._clearTimeout()
|
|
230
|
+
this._clearAbortHandler()
|
|
227
231
|
|
|
228
232
|
if (this._waitReject) {
|
|
229
233
|
this._waitReject(error)
|
|
@@ -232,15 +236,26 @@ export class Barrier<T = unknown> {
|
|
|
232
236
|
}
|
|
233
237
|
}
|
|
234
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Dispose of the barrier and cleanup all resources
|
|
241
|
+
*/
|
|
242
|
+
dispose(): void {
|
|
243
|
+
this._clearTimeout()
|
|
244
|
+
this._clearAbortHandler()
|
|
245
|
+
this._waitResolve = null
|
|
246
|
+
this._waitReject = null
|
|
247
|
+
}
|
|
248
|
+
|
|
235
249
|
/**
|
|
236
250
|
* Get current progress information
|
|
237
251
|
*/
|
|
238
252
|
getProgress(): BarrierProgress<T> {
|
|
253
|
+
const latestValue = this._arrived[this._arrived.length - 1]
|
|
239
254
|
return {
|
|
240
255
|
arrived: this._arrived.length,
|
|
241
256
|
expected: this._expected,
|
|
242
257
|
percentage: Math.round((this._arrived.length / this._expected) * 100),
|
|
243
|
-
latest:
|
|
258
|
+
...(latestValue !== undefined && { latest: latestValue }),
|
|
244
259
|
}
|
|
245
260
|
}
|
|
246
261
|
|
|
@@ -250,6 +265,13 @@ export class Barrier<T = unknown> {
|
|
|
250
265
|
this._timeoutId = null
|
|
251
266
|
}
|
|
252
267
|
}
|
|
268
|
+
|
|
269
|
+
private _clearAbortHandler(): void {
|
|
270
|
+
if (this._abortHandler && this._options.signal) {
|
|
271
|
+
this._options.signal.removeEventListener('abort', this._abortHandler)
|
|
272
|
+
this._abortHandler = null
|
|
273
|
+
}
|
|
274
|
+
}
|
|
253
275
|
}
|
|
254
276
|
|
|
255
277
|
/**
|
|
@@ -277,13 +299,28 @@ export async function waitForAll<T>(
|
|
|
277
299
|
|
|
278
300
|
const { timeout, signal } = options
|
|
279
301
|
|
|
302
|
+
// Track cleanup handlers
|
|
303
|
+
let abortHandler: (() => void) | null = null
|
|
304
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null
|
|
305
|
+
|
|
306
|
+
const cleanup = () => {
|
|
307
|
+
if (abortHandler && signal) {
|
|
308
|
+
signal.removeEventListener('abort', abortHandler)
|
|
309
|
+
abortHandler = null
|
|
310
|
+
}
|
|
311
|
+
if (timeoutId) {
|
|
312
|
+
clearTimeout(timeoutId)
|
|
313
|
+
timeoutId = null
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
280
317
|
// Build array of promises to race against
|
|
281
318
|
const racers: Promise<T[]>[] = [Promise.all(promises)]
|
|
282
319
|
|
|
283
320
|
// Add abort signal handling
|
|
284
321
|
if (signal) {
|
|
285
322
|
const abortPromise = new Promise<never>((_, reject) => {
|
|
286
|
-
|
|
323
|
+
abortHandler = () => {
|
|
287
324
|
reject(new Error('Operation aborted'))
|
|
288
325
|
}
|
|
289
326
|
if (signal.aborted) {
|
|
@@ -298,14 +335,18 @@ export async function waitForAll<T>(
|
|
|
298
335
|
// Add timeout if specified
|
|
299
336
|
if (timeout) {
|
|
300
337
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
301
|
-
setTimeout(() => {
|
|
338
|
+
timeoutId = setTimeout(() => {
|
|
302
339
|
reject(new BarrierTimeoutError(timeout, 0, promises.length))
|
|
303
340
|
}, timeout)
|
|
304
341
|
})
|
|
305
342
|
racers.push(timeoutPromise)
|
|
306
343
|
}
|
|
307
344
|
|
|
308
|
-
|
|
345
|
+
try {
|
|
346
|
+
return await Promise.race(racers)
|
|
347
|
+
} finally {
|
|
348
|
+
cleanup()
|
|
349
|
+
}
|
|
309
350
|
}
|
|
310
351
|
|
|
311
352
|
/**
|
package/src/cascade-context.ts
CHANGED
|
@@ -197,7 +197,10 @@ function correlationIdToTraceId(correlationId: string): string {
|
|
|
197
197
|
*/
|
|
198
198
|
function traceIdToCorrelationId(traceId: string): string {
|
|
199
199
|
// Convert 32 hex chars to UUID format
|
|
200
|
-
return `${traceId.slice(0, 8)}-${traceId.slice(8, 12)}-${traceId.slice(12, 16)}-${traceId.slice(
|
|
200
|
+
return `${traceId.slice(0, 8)}-${traceId.slice(8, 12)}-${traceId.slice(12, 16)}-${traceId.slice(
|
|
201
|
+
16,
|
|
202
|
+
20
|
|
203
|
+
)}-${traceId.slice(20, 32)}`
|
|
201
204
|
}
|
|
202
205
|
|
|
203
206
|
/**
|
|
@@ -208,13 +211,15 @@ export function createCascadeContext(options: CascadeContextOptions = {}): Casca
|
|
|
208
211
|
|
|
209
212
|
// Handle restoration from serialized format
|
|
210
213
|
if (fromSerialized) {
|
|
211
|
-
const restoredSteps: CascadeStep[] = fromSerialized.steps.map((s) =>
|
|
214
|
+
const restoredSteps: CascadeStep[] = fromSerialized.steps.map((s) =>
|
|
215
|
+
createRestoredStep(s, name)
|
|
216
|
+
)
|
|
212
217
|
|
|
213
218
|
const ctx: CascadeContext = {
|
|
214
219
|
correlationId: fromSerialized.correlationId,
|
|
215
220
|
spanId: fromSerialized.spanId,
|
|
216
|
-
parentId: fromSerialized.parentId,
|
|
217
|
-
name: fromSerialized.name,
|
|
221
|
+
...(fromSerialized.parentId !== undefined && { parentId: fromSerialized.parentId }),
|
|
222
|
+
...(fromSerialized.name !== undefined && { name: fromSerialized.name }),
|
|
218
223
|
depth: fromSerialized.depth,
|
|
219
224
|
steps: restoredSteps,
|
|
220
225
|
path: fromSerialized.path,
|
|
@@ -241,7 +246,7 @@ export function createCascadeContext(options: CascadeContextOptions = {}): Casca
|
|
|
241
246
|
correlationId,
|
|
242
247
|
spanId: newSpanId,
|
|
243
248
|
parentId: parsed.parentId,
|
|
244
|
-
name,
|
|
249
|
+
...(name !== undefined && { name }),
|
|
245
250
|
depth: 0,
|
|
246
251
|
steps: [],
|
|
247
252
|
path: [],
|
|
@@ -279,13 +284,13 @@ export function createCascadeContext(options: CascadeContextOptions = {}): Casca
|
|
|
279
284
|
const ctx: CascadeContext = {
|
|
280
285
|
correlationId,
|
|
281
286
|
spanId,
|
|
282
|
-
parentId,
|
|
283
|
-
name,
|
|
287
|
+
...(parentId !== undefined && { parentId }),
|
|
288
|
+
...(name !== undefined && { name }),
|
|
284
289
|
depth,
|
|
285
290
|
steps,
|
|
286
291
|
path,
|
|
287
292
|
createdAt,
|
|
288
|
-
parent,
|
|
293
|
+
...(parent !== undefined && { parent }),
|
|
289
294
|
get fullPath() {
|
|
290
295
|
if (this.parent) {
|
|
291
296
|
return [...this.parent.fullPath, ...this.path]
|
|
@@ -308,10 +313,10 @@ function createRestoredStep(serialized: SerializedCascadeStep, contextName?: str
|
|
|
308
313
|
const step: CascadeStep = {
|
|
309
314
|
name: serialized.name,
|
|
310
315
|
startedAt: serialized.startedAt,
|
|
311
|
-
completedAt: serialized.completedAt,
|
|
312
|
-
duration: serialized.duration,
|
|
316
|
+
...(serialized.completedAt !== undefined && { completedAt: serialized.completedAt }),
|
|
317
|
+
...(serialized.duration !== undefined && { duration: serialized.duration }),
|
|
313
318
|
status: serialized.status,
|
|
314
|
-
|
|
319
|
+
...(serialized.metadata !== undefined && { metadata: { ...serialized.metadata } }),
|
|
315
320
|
complete: () => {
|
|
316
321
|
step.status = 'completed'
|
|
317
322
|
step.completedAt = Date.now()
|
|
@@ -327,15 +332,15 @@ function createRestoredStep(serialized: SerializedCascadeStep, contextName?: str
|
|
|
327
332
|
step.metadata = { ...step.metadata, ...data }
|
|
328
333
|
},
|
|
329
334
|
to5WHEvent: () => ({
|
|
330
|
-
who: (step.metadata?.actor as string) || 'system',
|
|
331
|
-
what: (step.metadata?.action as string) || step.name,
|
|
335
|
+
who: (step.metadata?.['actor'] as string) || 'system',
|
|
336
|
+
what: (step.metadata?.['action'] as string) || step.name,
|
|
332
337
|
when: step.startedAt,
|
|
333
338
|
where: contextName || 'unknown',
|
|
334
|
-
why: step.metadata
|
|
339
|
+
...(step.metadata?.['reason'] !== undefined && { why: step.metadata['reason'] as string }),
|
|
335
340
|
how: {
|
|
336
|
-
duration: step.duration,
|
|
341
|
+
...(step.duration !== undefined && { duration: step.duration }),
|
|
337
342
|
status: step.status,
|
|
338
|
-
metadata: step.metadata,
|
|
343
|
+
...(step.metadata !== undefined && { metadata: step.metadata }),
|
|
339
344
|
},
|
|
340
345
|
}),
|
|
341
346
|
}
|
|
@@ -356,7 +361,7 @@ export function recordStep(
|
|
|
356
361
|
name,
|
|
357
362
|
startedAt,
|
|
358
363
|
status: 'running',
|
|
359
|
-
metadata
|
|
364
|
+
...(metadata !== undefined && { metadata: { ...metadata } }),
|
|
360
365
|
complete: () => {
|
|
361
366
|
step.status = 'completed'
|
|
362
367
|
step.completedAt = Date.now()
|
|
@@ -373,15 +378,15 @@ export function recordStep(
|
|
|
373
378
|
step.metadata = { ...step.metadata, ...data }
|
|
374
379
|
},
|
|
375
380
|
to5WHEvent: () => ({
|
|
376
|
-
who: (step.metadata?.actor as string) || 'system',
|
|
377
|
-
what: (step.metadata?.action as string) || name,
|
|
381
|
+
who: (step.metadata?.['actor'] as string) || 'system',
|
|
382
|
+
what: (step.metadata?.['action'] as string) || name,
|
|
378
383
|
when: startedAt,
|
|
379
384
|
where: ctx.name || 'cascade',
|
|
380
|
-
why: step.metadata
|
|
385
|
+
...(step.metadata?.['reason'] !== undefined && { why: step.metadata['reason'] as string }),
|
|
381
386
|
how: {
|
|
382
|
-
duration: step.duration,
|
|
387
|
+
...(step.duration !== undefined && { duration: step.duration }),
|
|
383
388
|
status: step.status,
|
|
384
|
-
metadata: step.metadata,
|
|
389
|
+
...(step.metadata !== undefined && { metadata: step.metadata }),
|
|
385
390
|
},
|
|
386
391
|
}),
|
|
387
392
|
}
|
|
@@ -408,16 +413,16 @@ function serializeContext(ctx: CascadeContext): SerializedCascadeContext {
|
|
|
408
413
|
return {
|
|
409
414
|
correlationId: ctx.correlationId,
|
|
410
415
|
spanId: ctx.spanId,
|
|
411
|
-
parentId: ctx.parentId,
|
|
412
|
-
name: ctx.name,
|
|
416
|
+
...(ctx.parentId !== undefined && { parentId: ctx.parentId }),
|
|
417
|
+
...(ctx.name !== undefined && { name: ctx.name }),
|
|
413
418
|
depth: ctx.depth,
|
|
414
419
|
steps: ctx.steps.map((step) => ({
|
|
415
420
|
name: step.name,
|
|
416
421
|
startedAt: step.startedAt,
|
|
417
|
-
completedAt: step.completedAt,
|
|
418
|
-
duration: step.duration,
|
|
422
|
+
...(step.completedAt !== undefined && { completedAt: step.completedAt }),
|
|
423
|
+
...(step.duration !== undefined && { duration: step.duration }),
|
|
419
424
|
status: step.status,
|
|
420
|
-
metadata: step.metadata,
|
|
425
|
+
...(step.metadata !== undefined && { metadata: step.metadata }),
|
|
421
426
|
})),
|
|
422
427
|
path: ctx.path,
|
|
423
428
|
createdAt: ctx.createdAt,
|
|
@@ -438,7 +443,8 @@ function formatContext(ctx: CascadeContext): string {
|
|
|
438
443
|
lines.push(` Depth: ${ctx.depth}`)
|
|
439
444
|
lines.push(` Steps:`)
|
|
440
445
|
for (const step of ctx.steps) {
|
|
441
|
-
const status =
|
|
446
|
+
const status =
|
|
447
|
+
step.status === 'completed' ? '[OK]' : step.status === 'failed' ? '[FAIL]' : '[...]'
|
|
442
448
|
const duration = step.duration ? ` (${step.duration}ms)` : ''
|
|
443
449
|
lines.push(` ${status} ${step.name}${duration}`)
|
|
444
450
|
}
|
|
@@ -464,7 +470,8 @@ function formatContextTree(ctx: CascadeContext): string {
|
|
|
464
470
|
const indent = ' '.repeat(i)
|
|
465
471
|
lines.push(`${indent}${context.name || 'cascade'} (depth: ${context.depth})`)
|
|
466
472
|
for (const step of context.steps) {
|
|
467
|
-
const status =
|
|
473
|
+
const status =
|
|
474
|
+
step.status === 'completed' ? '[OK]' : step.status === 'failed' ? '[FAIL]' : '[...]'
|
|
468
475
|
const duration = step.duration ? ` (${step.duration}ms)` : ''
|
|
469
476
|
lines.push(`${indent} ${status} ${step.name}${duration}`)
|
|
470
477
|
}
|
package/src/cascade-executor.ts
CHANGED
|
@@ -445,7 +445,8 @@ export class CascadeExecutor<T = unknown> {
|
|
|
445
445
|
attempts++
|
|
446
446
|
|
|
447
447
|
// Check if it's a timeout error
|
|
448
|
-
const isTimeout =
|
|
448
|
+
const isTimeout =
|
|
449
|
+
lastError.message.includes('timed out') || lastError.name === 'TimeoutError'
|
|
449
450
|
|
|
450
451
|
// If we've exhausted retries or it's a total timeout, stop
|
|
451
452
|
if (attempts > maxRetries || lastError instanceof CascadeTimeoutError) {
|
|
@@ -506,7 +507,7 @@ export class CascadeExecutor<T = unknown> {
|
|
|
506
507
|
return {
|
|
507
508
|
tier,
|
|
508
509
|
success: false,
|
|
509
|
-
error: lastError,
|
|
510
|
+
...(lastError !== undefined && { error: lastError }),
|
|
510
511
|
duration,
|
|
511
512
|
}
|
|
512
513
|
}
|
package/src/context.ts
CHANGED
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
* Workflow context implementation
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
WorkflowContext,
|
|
7
|
+
WorkflowState,
|
|
8
|
+
WorkflowHistoryEntry,
|
|
9
|
+
OnProxy,
|
|
10
|
+
EveryProxy,
|
|
11
|
+
EveryProxyTarget,
|
|
12
|
+
ScheduleHandler,
|
|
13
|
+
} from './types.js'
|
|
14
|
+
import { getLogger } from './logger.js'
|
|
6
15
|
|
|
7
16
|
/**
|
|
8
17
|
* Event bus interface (imported from send.ts to avoid circular dependency)
|
|
@@ -30,12 +39,15 @@ export function createWorkflowContext(eventBus: EventBusLike): WorkflowContext {
|
|
|
30
39
|
// Create no-op proxies for on/every (these are used in send context, not workflow setup)
|
|
31
40
|
const noOpOnProxy = new Proxy({} as OnProxy, {
|
|
32
41
|
get() {
|
|
33
|
-
return new Proxy(
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
return new Proxy(
|
|
43
|
+
{},
|
|
44
|
+
{
|
|
45
|
+
get() {
|
|
46
|
+
return () => {}
|
|
47
|
+
},
|
|
36
48
|
}
|
|
37
|
-
|
|
38
|
-
}
|
|
49
|
+
)
|
|
50
|
+
},
|
|
39
51
|
})
|
|
40
52
|
|
|
41
53
|
// Cast to EveryProxy is safe: Proxy handler implements all EveryProxy behaviors dynamically
|
|
@@ -46,14 +58,29 @@ export function createWorkflowContext(eventBus: EventBusLike): WorkflowContext {
|
|
|
46
58
|
get() {
|
|
47
59
|
return () => () => {}
|
|
48
60
|
},
|
|
49
|
-
apply() {}
|
|
61
|
+
apply() {},
|
|
50
62
|
}
|
|
51
63
|
) as EveryProxy
|
|
52
64
|
|
|
53
65
|
return {
|
|
54
|
-
|
|
66
|
+
track(event: string, data: unknown): void {
|
|
67
|
+
// Fire and forget - swallow errors
|
|
68
|
+
try {
|
|
69
|
+
addHistory({ type: 'event', name: `track:${event}`, data })
|
|
70
|
+
eventBus.emit(event, data).catch(() => {})
|
|
71
|
+
} catch {
|
|
72
|
+
// Silently swallow errors
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
send<T = unknown>(event: string, data: T): string {
|
|
77
|
+
const eventId = crypto.randomUUID()
|
|
55
78
|
addHistory({ type: 'event', name: event, data })
|
|
56
|
-
await
|
|
79
|
+
// Fire async but don't await - guaranteed delivery via event bus
|
|
80
|
+
eventBus.emit(event, { ...(data as object), _eventId: eventId }).catch((err) => {
|
|
81
|
+
getLogger().error(`[workflow] Failed to send event ${event}:`, err)
|
|
82
|
+
})
|
|
83
|
+
return eventId
|
|
57
84
|
},
|
|
58
85
|
|
|
59
86
|
async do<TData = unknown, TResult = unknown>(_event: string, _data: TData): Promise<TResult> {
|
|
@@ -72,7 +99,7 @@ export function createWorkflowContext(eventBus: EventBusLike): WorkflowContext {
|
|
|
72
99
|
getState(): WorkflowState {
|
|
73
100
|
// Return a deep copy to prevent mutation
|
|
74
101
|
return {
|
|
75
|
-
current: workflowState.current,
|
|
102
|
+
...(workflowState.current !== undefined && { current: workflowState.current }),
|
|
76
103
|
context: { ...workflowState.context },
|
|
77
104
|
history: [...workflowState.history],
|
|
78
105
|
}
|
|
@@ -88,7 +115,7 @@ export function createWorkflowContext(eventBus: EventBusLike): WorkflowContext {
|
|
|
88
115
|
|
|
89
116
|
log(message: string, data?: unknown): void {
|
|
90
117
|
addHistory({ type: 'action', name: 'log', data: { message, data } })
|
|
91
|
-
|
|
118
|
+
getLogger().log(`[workflow] ${message}`, data ?? '')
|
|
92
119
|
},
|
|
93
120
|
}
|
|
94
121
|
}
|
|
@@ -97,7 +124,9 @@ export function createWorkflowContext(eventBus: EventBusLike): WorkflowContext {
|
|
|
97
124
|
* Create an isolated workflow context (not connected to event bus)
|
|
98
125
|
* Useful for testing or standalone execution
|
|
99
126
|
*/
|
|
100
|
-
export function createIsolatedContext(): WorkflowContext & {
|
|
127
|
+
export function createIsolatedContext(): WorkflowContext & {
|
|
128
|
+
getEmittedEvents: () => Array<{ event: string; data: unknown }>
|
|
129
|
+
} {
|
|
101
130
|
const emittedEvents: Array<{ event: string; data: unknown }> = []
|
|
102
131
|
|
|
103
132
|
const ctx = createWorkflowContext({
|