ai-workflows 2.1.3 → 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 (188) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +8 -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
@@ -0,0 +1,291 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
+ import {
3
+ createCronJob,
4
+ stopCronJob,
5
+ startCronJob,
6
+ getActiveCronJobs,
7
+ stopAllCronJobs,
8
+ getActiveCronJobCount,
9
+ schedule,
10
+ } from '../src/cron-scheduler.js'
11
+
12
+ describe('cron-scheduler', () => {
13
+ beforeEach(() => {
14
+ vi.useFakeTimers()
15
+ stopAllCronJobs() // Clean up any existing jobs
16
+ })
17
+
18
+ afterEach(() => {
19
+ stopAllCronJobs()
20
+ vi.useRealTimers()
21
+ })
22
+
23
+ describe('createCronJob', () => {
24
+ it('should create a cron job with valid expression', () => {
25
+ const handler = vi.fn()
26
+ const job = createCronJob('0 * * * *', handler)
27
+
28
+ expect(job).toBeDefined()
29
+ expect(job.expression).toBe('0 * * * *')
30
+ expect(job.stopped).toBe(false)
31
+ expect(job.running).toBe(false)
32
+ })
33
+
34
+ it('should allow custom job ID', () => {
35
+ const job = createCronJob('* * * * *', () => {}, { id: 'my-custom-job' })
36
+ expect(job.id).toBe('my-custom-job')
37
+ })
38
+
39
+ it('should add job to active jobs', () => {
40
+ const job = createCronJob('* * * * *', () => {})
41
+ const active = getActiveCronJobs()
42
+ expect(active).toContain(job)
43
+ })
44
+
45
+ it('should calculate next run time', () => {
46
+ const job = createCronJob('0 * * * *', () => {})
47
+ expect(job.nextRun).not.toBeNull()
48
+ expect(job.nextRun!.getMinutes()).toBe(0)
49
+ })
50
+
51
+ it('should execute handler on cron schedule', async () => {
52
+ const handler = vi.fn()
53
+ // Every minute
54
+ createCronJob('* * * * *', handler)
55
+
56
+ // Advance time to next minute
57
+ await vi.advanceTimersByTimeAsync(60 * 1000)
58
+
59
+ expect(handler).toHaveBeenCalledTimes(1)
60
+ })
61
+
62
+ it('should execute multiple times on recurring schedule', async () => {
63
+ const handler = vi.fn()
64
+ // Every minute
65
+ createCronJob('* * * * *', handler)
66
+
67
+ // Advance time by 3 minutes
68
+ await vi.advanceTimersByTimeAsync(60 * 1000)
69
+ await vi.advanceTimersByTimeAsync(60 * 1000)
70
+ await vi.advanceTimersByTimeAsync(60 * 1000)
71
+
72
+ expect(handler).toHaveBeenCalledTimes(3)
73
+ })
74
+
75
+ it('should handle async handlers', async () => {
76
+ const results: number[] = []
77
+ const handler = async () => {
78
+ await new Promise((resolve) => setTimeout(resolve, 10))
79
+ results.push(Date.now())
80
+ }
81
+
82
+ createCronJob('* * * * *', handler)
83
+
84
+ await vi.advanceTimersByTimeAsync(60 * 1000)
85
+ expect(results.length).toBeGreaterThanOrEqual(1)
86
+ })
87
+
88
+ it('should call error handler on error', async () => {
89
+ const error = new Error('Test error')
90
+ const handler = vi.fn(() => {
91
+ throw error
92
+ })
93
+ const errorHandler = vi.fn()
94
+
95
+ createCronJob('* * * * *', handler, { onError: errorHandler })
96
+
97
+ await vi.advanceTimersByTimeAsync(60 * 1000)
98
+
99
+ expect(handler).toHaveBeenCalled()
100
+ expect(errorHandler).toHaveBeenCalledWith(error)
101
+ })
102
+
103
+ it('should continue scheduling after error', async () => {
104
+ const handler = vi.fn(() => {
105
+ throw new Error('Test error')
106
+ })
107
+ const errorHandler = vi.fn()
108
+
109
+ createCronJob('* * * * *', handler, { onError: errorHandler })
110
+
111
+ // Advance 3 minutes
112
+ await vi.advanceTimersByTimeAsync(60 * 1000)
113
+ await vi.advanceTimersByTimeAsync(60 * 1000)
114
+ await vi.advanceTimersByTimeAsync(60 * 1000)
115
+
116
+ // Handler should have been called 3 times despite errors
117
+ expect(handler).toHaveBeenCalledTimes(3)
118
+ expect(errorHandler).toHaveBeenCalledTimes(3)
119
+ })
120
+
121
+ it('should not start if startImmediately is false', () => {
122
+ const handler = vi.fn()
123
+ const job = createCronJob('* * * * *', handler, { startImmediately: false })
124
+
125
+ expect(job.timer).toBeNull()
126
+ expect(job.nextRun).toBeNull()
127
+ })
128
+ })
129
+
130
+ describe('stopCronJob', () => {
131
+ it('should stop a cron job', () => {
132
+ const handler = vi.fn()
133
+ const job = createCronJob('* * * * *', handler)
134
+
135
+ stopCronJob(job)
136
+
137
+ expect(job.stopped).toBe(true)
138
+ expect(job.timer).toBeNull()
139
+ expect(job.nextRun).toBeNull()
140
+ })
141
+
142
+ it('should remove job from active jobs', () => {
143
+ const job = createCronJob('* * * * *', () => {})
144
+ expect(getActiveCronJobCount()).toBe(1)
145
+
146
+ stopCronJob(job)
147
+ expect(getActiveCronJobCount()).toBe(0)
148
+ })
149
+
150
+ it('should prevent future executions', async () => {
151
+ const handler = vi.fn()
152
+ const job = createCronJob('* * * * *', handler)
153
+
154
+ // Stop before first execution
155
+ stopCronJob(job)
156
+
157
+ await vi.advanceTimersByTimeAsync(60 * 1000)
158
+ expect(handler).not.toHaveBeenCalled()
159
+ })
160
+ })
161
+
162
+ describe('startCronJob', () => {
163
+ it('should start a stopped job', () => {
164
+ const handler = vi.fn()
165
+ const job = createCronJob('* * * * *', handler, { startImmediately: false })
166
+
167
+ expect(job.nextRun).toBeNull()
168
+
169
+ startCronJob(job)
170
+
171
+ expect(job.stopped).toBe(false)
172
+ expect(job.nextRun).not.toBeNull()
173
+ })
174
+
175
+ it('should resume execution after restart', async () => {
176
+ const handler = vi.fn()
177
+ const job = createCronJob('* * * * *', handler)
178
+
179
+ stopCronJob(job)
180
+ await vi.advanceTimersByTimeAsync(60 * 1000)
181
+ expect(handler).not.toHaveBeenCalled()
182
+
183
+ startCronJob(job)
184
+ await vi.advanceTimersByTimeAsync(60 * 1000)
185
+ expect(handler).toHaveBeenCalledTimes(1)
186
+ })
187
+ })
188
+
189
+ describe('getActiveCronJobs', () => {
190
+ it('should return all active jobs', () => {
191
+ const job1 = createCronJob('* * * * *', () => {})
192
+ const job2 = createCronJob('0 * * * *', () => {})
193
+
194
+ const active = getActiveCronJobs()
195
+ expect(active).toHaveLength(2)
196
+ expect(active).toContain(job1)
197
+ expect(active).toContain(job2)
198
+ })
199
+
200
+ it('should not include stopped jobs', () => {
201
+ const job1 = createCronJob('* * * * *', () => {})
202
+ const job2 = createCronJob('0 * * * *', () => {})
203
+
204
+ stopCronJob(job1)
205
+
206
+ const active = getActiveCronJobs()
207
+ expect(active).toHaveLength(1)
208
+ expect(active).not.toContain(job1)
209
+ expect(active).toContain(job2)
210
+ })
211
+ })
212
+
213
+ describe('stopAllCronJobs', () => {
214
+ it('should stop all active jobs', () => {
215
+ createCronJob('* * * * *', () => {})
216
+ createCronJob('0 * * * *', () => {})
217
+ createCronJob('0 0 * * *', () => {})
218
+
219
+ expect(getActiveCronJobCount()).toBe(3)
220
+
221
+ stopAllCronJobs()
222
+
223
+ expect(getActiveCronJobCount()).toBe(0)
224
+ })
225
+ })
226
+
227
+ describe('getActiveCronJobCount', () => {
228
+ it('should return count of active jobs', () => {
229
+ expect(getActiveCronJobCount()).toBe(0)
230
+
231
+ createCronJob('* * * * *', () => {})
232
+ expect(getActiveCronJobCount()).toBe(1)
233
+
234
+ createCronJob('0 * * * *', () => {})
235
+ expect(getActiveCronJobCount()).toBe(2)
236
+ })
237
+ })
238
+
239
+ describe('schedule', () => {
240
+ it('should be an alias for createCronJob', () => {
241
+ const handler = vi.fn()
242
+ const job = schedule('0 9 * * 1', handler)
243
+
244
+ expect(job).toBeDefined()
245
+ expect(job.expression).toBe('0 9 * * 1')
246
+ })
247
+
248
+ it('should support options', () => {
249
+ const errorHandler = vi.fn()
250
+ const job = schedule('* * * * *', () => {}, {
251
+ id: 'my-schedule',
252
+ onError: errorHandler,
253
+ })
254
+
255
+ expect(job.id).toBe('my-schedule')
256
+ expect(job.onError).toBe(errorHandler)
257
+ })
258
+ })
259
+
260
+ describe('complex cron expressions', () => {
261
+ it('should handle */5 * * * * (every 5 minutes)', async () => {
262
+ const handler = vi.fn()
263
+ // Set a specific time: 10:02
264
+ vi.setSystemTime(new Date('2024-01-15T10:02:00'))
265
+
266
+ createCronJob('*/5 * * * *', handler)
267
+
268
+ // Advance to 10:05 (next 5-minute mark)
269
+ await vi.advanceTimersByTimeAsync(3 * 60 * 1000)
270
+
271
+ expect(handler).toHaveBeenCalledTimes(1)
272
+
273
+ // Advance another 5 minutes to 10:10
274
+ await vi.advanceTimersByTimeAsync(5 * 60 * 1000)
275
+ expect(handler).toHaveBeenCalledTimes(2)
276
+ })
277
+
278
+ it('should handle 0 9 * * 1-5 (weekdays at 9am)', async () => {
279
+ const handler = vi.fn()
280
+ // Set to Monday 8am
281
+ vi.setSystemTime(new Date('2024-01-15T08:00:00'))
282
+
283
+ createCronJob('0 9 * * 1-5', handler)
284
+
285
+ // Advance 1 hour to 9am Monday
286
+ await vi.advanceTimersByTimeAsync(60 * 60 * 1000)
287
+
288
+ expect(handler).toHaveBeenCalledTimes(1)
289
+ })
290
+ })
291
+ })