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.
Files changed (188) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +14 -1
  3. package/README.md +2 -0
  4. package/dist/barrier.d.ts +6 -0
  5. package/dist/barrier.d.ts.map +1 -1
  6. package/dist/barrier.js +45 -7
  7. package/dist/barrier.js.map +1 -1
  8. package/dist/cascade-context.d.ts.map +1 -1
  9. package/dist/cascade-context.js +25 -25
  10. package/dist/cascade-context.js.map +1 -1
  11. package/dist/cascade-executor.d.ts.map +1 -1
  12. package/dist/cascade-executor.js +1 -1
  13. package/dist/cascade-executor.js.map +1 -1
  14. package/dist/context.d.ts.map +1 -1
  15. package/dist/context.js +23 -7
  16. package/dist/context.js.map +1 -1
  17. package/dist/cron-parser.d.ts +65 -0
  18. package/dist/cron-parser.d.ts.map +1 -0
  19. package/dist/cron-parser.js +294 -0
  20. package/dist/cron-parser.js.map +1 -0
  21. package/dist/cron-scheduler.d.ts +117 -0
  22. package/dist/cron-scheduler.d.ts.map +1 -0
  23. package/dist/cron-scheduler.js +176 -0
  24. package/dist/cron-scheduler.js.map +1 -0
  25. package/dist/database-context.d.ts +184 -0
  26. package/dist/database-context.d.ts.map +1 -0
  27. package/dist/database-context.js +428 -0
  28. package/dist/database-context.js.map +1 -0
  29. package/dist/digital-objects-adapter.d.ts +159 -0
  30. package/dist/digital-objects-adapter.d.ts.map +1 -0
  31. package/dist/digital-objects-adapter.js +229 -0
  32. package/dist/digital-objects-adapter.js.map +1 -0
  33. package/dist/durable-execution-cloudflare.d.ts +427 -0
  34. package/dist/durable-execution-cloudflare.d.ts.map +1 -0
  35. package/dist/durable-execution-cloudflare.js +510 -0
  36. package/dist/durable-execution-cloudflare.js.map +1 -0
  37. package/dist/durable-execution.d.ts +482 -0
  38. package/dist/durable-execution.d.ts.map +1 -0
  39. package/dist/durable-execution.js +594 -0
  40. package/dist/durable-execution.js.map +1 -0
  41. package/dist/durable-workflow.d.ts +176 -0
  42. package/dist/durable-workflow.d.ts.map +1 -0
  43. package/dist/durable-workflow.js +552 -0
  44. package/dist/durable-workflow.js.map +1 -0
  45. package/dist/graph/topological-sort.d.ts.map +1 -1
  46. package/dist/graph/topological-sort.js +5 -5
  47. package/dist/graph/topological-sort.js.map +1 -1
  48. package/dist/index.d.ts +4 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +15 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/logger.d.ts +101 -0
  53. package/dist/logger.d.ts.map +1 -0
  54. package/dist/logger.js +115 -0
  55. package/dist/logger.js.map +1 -0
  56. package/dist/on.d.ts.map +1 -1
  57. package/dist/on.js +3 -3
  58. package/dist/on.js.map +1 -1
  59. package/dist/runtime.d.ts +169 -0
  60. package/dist/runtime.d.ts.map +1 -0
  61. package/dist/runtime.js +275 -0
  62. package/dist/runtime.js.map +1 -0
  63. package/dist/send.d.ts.map +1 -1
  64. package/dist/send.js +4 -3
  65. package/dist/send.js.map +1 -1
  66. package/dist/telemetry.d.ts +150 -0
  67. package/dist/telemetry.d.ts.map +1 -0
  68. package/dist/telemetry.js +388 -0
  69. package/dist/telemetry.js.map +1 -0
  70. package/dist/timer-registry.d.ts +25 -0
  71. package/dist/timer-registry.d.ts.map +1 -1
  72. package/dist/timer-registry.js +42 -8
  73. package/dist/timer-registry.js.map +1 -1
  74. package/dist/types.d.ts +17 -6
  75. package/dist/types.d.ts.map +1 -1
  76. package/dist/types.js +1 -1
  77. package/dist/types.js.map +1 -1
  78. package/dist/worker/durable-step.d.ts +481 -0
  79. package/dist/worker/durable-step.d.ts.map +1 -0
  80. package/dist/worker/durable-step.js +606 -0
  81. package/dist/worker/durable-step.js.map +1 -0
  82. package/dist/worker/index.d.ts +106 -0
  83. package/dist/worker/index.d.ts.map +1 -0
  84. package/dist/worker/index.js +124 -0
  85. package/dist/worker/index.js.map +1 -0
  86. package/dist/worker/state-adapter.d.ts +230 -0
  87. package/dist/worker/state-adapter.d.ts.map +1 -0
  88. package/dist/worker/state-adapter.js +409 -0
  89. package/dist/worker/state-adapter.js.map +1 -0
  90. package/dist/worker/topological-executor.d.ts +282 -0
  91. package/dist/worker/topological-executor.d.ts.map +1 -0
  92. package/dist/worker/topological-executor.js +396 -0
  93. package/dist/worker/topological-executor.js.map +1 -0
  94. package/dist/worker/workflow-builder.d.ts +286 -0
  95. package/dist/worker/workflow-builder.d.ts.map +1 -0
  96. package/dist/worker/workflow-builder.js +565 -0
  97. package/dist/worker/workflow-builder.js.map +1 -0
  98. package/dist/worker.d.ts +800 -0
  99. package/dist/worker.d.ts.map +1 -0
  100. package/dist/worker.js +2428 -0
  101. package/dist/worker.js.map +1 -0
  102. package/dist/workflow-builder.d.ts +287 -0
  103. package/dist/workflow-builder.d.ts.map +1 -0
  104. package/dist/workflow-builder.js +762 -0
  105. package/dist/workflow-builder.js.map +1 -0
  106. package/dist/workflow.d.ts +14 -30
  107. package/dist/workflow.d.ts.map +1 -1
  108. package/dist/workflow.js +132 -292
  109. package/dist/workflow.js.map +1 -1
  110. package/examples/01-ecommerce-order-pipeline.ts +358 -0
  111. package/examples/02-content-moderation-cascade.ts +454 -0
  112. package/examples/03-scheduled-reporting-dependencies.ts +479 -0
  113. package/examples/04-database-persistence.ts +518 -0
  114. package/examples/README.md +173 -0
  115. package/package.json +30 -13
  116. package/src/__tests__/digital-objects-adapter.test.ts +274 -0
  117. package/src/__tests__/durable-workflow.test.ts +297 -0
  118. package/src/barrier.ts +48 -7
  119. package/src/cascade-context.ts +36 -29
  120. package/src/cascade-executor.ts +3 -2
  121. package/src/context.ts +41 -12
  122. package/src/cron-parser.ts +347 -0
  123. package/src/cron-scheduler.ts +239 -0
  124. package/src/database-context.ts +658 -0
  125. package/src/digital-objects-adapter.ts +351 -0
  126. package/src/durable-execution-cloudflare.ts +855 -0
  127. package/src/durable-execution.ts +1042 -0
  128. package/src/durable-workflow.ts +717 -0
  129. package/src/graph/topological-sort.ts +6 -8
  130. package/src/index.ts +69 -0
  131. package/src/logger.ts +148 -0
  132. package/src/on.ts +8 -9
  133. package/src/runtime.ts +436 -0
  134. package/src/send.ts +4 -5
  135. package/src/telemetry.ts +577 -0
  136. package/src/timer-registry.ts +44 -10
  137. package/src/types.ts +32 -17
  138. package/src/worker/durable-step.ts +976 -0
  139. package/src/worker/index.ts +216 -0
  140. package/src/worker/state-adapter.ts +589 -0
  141. package/src/worker/topological-executor.ts +625 -0
  142. package/src/worker/workflow-builder.ts +871 -0
  143. package/src/worker.ts +2906 -0
  144. package/src/workflow-builder.ts +1068 -0
  145. package/src/workflow.ts +188 -351
  146. package/test/barrier-join.test.ts +32 -24
  147. package/test/cascade-executor.test.ts +9 -16
  148. package/test/cron-parser.test.ts +314 -0
  149. package/test/cron-scheduler.test.ts +291 -0
  150. package/test/database-context.test.ts +770 -0
  151. package/test/db-provider-adapter.test.ts +862 -0
  152. package/test/durable-execution-cloudflare.test.ts +606 -0
  153. package/test/durable-execution-in-process.test.ts +286 -0
  154. package/test/durable-execution.test.ts +247 -0
  155. package/test/e2e/workflow-scenarios.e2e.test.ts +1039 -0
  156. package/test/integration.test.ts +442 -0
  157. package/test/rpc-surface.test.ts +946 -0
  158. package/test/runtime.test.ts +262 -0
  159. package/test/schedule-timer-cleanup.test.ts +30 -21
  160. package/test/send-race-conditions.test.ts +30 -40
  161. package/test/worker/durable-cascade.test.ts +1117 -0
  162. package/test/worker/durable-step.test.ts +723 -0
  163. package/test/worker/topological-executor.test.ts +1240 -0
  164. package/test/worker/workflow-builder.test.ts +1067 -0
  165. package/test/worker.test.ts +608 -0
  166. package/test/workflow-builder.test.ts +1670 -0
  167. package/test/workflow-cron.test.ts +256 -0
  168. package/test/workflow-state-adapter.test.ts +923 -0
  169. package/test/workflow.test.ts +25 -22
  170. package/tsconfig.json +3 -1
  171. package/vitest.config.ts +38 -1
  172. package/vitest.workers.config.ts +44 -0
  173. package/wrangler.jsonc +22 -0
  174. package/.turbo/turbo-test.log +0 -169
  175. package/LICENSE +0 -21
  176. package/src/context.js +0 -83
  177. package/src/every.js +0 -267
  178. package/src/index.js +0 -71
  179. package/src/on.js +0 -79
  180. package/src/send.js +0 -111
  181. package/src/types.js +0 -4
  182. package/src/workflow.js +0 -455
  183. package/test/context.test.js +0 -116
  184. package/test/every.test.js +0 -282
  185. package/test/on.test.js +0 -80
  186. package/test/send.test.js +0 -89
  187. package/test/workflow.test.js +0 -224
  188. 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
- options.signal.addEventListener('abort', () => {
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: this._arrived[this._arrived.length - 1],
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
- const abortHandler = () => {
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
- return await Promise.race(racers)
345
+ try {
346
+ return await Promise.race(racers)
347
+ } finally {
348
+ cleanup()
349
+ }
309
350
  }
310
351
 
311
352
  /**
@@ -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(16, 20)}-${traceId.slice(20, 32)}`
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) => createRestoredStep(s, name))
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
- metadata: serialized.metadata ? { ...serialized.metadata } : undefined,
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?.reason as string,
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: metadata ? { ...metadata } : undefined,
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?.reason as string,
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 = step.status === 'completed' ? '[OK]' : step.status === 'failed' ? '[FAIL]' : '[...]'
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 = step.status === 'completed' ? '[OK]' : step.status === 'failed' ? '[FAIL]' : '[...]'
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
  }
@@ -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 = lastError.message.includes('timed out') || lastError.name === 'TimeoutError'
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 { WorkflowContext, WorkflowState, WorkflowHistoryEntry, OnProxy, EveryProxy, EveryProxyTarget, ScheduleHandler } from './types.js'
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
- get() {
35
- return () => {}
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
- async send<T = unknown>(event: string, data: T): Promise<void> {
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 eventBus.emit(event, data)
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
- console.log(`[workflow] ${message}`, data ?? '')
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 & { getEmittedEvents: () => Array<{ event: string; data: unknown }> } {
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({