ai-workflows 2.1.1 → 2.3.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 (211) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +17 -1
  3. package/README.md +305 -184
  4. package/dist/barrier.d.ts +159 -0
  5. package/dist/barrier.d.ts.map +1 -0
  6. package/dist/barrier.js +377 -0
  7. package/dist/barrier.js.map +1 -0
  8. package/dist/cascade-context.d.ts +149 -0
  9. package/dist/cascade-context.d.ts.map +1 -0
  10. package/dist/cascade-context.js +324 -0
  11. package/dist/cascade-context.js.map +1 -0
  12. package/dist/cascade-executor.d.ts +196 -0
  13. package/dist/cascade-executor.d.ts.map +1 -0
  14. package/dist/cascade-executor.js +384 -0
  15. package/dist/cascade-executor.js.map +1 -0
  16. package/dist/context.d.ts.map +1 -1
  17. package/dist/context.js +27 -8
  18. package/dist/context.js.map +1 -1
  19. package/dist/cron-parser.d.ts +65 -0
  20. package/dist/cron-parser.d.ts.map +1 -0
  21. package/dist/cron-parser.js +294 -0
  22. package/dist/cron-parser.js.map +1 -0
  23. package/dist/cron-scheduler.d.ts +117 -0
  24. package/dist/cron-scheduler.d.ts.map +1 -0
  25. package/dist/cron-scheduler.js +176 -0
  26. package/dist/cron-scheduler.js.map +1 -0
  27. package/dist/database-context.d.ts +184 -0
  28. package/dist/database-context.d.ts.map +1 -0
  29. package/dist/database-context.js +428 -0
  30. package/dist/database-context.js.map +1 -0
  31. package/dist/dependency-graph.d.ts +157 -0
  32. package/dist/dependency-graph.d.ts.map +1 -0
  33. package/dist/dependency-graph.js +382 -0
  34. package/dist/dependency-graph.js.map +1 -0
  35. package/dist/digital-objects-adapter.d.ts +159 -0
  36. package/dist/digital-objects-adapter.d.ts.map +1 -0
  37. package/dist/digital-objects-adapter.js +229 -0
  38. package/dist/digital-objects-adapter.js.map +1 -0
  39. package/dist/durable-execution-cloudflare.d.ts +427 -0
  40. package/dist/durable-execution-cloudflare.d.ts.map +1 -0
  41. package/dist/durable-execution-cloudflare.js +510 -0
  42. package/dist/durable-execution-cloudflare.js.map +1 -0
  43. package/dist/durable-execution.d.ts +482 -0
  44. package/dist/durable-execution.d.ts.map +1 -0
  45. package/dist/durable-execution.js +594 -0
  46. package/dist/durable-execution.js.map +1 -0
  47. package/dist/durable-workflow.d.ts +176 -0
  48. package/dist/durable-workflow.d.ts.map +1 -0
  49. package/dist/durable-workflow.js +552 -0
  50. package/dist/durable-workflow.js.map +1 -0
  51. package/dist/every.d.ts +31 -2
  52. package/dist/every.d.ts.map +1 -1
  53. package/dist/every.js +63 -32
  54. package/dist/every.js.map +1 -1
  55. package/dist/graph/index.d.ts +8 -0
  56. package/dist/graph/index.d.ts.map +1 -0
  57. package/dist/graph/index.js +8 -0
  58. package/dist/graph/index.js.map +1 -0
  59. package/dist/graph/topological-sort.d.ts +121 -0
  60. package/dist/graph/topological-sort.d.ts.map +1 -0
  61. package/dist/graph/topological-sort.js +292 -0
  62. package/dist/graph/topological-sort.js.map +1 -0
  63. package/dist/index.d.ts +10 -1
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +25 -0
  66. package/dist/index.js.map +1 -1
  67. package/dist/logger.d.ts +101 -0
  68. package/dist/logger.d.ts.map +1 -0
  69. package/dist/logger.js +115 -0
  70. package/dist/logger.js.map +1 -0
  71. package/dist/on.d.ts +35 -10
  72. package/dist/on.d.ts.map +1 -1
  73. package/dist/on.js +53 -19
  74. package/dist/on.js.map +1 -1
  75. package/dist/runtime.d.ts +169 -0
  76. package/dist/runtime.d.ts.map +1 -0
  77. package/dist/runtime.js +275 -0
  78. package/dist/runtime.js.map +1 -0
  79. package/dist/send.d.ts.map +1 -1
  80. package/dist/send.js +4 -3
  81. package/dist/send.js.map +1 -1
  82. package/dist/telemetry.d.ts +150 -0
  83. package/dist/telemetry.d.ts.map +1 -0
  84. package/dist/telemetry.js +388 -0
  85. package/dist/telemetry.js.map +1 -0
  86. package/dist/timer-registry.d.ts +77 -0
  87. package/dist/timer-registry.d.ts.map +1 -0
  88. package/dist/timer-registry.js +154 -0
  89. package/dist/timer-registry.js.map +1 -0
  90. package/dist/types.d.ts +105 -6
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/types.js +17 -1
  93. package/dist/types.js.map +1 -1
  94. package/dist/worker/durable-step.d.ts +481 -0
  95. package/dist/worker/durable-step.d.ts.map +1 -0
  96. package/dist/worker/durable-step.js +606 -0
  97. package/dist/worker/durable-step.js.map +1 -0
  98. package/dist/worker/index.d.ts +106 -0
  99. package/dist/worker/index.d.ts.map +1 -0
  100. package/dist/worker/index.js +124 -0
  101. package/dist/worker/index.js.map +1 -0
  102. package/dist/worker/state-adapter.d.ts +230 -0
  103. package/dist/worker/state-adapter.d.ts.map +1 -0
  104. package/dist/worker/state-adapter.js +409 -0
  105. package/dist/worker/state-adapter.js.map +1 -0
  106. package/dist/worker/topological-executor.d.ts +282 -0
  107. package/dist/worker/topological-executor.d.ts.map +1 -0
  108. package/dist/worker/topological-executor.js +396 -0
  109. package/dist/worker/topological-executor.js.map +1 -0
  110. package/dist/worker/workflow-builder.d.ts +286 -0
  111. package/dist/worker/workflow-builder.d.ts.map +1 -0
  112. package/dist/worker/workflow-builder.js +565 -0
  113. package/dist/worker/workflow-builder.js.map +1 -0
  114. package/dist/worker.d.ts +800 -0
  115. package/dist/worker.d.ts.map +1 -0
  116. package/dist/worker.js +2428 -0
  117. package/dist/worker.js.map +1 -0
  118. package/dist/workflow-builder.d.ts +287 -0
  119. package/dist/workflow-builder.d.ts.map +1 -0
  120. package/dist/workflow-builder.js +762 -0
  121. package/dist/workflow-builder.js.map +1 -0
  122. package/dist/workflow.d.ts +14 -30
  123. package/dist/workflow.d.ts.map +1 -1
  124. package/dist/workflow.js +136 -292
  125. package/dist/workflow.js.map +1 -1
  126. package/examples/01-ecommerce-order-pipeline.ts +358 -0
  127. package/examples/02-content-moderation-cascade.ts +454 -0
  128. package/examples/03-scheduled-reporting-dependencies.ts +479 -0
  129. package/examples/04-database-persistence.ts +518 -0
  130. package/examples/README.md +173 -0
  131. package/package.json +21 -4
  132. package/src/__tests__/digital-objects-adapter.test.ts +274 -0
  133. package/src/__tests__/durable-workflow.test.ts +297 -0
  134. package/src/barrier.ts +507 -0
  135. package/src/cascade-context.ts +495 -0
  136. package/src/cascade-executor.ts +588 -0
  137. package/src/context.ts +51 -17
  138. package/src/cron-parser.ts +347 -0
  139. package/src/cron-scheduler.ts +239 -0
  140. package/src/database-context.ts +658 -0
  141. package/src/dependency-graph.ts +518 -0
  142. package/src/digital-objects-adapter.ts +351 -0
  143. package/src/durable-execution-cloudflare.ts +855 -0
  144. package/src/durable-execution.ts +1042 -0
  145. package/src/durable-workflow.ts +717 -0
  146. package/src/every.ts +104 -35
  147. package/src/graph/index.ts +19 -0
  148. package/src/graph/topological-sort.ts +412 -0
  149. package/src/index.ts +147 -0
  150. package/src/logger.ts +148 -0
  151. package/src/on.ts +81 -26
  152. package/src/runtime.ts +436 -0
  153. package/src/send.ts +4 -5
  154. package/src/telemetry.ts +577 -0
  155. package/src/timer-registry.ts +179 -0
  156. package/src/types.ts +146 -10
  157. package/src/worker/durable-step.ts +976 -0
  158. package/src/worker/index.ts +216 -0
  159. package/src/worker/state-adapter.ts +589 -0
  160. package/src/worker/topological-executor.ts +625 -0
  161. package/src/worker/workflow-builder.ts +871 -0
  162. package/src/worker.ts +2906 -0
  163. package/src/workflow-builder.ts +1068 -0
  164. package/src/workflow.ts +199 -355
  165. package/test/barrier-join.test.ts +442 -0
  166. package/test/barrier-unhandled-rejections.test.ts +359 -0
  167. package/test/cascade-context.test.ts +390 -0
  168. package/test/cascade-executor.test.ts +852 -0
  169. package/test/cron-parser.test.ts +314 -0
  170. package/test/cron-scheduler.test.ts +291 -0
  171. package/test/database-context.test.ts +770 -0
  172. package/test/db-provider-adapter.test.ts +862 -0
  173. package/test/dependency-graph.test.ts +512 -0
  174. package/test/durable-execution-cloudflare.test.ts +606 -0
  175. package/test/durable-execution-in-process.test.ts +286 -0
  176. package/test/durable-execution.test.ts +247 -0
  177. package/test/e2e/workflow-scenarios.e2e.test.ts +1039 -0
  178. package/test/graph/topological-sort.test.ts +586 -0
  179. package/test/integration.test.ts +442 -0
  180. package/test/rpc-surface.test.ts +946 -0
  181. package/test/runtime.test.ts +262 -0
  182. package/test/schedule-timer-cleanup.test.ts +353 -0
  183. package/test/send-race-conditions.test.ts +400 -0
  184. package/test/type-safety-every.test.ts +303 -0
  185. package/test/worker/durable-cascade.test.ts +1117 -0
  186. package/test/worker/durable-step.test.ts +723 -0
  187. package/test/worker/topological-executor.test.ts +1240 -0
  188. package/test/worker/workflow-builder.test.ts +1067 -0
  189. package/test/worker.test.ts +608 -0
  190. package/test/workflow-builder.test.ts +1670 -0
  191. package/test/workflow-cron.test.ts +256 -0
  192. package/test/workflow-state-adapter.test.ts +923 -0
  193. package/test/workflow.test.ts +25 -22
  194. package/tsconfig.json +3 -1
  195. package/vitest.config.ts +38 -1
  196. package/vitest.workers.config.ts +44 -0
  197. package/wrangler.jsonc +22 -0
  198. package/.turbo/turbo-test.log +0 -7
  199. package/src/context.js +0 -83
  200. package/src/every.js +0 -267
  201. package/src/index.js +0 -71
  202. package/src/on.js +0 -79
  203. package/src/send.js +0 -111
  204. package/src/types.js +0 -4
  205. package/src/workflow.js +0 -455
  206. package/test/context.test.js +0 -116
  207. package/test/every.test.js +0 -282
  208. package/test/on.test.js +0 -80
  209. package/test/send.test.js +0 -89
  210. package/test/workflow.test.js +0 -224
  211. package/vitest.config.js +0 -7
@@ -0,0 +1,442 @@
1
+ /**
2
+ * Integration tests for ai-workflows package
3
+ *
4
+ * Tests the workflow, event handling, scheduling, and coordination primitives.
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest'
8
+ import {
9
+ Workflow,
10
+ createTestContext,
11
+ parseEvent,
12
+ on,
13
+ every,
14
+ send,
15
+ getEventBus,
16
+ registerEventHandler,
17
+ getEventHandlers,
18
+ clearEventHandlers,
19
+ registerScheduleHandler,
20
+ getScheduleHandlers,
21
+ clearScheduleHandlers,
22
+ createWorkflowContext,
23
+ createIsolatedContext,
24
+ createCascadeContext,
25
+ recordStep,
26
+ DependencyGraph,
27
+ CircularDependencyError,
28
+ MissingDependencyError,
29
+ topologicalSort,
30
+ getExecutionLevels,
31
+ Barrier,
32
+ createBarrier,
33
+ waitForAll,
34
+ waitForAny,
35
+ withConcurrencyLimit,
36
+ CascadeExecutor,
37
+ workflow,
38
+ WorkflowBuilder,
39
+ WorkflowStateAdapter,
40
+ type WorkflowInstance,
41
+ type CascadeContext,
42
+ } from '../src/index.js'
43
+
44
+ // Skip tests if no gateway configured
45
+ const hasGateway = !!process.env.AI_GATEWAY_URL || !!process.env.ANTHROPIC_API_KEY
46
+
47
+ describe('ai-workflows exports', () => {
48
+ it('should export Workflow', () => {
49
+ expect(Workflow).toBeDefined()
50
+ expect(typeof Workflow).toBe('function')
51
+ })
52
+
53
+ it('should export event handling functions', () => {
54
+ expect(on).toBeDefined()
55
+ expect(registerEventHandler).toBeDefined()
56
+ expect(getEventHandlers).toBeDefined()
57
+ expect(clearEventHandlers).toBeDefined()
58
+ })
59
+
60
+ it('should export scheduling functions', () => {
61
+ expect(every).toBeDefined()
62
+ expect(registerScheduleHandler).toBeDefined()
63
+ expect(getScheduleHandlers).toBeDefined()
64
+ expect(clearScheduleHandlers).toBeDefined()
65
+ })
66
+
67
+ it('should export event emission', () => {
68
+ expect(send).toBeDefined()
69
+ expect(getEventBus).toBeDefined()
70
+ })
71
+
72
+ it('should export context utilities', () => {
73
+ expect(createWorkflowContext).toBeDefined()
74
+ expect(createIsolatedContext).toBeDefined()
75
+ expect(createTestContext).toBeDefined()
76
+ })
77
+
78
+ it('should export cascade context', () => {
79
+ expect(createCascadeContext).toBeDefined()
80
+ expect(recordStep).toBeDefined()
81
+ })
82
+
83
+ it('should export dependency graph', () => {
84
+ expect(DependencyGraph).toBeDefined()
85
+ expect(CircularDependencyError).toBeDefined()
86
+ expect(MissingDependencyError).toBeDefined()
87
+ })
88
+
89
+ it('should export topological sort', () => {
90
+ expect(topologicalSort).toBeDefined()
91
+ expect(getExecutionLevels).toBeDefined()
92
+ })
93
+
94
+ it('should export barrier utilities', () => {
95
+ expect(Barrier).toBeDefined()
96
+ expect(createBarrier).toBeDefined()
97
+ expect(waitForAll).toBeDefined()
98
+ expect(waitForAny).toBeDefined()
99
+ expect(withConcurrencyLimit).toBeDefined()
100
+ })
101
+
102
+ it('should export cascade executor', () => {
103
+ expect(CascadeExecutor).toBeDefined()
104
+ })
105
+
106
+ it('should export workflow builder', () => {
107
+ expect(workflow).toBeDefined()
108
+ expect(WorkflowBuilder).toBeDefined()
109
+ })
110
+
111
+ it('should export state adapter', () => {
112
+ expect(WorkflowStateAdapter).toBeDefined()
113
+ })
114
+ })
115
+
116
+ describe('Workflow creation', () => {
117
+ let workflowInstance: WorkflowInstance
118
+
119
+ afterEach(async () => {
120
+ if (workflowInstance) {
121
+ await workflowInstance.destroy()
122
+ }
123
+ })
124
+
125
+ it('should create a basic workflow', () => {
126
+ workflowInstance = Workflow(($) => {
127
+ // Empty workflow
128
+ })
129
+
130
+ expect(workflowInstance).toBeDefined()
131
+ expect(workflowInstance.definition).toBeDefined()
132
+ expect(workflowInstance.state).toBeDefined()
133
+ expect(workflowInstance.$).toBeDefined()
134
+ })
135
+
136
+ it('should register event handlers', () => {
137
+ workflowInstance = Workflow(($) => {
138
+ $.on.Customer.created(async (data, $) => {
139
+ // handler
140
+ })
141
+
142
+ $.on.Order.completed(async (data, $) => {
143
+ // handler
144
+ })
145
+ })
146
+
147
+ expect(workflowInstance.definition.events.length).toBe(2)
148
+ })
149
+ })
150
+
151
+ describe('Event parsing', () => {
152
+ it('should parse valid event strings', () => {
153
+ const result = parseEvent('Customer.created')
154
+ expect(result).toEqual({ noun: 'Customer', event: 'created' })
155
+ })
156
+
157
+ it('should parse different event names', () => {
158
+ expect(parseEvent('Order.completed')).toEqual({ noun: 'Order', event: 'completed' })
159
+ expect(parseEvent('User.updated')).toEqual({ noun: 'User', event: 'updated' })
160
+ expect(parseEvent('Payment.failed')).toEqual({ noun: 'Payment', event: 'failed' })
161
+ })
162
+
163
+ it('should return null for invalid event strings', () => {
164
+ expect(parseEvent('invalid')).toBeNull()
165
+ expect(parseEvent('')).toBeNull()
166
+ expect(parseEvent('too.many.parts')).toBeNull()
167
+ })
168
+ })
169
+
170
+ describe('DependencyGraph', () => {
171
+ it('should add nodes', () => {
172
+ const graph = new DependencyGraph()
173
+
174
+ graph.addNode('a')
175
+ graph.addNode('b')
176
+ graph.addNode('c')
177
+
178
+ expect(graph.hasNode('a')).toBe(true)
179
+ expect(graph.hasNode('b')).toBe(true)
180
+ expect(graph.hasNode('c')).toBe(true)
181
+ expect(graph.hasNode('d')).toBe(false)
182
+ })
183
+
184
+ it('should add edges (dependencies)', () => {
185
+ const graph = new DependencyGraph()
186
+
187
+ graph.addNode('a')
188
+ graph.addNode('b')
189
+ graph.addEdge('a', 'b') // b depends on a
190
+
191
+ expect(graph.getDependencies('b')).toContain('a')
192
+ expect(graph.getDependents('a')).toContain('b')
193
+ })
194
+
195
+ it('should detect circular dependencies', () => {
196
+ const graph = new DependencyGraph()
197
+
198
+ graph.addNode('a')
199
+ graph.addNode('b')
200
+ graph.addNode('c')
201
+
202
+ graph.addEdge('a', 'b')
203
+ graph.addEdge('b', 'c')
204
+
205
+ expect(() => graph.addEdge('c', 'a')).toThrow(CircularDependencyError)
206
+ })
207
+
208
+ it('should get parallel execution groups', () => {
209
+ const graph = new DependencyGraph()
210
+
211
+ graph.addNode('a')
212
+ graph.addNode('b')
213
+ graph.addNode('c')
214
+ graph.addNode('d')
215
+
216
+ graph.addEdge('a', 'c')
217
+ graph.addEdge('b', 'c')
218
+ graph.addEdge('c', 'd')
219
+
220
+ const groups = graph.getParallelGroups()
221
+
222
+ // First group (level 0): a, b (no dependencies)
223
+ expect(groups.length).toBeGreaterThanOrEqual(2)
224
+ expect(groups[0].nodes).toContain('a')
225
+ expect(groups[0].nodes).toContain('b')
226
+ expect(groups[0].level).toBe(0)
227
+ })
228
+ })
229
+
230
+ describe('Topological Sort', () => {
231
+ it('should sort nodes in dependency order', () => {
232
+ const nodes = [
233
+ { id: 'a', dependencies: [] },
234
+ { id: 'b', dependencies: [] },
235
+ { id: 'c', dependencies: ['a', 'b'] },
236
+ { id: 'd', dependencies: ['c'] },
237
+ ]
238
+
239
+ const result = topologicalSort(nodes)
240
+ const sorted = result.order
241
+
242
+ expect(sorted.indexOf('a')).toBeLessThan(sorted.indexOf('c'))
243
+ expect(sorted.indexOf('b')).toBeLessThan(sorted.indexOf('c'))
244
+ expect(sorted.indexOf('c')).toBeLessThan(sorted.indexOf('d'))
245
+ expect(result.hasCycle).toBe(false)
246
+ })
247
+
248
+ it('should get execution levels', () => {
249
+ const nodes = [
250
+ { id: 'a', dependencies: [] },
251
+ { id: 'b', dependencies: [] },
252
+ { id: 'c', dependencies: ['a', 'b'] },
253
+ { id: 'd', dependencies: ['c'] },
254
+ ]
255
+
256
+ const levels = getExecutionLevels(nodes)
257
+
258
+ // Level 0: a, b
259
+ // Level 1: c
260
+ // Level 2: d
261
+ expect(levels.length).toBe(3)
262
+ expect(levels[0].nodes).toContain('a')
263
+ expect(levels[0].nodes).toContain('b')
264
+ expect(levels[1].nodes).toContain('c')
265
+ expect(levels[2].nodes).toContain('d')
266
+ })
267
+ })
268
+
269
+ describe('Barrier', () => {
270
+ it('should wait for all promises', async () => {
271
+ const results = await waitForAll([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)])
272
+
273
+ expect(results).toEqual([1, 2, 3])
274
+ })
275
+
276
+ it('should limit concurrency', async () => {
277
+ let concurrent = 0
278
+ let maxConcurrent = 0
279
+
280
+ const tasks = Array.from({ length: 10 }, (_, i) => async () => {
281
+ concurrent++
282
+ maxConcurrent = Math.max(maxConcurrent, concurrent)
283
+ await new Promise((resolve) => setTimeout(resolve, 50))
284
+ concurrent--
285
+ return i
286
+ })
287
+
288
+ const results = await withConcurrencyLimit(tasks, 3)
289
+
290
+ expect(results.length).toBe(10)
291
+ expect(maxConcurrent).toBeLessThanOrEqual(3)
292
+ })
293
+ })
294
+
295
+ describe('Cascade Context', () => {
296
+ it('should create cascade context with correlation ID', () => {
297
+ const ctx = createCascadeContext({
298
+ correlationId: 'corr-123',
299
+ })
300
+
301
+ expect(ctx).toBeDefined()
302
+ expect(ctx.correlationId).toBe('corr-123')
303
+ })
304
+
305
+ it('should record steps in cascade context', () => {
306
+ const ctx = createCascadeContext({
307
+ correlationId: 'corr-456',
308
+ })
309
+
310
+ // recordStep takes (ctx, name, metadata?) and returns a step
311
+ const step1 = recordStep(ctx, 'step1')
312
+ step1.complete()
313
+
314
+ const step2 = recordStep(ctx, 'step2')
315
+ step2.complete()
316
+
317
+ expect(ctx.steps.length).toBe(2)
318
+ expect(ctx.steps[0].name).toBe('step1')
319
+ expect(ctx.steps[1].name).toBe('step2')
320
+ expect(ctx.steps[0].status).toBe('completed')
321
+ })
322
+ })
323
+
324
+ describe('CascadeExecutor', () => {
325
+ it('should execute code tier first', async () => {
326
+ const executor = new CascadeExecutor({
327
+ tiers: {
328
+ code: {
329
+ name: 'code-handler',
330
+ execute: async () => 'code result',
331
+ },
332
+ },
333
+ })
334
+
335
+ const result = await executor.execute('test input')
336
+
337
+ // Result doesn't have 'success' - it returns value on success or throws
338
+ expect(result.tier).toBe('code')
339
+ expect(result.value).toBe('code result')
340
+ expect(result.history).toBeDefined()
341
+ })
342
+
343
+ it('should cascade to next tier on failure', async () => {
344
+ const executor = new CascadeExecutor({
345
+ tiers: {
346
+ code: {
347
+ name: 'code-handler',
348
+ execute: async () => {
349
+ throw new Error('Code failed')
350
+ },
351
+ },
352
+ generative: {
353
+ name: 'generative-handler',
354
+ execute: async () => 'generative result',
355
+ },
356
+ },
357
+ timeouts: {
358
+ code: 100,
359
+ generative: 5000,
360
+ },
361
+ })
362
+
363
+ const result = await executor.execute('test input')
364
+
365
+ expect(result.tier).toBe('generative')
366
+ expect(result.value).toBe('generative result')
367
+ })
368
+
369
+ it('should track execution history', async () => {
370
+ const executor = new CascadeExecutor({
371
+ tiers: {
372
+ code: {
373
+ name: 'code-handler',
374
+ execute: async () => {
375
+ throw new Error('Code failed')
376
+ },
377
+ },
378
+ generative: {
379
+ name: 'generative-handler',
380
+ execute: async () => 'ok',
381
+ },
382
+ },
383
+ })
384
+
385
+ const result = await executor.execute('test')
386
+
387
+ expect(result.history.length).toBeGreaterThan(0)
388
+ expect(result.tier).toBe('generative')
389
+ })
390
+ })
391
+
392
+ describe('WorkflowBuilder DSL', () => {
393
+ it('should create workflow with builder', () => {
394
+ const wf = workflow('test-workflow')
395
+ .step('step1', async () => ({ value: 1 }))
396
+ .step('step2', async () => ({ value: 2 }))
397
+ .build()
398
+
399
+ expect(wf).toBeDefined()
400
+ expect(wf.name).toBe('test-workflow')
401
+ expect(wf.steps.length).toBe(2)
402
+ })
403
+
404
+ it('should support conditional steps', () => {
405
+ const wf = workflow('conditional-workflow')
406
+ .step('check', async () => ({ shouldContinue: true }))
407
+ .when((ctx) => ctx.result?.check?.shouldContinue === true)
408
+ .then(workflow('continue-flow').step('continue', async () => ({ continued: true })))
409
+ .build()
410
+
411
+ expect(wf).toBeDefined()
412
+ expect(wf.steps.length).toBe(2)
413
+ expect(wf.steps[1].type).toBe('conditional')
414
+ })
415
+
416
+ it('should support timeout configuration', () => {
417
+ const wf = workflow('timeout-workflow')
418
+ .step('slow', async () => ({ done: true }))
419
+ .timeout(5000)
420
+ .build()
421
+
422
+ expect(wf).toBeDefined()
423
+ })
424
+ })
425
+
426
+ describe('WorkflowStateAdapter exports', () => {
427
+ it('should export WorkflowStateAdapter class', () => {
428
+ // WorkflowStateAdapter requires a database connection
429
+ expect(WorkflowStateAdapter).toBeDefined()
430
+ expect(typeof WorkflowStateAdapter).toBe('function')
431
+ })
432
+ })
433
+
434
+ describe('Error handling', () => {
435
+ it('should handle missing dependencies gracefully', () => {
436
+ const graph = new DependencyGraph()
437
+
438
+ graph.addNode('a')
439
+
440
+ expect(() => graph.addEdge('a', 'nonexistent')).toThrow(MissingDependencyError)
441
+ })
442
+ })