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
|
@@ -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
|
+
})
|