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/test/every.test.js
DELETED
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { every, registerScheduleHandler, getScheduleHandlers, clearScheduleHandlers, toCron, intervalToMs, formatInterval, setCronConverter, } from '../src/every.js';
|
|
3
|
-
describe('every - schedule handler registration', () => {
|
|
4
|
-
beforeEach(() => {
|
|
5
|
-
clearScheduleHandlers();
|
|
6
|
-
});
|
|
7
|
-
describe('every.interval pattern', () => {
|
|
8
|
-
it('should register handler for every.hour', () => {
|
|
9
|
-
const handler = () => { };
|
|
10
|
-
every.hour(handler);
|
|
11
|
-
const handlers = getScheduleHandlers();
|
|
12
|
-
expect(handlers).toHaveLength(1);
|
|
13
|
-
expect(handlers[0]?.interval).toEqual({
|
|
14
|
-
type: 'cron',
|
|
15
|
-
expression: '0 * * * *',
|
|
16
|
-
natural: 'hour',
|
|
17
|
-
});
|
|
18
|
-
expect(handlers[0]?.source).toBeDefined();
|
|
19
|
-
});
|
|
20
|
-
it('should register handler for every.day', () => {
|
|
21
|
-
every.day(() => { });
|
|
22
|
-
const handlers = getScheduleHandlers();
|
|
23
|
-
expect(handlers[0]?.interval).toEqual({
|
|
24
|
-
type: 'cron',
|
|
25
|
-
expression: '0 0 * * *',
|
|
26
|
-
natural: 'day',
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
it('should register handler for every.minute', () => {
|
|
30
|
-
every.minute(() => { });
|
|
31
|
-
const handlers = getScheduleHandlers();
|
|
32
|
-
expect(handlers[0]?.interval).toEqual({
|
|
33
|
-
type: 'cron',
|
|
34
|
-
expression: '* * * * *',
|
|
35
|
-
natural: 'minute',
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
it('should register handler for every.week', () => {
|
|
39
|
-
every.week(() => { });
|
|
40
|
-
const handlers = getScheduleHandlers();
|
|
41
|
-
expect(handlers[0]?.interval).toEqual({
|
|
42
|
-
type: 'cron',
|
|
43
|
-
expression: '0 0 * * 0',
|
|
44
|
-
natural: 'week',
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
describe('every.Day pattern', () => {
|
|
49
|
-
it('should register handler for every.Monday', () => {
|
|
50
|
-
every.Monday(() => { });
|
|
51
|
-
const handlers = getScheduleHandlers();
|
|
52
|
-
expect(handlers[0]?.interval).toEqual({
|
|
53
|
-
type: 'cron',
|
|
54
|
-
expression: '0 0 * * 1',
|
|
55
|
-
natural: 'Monday',
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
it('should register handler for every.Friday', () => {
|
|
59
|
-
every.Friday(() => { });
|
|
60
|
-
const handlers = getScheduleHandlers();
|
|
61
|
-
expect(handlers[0]?.interval).toEqual({
|
|
62
|
-
type: 'cron',
|
|
63
|
-
expression: '0 0 * * 5',
|
|
64
|
-
natural: 'Friday',
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
it('should register handler for every.weekday', () => {
|
|
68
|
-
every.weekday(() => { });
|
|
69
|
-
const handlers = getScheduleHandlers();
|
|
70
|
-
expect(handlers[0]?.interval).toEqual({
|
|
71
|
-
type: 'cron',
|
|
72
|
-
expression: '0 0 * * 1-5',
|
|
73
|
-
natural: 'weekday',
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
it('should register handler for every.weekend', () => {
|
|
77
|
-
every.weekend(() => { });
|
|
78
|
-
const handlers = getScheduleHandlers();
|
|
79
|
-
expect(handlers[0]?.interval).toEqual({
|
|
80
|
-
type: 'cron',
|
|
81
|
-
expression: '0 0 * * 0,6',
|
|
82
|
-
natural: 'weekend',
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
describe('every.Day.atTime pattern', () => {
|
|
87
|
-
it('should register handler for every.Monday.at9am', () => {
|
|
88
|
-
every.Monday.at9am(() => { });
|
|
89
|
-
const handlers = getScheduleHandlers();
|
|
90
|
-
expect(handlers[0]?.interval).toEqual({
|
|
91
|
-
type: 'cron',
|
|
92
|
-
expression: '0 9 * * 1',
|
|
93
|
-
natural: 'Monday.at9am',
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
it('should register handler for every.Friday.at5pm', () => {
|
|
97
|
-
every.Friday.at5pm(() => { });
|
|
98
|
-
const handlers = getScheduleHandlers();
|
|
99
|
-
expect(handlers[0]?.interval).toEqual({
|
|
100
|
-
type: 'cron',
|
|
101
|
-
expression: '0 17 * * 5',
|
|
102
|
-
natural: 'Friday.at5pm',
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
it('should register handler for every.weekday.at8am', () => {
|
|
106
|
-
every.weekday.at8am(() => { });
|
|
107
|
-
const handlers = getScheduleHandlers();
|
|
108
|
-
expect(handlers[0]?.interval).toEqual({
|
|
109
|
-
type: 'cron',
|
|
110
|
-
expression: '0 8 * * 1-5',
|
|
111
|
-
natural: 'weekday.at8am',
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
it('should register handler for every.Thursday.atnoon', () => {
|
|
115
|
-
every.Thursday.atnoon(() => { });
|
|
116
|
-
const handlers = getScheduleHandlers();
|
|
117
|
-
expect(handlers[0]?.interval).toEqual({
|
|
118
|
-
type: 'cron',
|
|
119
|
-
expression: '0 12 * * 4',
|
|
120
|
-
natural: 'Thursday.atnoon',
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
it('should register handler for every.Sunday.atmidnight', () => {
|
|
124
|
-
every.Sunday.atmidnight(() => { });
|
|
125
|
-
const handlers = getScheduleHandlers();
|
|
126
|
-
expect(handlers[0]?.interval).toEqual({
|
|
127
|
-
type: 'cron',
|
|
128
|
-
expression: '0 0 * * 0',
|
|
129
|
-
natural: 'Sunday.atmidnight',
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
describe('every.units(value) pattern', () => {
|
|
134
|
-
it('should register handler for every.minutes(30)', () => {
|
|
135
|
-
every.minutes(30)(() => { });
|
|
136
|
-
const handlers = getScheduleHandlers();
|
|
137
|
-
expect(handlers[0]?.interval).toEqual({
|
|
138
|
-
type: 'minute',
|
|
139
|
-
value: 30,
|
|
140
|
-
natural: '30 minutes',
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
it('should register handler for every.hours(4)', () => {
|
|
144
|
-
every.hours(4)(() => { });
|
|
145
|
-
const handlers = getScheduleHandlers();
|
|
146
|
-
expect(handlers[0]?.interval).toEqual({
|
|
147
|
-
type: 'hour',
|
|
148
|
-
value: 4,
|
|
149
|
-
natural: '4 hours',
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
it('should register handler for every.seconds(10)', () => {
|
|
153
|
-
every.seconds(10)(() => { });
|
|
154
|
-
const handlers = getScheduleHandlers();
|
|
155
|
-
expect(handlers[0]?.interval).toEqual({
|
|
156
|
-
type: 'second',
|
|
157
|
-
value: 10,
|
|
158
|
-
natural: '10 seconds',
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
describe('every(natural language) pattern', () => {
|
|
163
|
-
it('should register handler with natural language description', () => {
|
|
164
|
-
every('first Monday of the month at 9am', () => { });
|
|
165
|
-
const handlers = getScheduleHandlers();
|
|
166
|
-
expect(handlers[0]?.interval).toEqual({
|
|
167
|
-
type: 'natural',
|
|
168
|
-
description: 'first Monday of the month at 9am',
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
it('should capture source code', () => {
|
|
172
|
-
every('weekly', async ($) => {
|
|
173
|
-
$.log('weekly task');
|
|
174
|
-
});
|
|
175
|
-
const handlers = getScheduleHandlers();
|
|
176
|
-
expect(handlers[0]?.source).toContain('$.log');
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
describe('registerScheduleHandler', () => {
|
|
180
|
-
it('should register handler directly', () => {
|
|
181
|
-
const handler = () => { };
|
|
182
|
-
registerScheduleHandler({ type: 'hour', value: 2 }, handler);
|
|
183
|
-
const handlers = getScheduleHandlers();
|
|
184
|
-
expect(handlers).toHaveLength(1);
|
|
185
|
-
expect(handlers[0]?.interval).toEqual({ type: 'hour', value: 2 });
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
describe('clearScheduleHandlers', () => {
|
|
189
|
-
it('should clear all handlers', () => {
|
|
190
|
-
every.hour(() => { });
|
|
191
|
-
every.day(() => { });
|
|
192
|
-
expect(getScheduleHandlers()).toHaveLength(2);
|
|
193
|
-
clearScheduleHandlers();
|
|
194
|
-
expect(getScheduleHandlers()).toHaveLength(0);
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
describe('toCron', () => {
|
|
199
|
-
it('should return known patterns directly', async () => {
|
|
200
|
-
expect(await toCron('hour')).toBe('0 * * * *');
|
|
201
|
-
expect(await toCron('day')).toBe('0 0 * * *');
|
|
202
|
-
expect(await toCron('Monday')).toBe('0 0 * * 1');
|
|
203
|
-
expect(await toCron('weekday')).toBe('0 0 * * 1-5');
|
|
204
|
-
});
|
|
205
|
-
it('should return cron expression if already valid', async () => {
|
|
206
|
-
expect(await toCron('0 9 * * 1-5')).toBe('0 9 * * 1-5');
|
|
207
|
-
expect(await toCron('*/15 * * * *')).toBe('*/15 * * * *');
|
|
208
|
-
});
|
|
209
|
-
it('should throw for unknown patterns without converter', async () => {
|
|
210
|
-
await expect(toCron('every 15 minutes during business hours')).rejects.toThrow('Unknown schedule pattern');
|
|
211
|
-
});
|
|
212
|
-
it('should use custom converter when set', async () => {
|
|
213
|
-
setCronConverter(async (desc) => {
|
|
214
|
-
if (desc.includes('business hours')) {
|
|
215
|
-
return '*/15 9-17 * * 1-5';
|
|
216
|
-
}
|
|
217
|
-
return '* * * * *';
|
|
218
|
-
});
|
|
219
|
-
expect(await toCron('every 15 minutes during business hours')).toBe('*/15 9-17 * * 1-5');
|
|
220
|
-
// Reset converter
|
|
221
|
-
setCronConverter(async () => '* * * * *');
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
describe('intervalToMs', () => {
|
|
225
|
-
it('should convert second intervals', () => {
|
|
226
|
-
expect(intervalToMs({ type: 'second' })).toBe(1000);
|
|
227
|
-
expect(intervalToMs({ type: 'second', value: 5 })).toBe(5000);
|
|
228
|
-
});
|
|
229
|
-
it('should convert minute intervals', () => {
|
|
230
|
-
expect(intervalToMs({ type: 'minute' })).toBe(60000);
|
|
231
|
-
expect(intervalToMs({ type: 'minute', value: 30 })).toBe(1800000);
|
|
232
|
-
});
|
|
233
|
-
it('should convert hour intervals', () => {
|
|
234
|
-
expect(intervalToMs({ type: 'hour' })).toBe(3600000);
|
|
235
|
-
expect(intervalToMs({ type: 'hour', value: 4 })).toBe(14400000);
|
|
236
|
-
});
|
|
237
|
-
it('should convert day intervals', () => {
|
|
238
|
-
expect(intervalToMs({ type: 'day' })).toBe(86400000);
|
|
239
|
-
expect(intervalToMs({ type: 'day', value: 2 })).toBe(172800000);
|
|
240
|
-
});
|
|
241
|
-
it('should convert week intervals', () => {
|
|
242
|
-
expect(intervalToMs({ type: 'week' })).toBe(604800000);
|
|
243
|
-
expect(intervalToMs({ type: 'week', value: 2 })).toBe(1209600000);
|
|
244
|
-
});
|
|
245
|
-
it('should return 0 for cron intervals', () => {
|
|
246
|
-
expect(intervalToMs({ type: 'cron', expression: '0 * * * *' })).toBe(0);
|
|
247
|
-
});
|
|
248
|
-
it('should return 0 for natural intervals', () => {
|
|
249
|
-
expect(intervalToMs({ type: 'natural', description: 'every hour' })).toBe(0);
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
describe('formatInterval', () => {
|
|
253
|
-
it('should format second intervals', () => {
|
|
254
|
-
expect(formatInterval({ type: 'second' })).toBe('every second');
|
|
255
|
-
expect(formatInterval({ type: 'second', value: 5 })).toBe('every 5 seconds');
|
|
256
|
-
});
|
|
257
|
-
it('should format minute intervals', () => {
|
|
258
|
-
expect(formatInterval({ type: 'minute' })).toBe('every minute');
|
|
259
|
-
expect(formatInterval({ type: 'minute', value: 30 })).toBe('every 30 minutes');
|
|
260
|
-
});
|
|
261
|
-
it('should format hour intervals', () => {
|
|
262
|
-
expect(formatInterval({ type: 'hour' })).toBe('every hour');
|
|
263
|
-
expect(formatInterval({ type: 'hour', value: 4 })).toBe('every 4 hours');
|
|
264
|
-
});
|
|
265
|
-
it('should format day intervals', () => {
|
|
266
|
-
expect(formatInterval({ type: 'day' })).toBe('every day');
|
|
267
|
-
expect(formatInterval({ type: 'day', value: 2 })).toBe('every 2 days');
|
|
268
|
-
});
|
|
269
|
-
it('should format week intervals', () => {
|
|
270
|
-
expect(formatInterval({ type: 'week' })).toBe('every week');
|
|
271
|
-
expect(formatInterval({ type: 'week', value: 2 })).toBe('every 2 weeks');
|
|
272
|
-
});
|
|
273
|
-
it('should format cron intervals', () => {
|
|
274
|
-
expect(formatInterval({ type: 'cron', expression: '0 9 * * 1' })).toBe('cron: 0 9 * * 1');
|
|
275
|
-
});
|
|
276
|
-
it('should format natural intervals', () => {
|
|
277
|
-
expect(formatInterval({ type: 'natural', description: 'every hour during business hours' })).toBe('every hour during business hours');
|
|
278
|
-
});
|
|
279
|
-
it('should use natural description when available', () => {
|
|
280
|
-
expect(formatInterval({ type: 'cron', expression: '0 9 * * 1', natural: 'Monday.at9am' })).toBe('Monday.at9am');
|
|
281
|
-
});
|
|
282
|
-
});
|
package/test/on.test.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { on, registerEventHandler, getEventHandlers, clearEventHandlers } from '../src/on.js';
|
|
3
|
-
describe('on - event handler registration', () => {
|
|
4
|
-
beforeEach(() => {
|
|
5
|
-
clearEventHandlers();
|
|
6
|
-
});
|
|
7
|
-
describe('on.Noun.event pattern', () => {
|
|
8
|
-
it('should register handler for Customer.created', () => {
|
|
9
|
-
const handler = () => { };
|
|
10
|
-
on.Customer.created(handler);
|
|
11
|
-
const handlers = getEventHandlers();
|
|
12
|
-
expect(handlers).toHaveLength(1);
|
|
13
|
-
expect(handlers[0]?.noun).toBe('Customer');
|
|
14
|
-
expect(handlers[0]?.event).toBe('created');
|
|
15
|
-
expect(handlers[0]?.handler).toBe(handler);
|
|
16
|
-
expect(handlers[0]?.source).toBeDefined();
|
|
17
|
-
});
|
|
18
|
-
it('should register handler for Order.completed', () => {
|
|
19
|
-
const handler = () => { };
|
|
20
|
-
on.Order.completed(handler);
|
|
21
|
-
const handlers = getEventHandlers();
|
|
22
|
-
expect(handlers).toHaveLength(1);
|
|
23
|
-
expect(handlers[0]?.noun).toBe('Order');
|
|
24
|
-
expect(handlers[0]?.event).toBe('completed');
|
|
25
|
-
});
|
|
26
|
-
it('should register multiple handlers', () => {
|
|
27
|
-
on.Customer.created(() => { });
|
|
28
|
-
on.Customer.updated(() => { });
|
|
29
|
-
on.Order.completed(() => { });
|
|
30
|
-
const handlers = getEventHandlers();
|
|
31
|
-
expect(handlers).toHaveLength(3);
|
|
32
|
-
});
|
|
33
|
-
it('should register multiple handlers for same event', () => {
|
|
34
|
-
on.Customer.created(() => { });
|
|
35
|
-
on.Customer.created(() => { });
|
|
36
|
-
const handlers = getEventHandlers();
|
|
37
|
-
expect(handlers).toHaveLength(2);
|
|
38
|
-
expect(handlers[0]?.noun).toBe('Customer');
|
|
39
|
-
expect(handlers[0]?.event).toBe('created');
|
|
40
|
-
expect(handlers[1]?.noun).toBe('Customer');
|
|
41
|
-
expect(handlers[1]?.event).toBe('created');
|
|
42
|
-
});
|
|
43
|
-
it('should capture function source code', () => {
|
|
44
|
-
on.Test.event(async (data, $) => {
|
|
45
|
-
$.log('test', data);
|
|
46
|
-
});
|
|
47
|
-
const handlers = getEventHandlers();
|
|
48
|
-
expect(handlers[0]?.source).toContain('$.log');
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
describe('registerEventHandler', () => {
|
|
52
|
-
it('should register handler directly', () => {
|
|
53
|
-
const handler = () => { };
|
|
54
|
-
registerEventHandler('Payment', 'failed', handler);
|
|
55
|
-
const handlers = getEventHandlers();
|
|
56
|
-
expect(handlers).toHaveLength(1);
|
|
57
|
-
expect(handlers[0]?.noun).toBe('Payment');
|
|
58
|
-
expect(handlers[0]?.event).toBe('failed');
|
|
59
|
-
expect(handlers[0]?.handler).toBe(handler);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
describe('clearEventHandlers', () => {
|
|
63
|
-
it('should clear all handlers', () => {
|
|
64
|
-
on.Customer.created(() => { });
|
|
65
|
-
on.Order.completed(() => { });
|
|
66
|
-
expect(getEventHandlers()).toHaveLength(2);
|
|
67
|
-
clearEventHandlers();
|
|
68
|
-
expect(getEventHandlers()).toHaveLength(0);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
describe('getEventHandlers', () => {
|
|
72
|
-
it('should return a copy of handlers array', () => {
|
|
73
|
-
on.Customer.created(() => { });
|
|
74
|
-
const handlers1 = getEventHandlers();
|
|
75
|
-
const handlers2 = getEventHandlers();
|
|
76
|
-
expect(handlers1).not.toBe(handlers2);
|
|
77
|
-
expect(handlers1).toEqual(handlers2);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
});
|
package/test/send.test.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { send, parseEvent } from '../src/send.js';
|
|
3
|
-
import { on, clearEventHandlers } from '../src/on.js';
|
|
4
|
-
describe('send - event emission', () => {
|
|
5
|
-
beforeEach(() => {
|
|
6
|
-
clearEventHandlers();
|
|
7
|
-
});
|
|
8
|
-
describe('parseEvent', () => {
|
|
9
|
-
it('should parse valid event strings', () => {
|
|
10
|
-
expect(parseEvent('Customer.created')).toEqual({
|
|
11
|
-
noun: 'Customer',
|
|
12
|
-
event: 'created',
|
|
13
|
-
});
|
|
14
|
-
expect(parseEvent('Order.completed')).toEqual({
|
|
15
|
-
noun: 'Order',
|
|
16
|
-
event: 'completed',
|
|
17
|
-
});
|
|
18
|
-
expect(parseEvent('Payment.failed')).toEqual({
|
|
19
|
-
noun: 'Payment',
|
|
20
|
-
event: 'failed',
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
it('should return null for invalid event strings', () => {
|
|
24
|
-
expect(parseEvent('invalid')).toBeNull();
|
|
25
|
-
expect(parseEvent('too.many.parts')).toBeNull();
|
|
26
|
-
expect(parseEvent('')).toBeNull();
|
|
27
|
-
expect(parseEvent('.')).toBeNull();
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
describe('send', () => {
|
|
31
|
-
it('should emit event to registered handler', async () => {
|
|
32
|
-
const handler = vi.fn();
|
|
33
|
-
on.Customer.created(handler);
|
|
34
|
-
await send('Customer.created', { id: '123', name: 'John' });
|
|
35
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
36
|
-
expect(handler).toHaveBeenCalledWith({ id: '123', name: 'John' }, expect.objectContaining({
|
|
37
|
-
send: expect.any(Function),
|
|
38
|
-
getState: expect.any(Function),
|
|
39
|
-
set: expect.any(Function),
|
|
40
|
-
get: expect.any(Function),
|
|
41
|
-
log: expect.any(Function),
|
|
42
|
-
}));
|
|
43
|
-
});
|
|
44
|
-
it('should emit event to multiple handlers', async () => {
|
|
45
|
-
const handler1 = vi.fn();
|
|
46
|
-
const handler2 = vi.fn();
|
|
47
|
-
on.Customer.created(handler1);
|
|
48
|
-
on.Customer.created(handler2);
|
|
49
|
-
await send('Customer.created', { id: '123' });
|
|
50
|
-
expect(handler1).toHaveBeenCalledTimes(1);
|
|
51
|
-
expect(handler2).toHaveBeenCalledTimes(1);
|
|
52
|
-
});
|
|
53
|
-
it('should not throw when no handlers are registered', async () => {
|
|
54
|
-
await expect(send('Customer.created', { id: '123' })).resolves.not.toThrow();
|
|
55
|
-
});
|
|
56
|
-
it('should not call handlers for different events', async () => {
|
|
57
|
-
const handler = vi.fn();
|
|
58
|
-
on.Customer.updated(handler);
|
|
59
|
-
await send('Customer.created', { id: '123' });
|
|
60
|
-
expect(handler).not.toHaveBeenCalled();
|
|
61
|
-
});
|
|
62
|
-
it('should not call handlers for different nouns', async () => {
|
|
63
|
-
const handler = vi.fn();
|
|
64
|
-
on.Order.created(handler);
|
|
65
|
-
await send('Customer.created', { id: '123' });
|
|
66
|
-
expect(handler).not.toHaveBeenCalled();
|
|
67
|
-
});
|
|
68
|
-
it('should handle async handlers', async () => {
|
|
69
|
-
let completed = false;
|
|
70
|
-
on.Customer.created(async () => {
|
|
71
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
72
|
-
completed = true;
|
|
73
|
-
});
|
|
74
|
-
await send('Customer.created', { id: '123' });
|
|
75
|
-
expect(completed).toBe(true);
|
|
76
|
-
});
|
|
77
|
-
it('should continue with other handlers if one throws', async () => {
|
|
78
|
-
const handler1 = vi.fn().mockRejectedValue(new Error('Handler 1 failed'));
|
|
79
|
-
const handler2 = vi.fn();
|
|
80
|
-
on.Customer.created(handler1);
|
|
81
|
-
on.Customer.created(handler2);
|
|
82
|
-
// Should not throw
|
|
83
|
-
await expect(send('Customer.created', { id: '123' })).resolves.not.toThrow();
|
|
84
|
-
// Both handlers should be called
|
|
85
|
-
expect(handler1).toHaveBeenCalledTimes(1);
|
|
86
|
-
expect(handler2).toHaveBeenCalledTimes(1);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
});
|
package/test/workflow.test.js
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
2
|
-
import { Workflow, createTestContext, parseEvent } from '../src/workflow.js';
|
|
3
|
-
import { clearEventHandlers } from '../src/on.js';
|
|
4
|
-
import { clearScheduleHandlers } from '../src/every.js';
|
|
5
|
-
describe('Workflow - unified $ API', () => {
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
clearEventHandlers();
|
|
8
|
-
clearScheduleHandlers();
|
|
9
|
-
vi.useFakeTimers();
|
|
10
|
-
});
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
vi.useRealTimers();
|
|
13
|
-
});
|
|
14
|
-
describe('Workflow()', () => {
|
|
15
|
-
it('should create a workflow with $ context', () => {
|
|
16
|
-
const workflow = Workflow($ => {
|
|
17
|
-
// Just accessing $ to verify it works
|
|
18
|
-
expect($).toBeDefined();
|
|
19
|
-
expect($.on).toBeDefined();
|
|
20
|
-
expect($.every).toBeDefined();
|
|
21
|
-
expect($.send).toBeDefined();
|
|
22
|
-
expect($.log).toBeDefined();
|
|
23
|
-
expect($.get).toBeDefined();
|
|
24
|
-
expect($.set).toBeDefined();
|
|
25
|
-
expect($.getState).toBeDefined();
|
|
26
|
-
});
|
|
27
|
-
expect(workflow).toBeDefined();
|
|
28
|
-
expect(workflow.$).toBeDefined();
|
|
29
|
-
expect(workflow.send).toBeDefined();
|
|
30
|
-
expect(workflow.start).toBeDefined();
|
|
31
|
-
expect(workflow.stop).toBeDefined();
|
|
32
|
-
});
|
|
33
|
-
it('should capture event handlers registered via $.on', () => {
|
|
34
|
-
const workflow = Workflow($ => {
|
|
35
|
-
$.on.Customer.created(() => { });
|
|
36
|
-
$.on.Order.completed(() => { });
|
|
37
|
-
});
|
|
38
|
-
expect(workflow.definition.events).toHaveLength(2);
|
|
39
|
-
expect(workflow.definition.events[0]?.noun).toBe('Customer');
|
|
40
|
-
expect(workflow.definition.events[0]?.event).toBe('created');
|
|
41
|
-
expect(workflow.definition.events[1]?.noun).toBe('Order');
|
|
42
|
-
expect(workflow.definition.events[1]?.event).toBe('completed');
|
|
43
|
-
});
|
|
44
|
-
it('should capture schedule handlers registered via $.every', () => {
|
|
45
|
-
const workflow = Workflow($ => {
|
|
46
|
-
$.every.hour(() => { });
|
|
47
|
-
$.every.Monday.at9am(() => { });
|
|
48
|
-
});
|
|
49
|
-
expect(workflow.definition.schedules).toHaveLength(2);
|
|
50
|
-
});
|
|
51
|
-
it('should capture function source code', () => {
|
|
52
|
-
const workflow = Workflow($ => {
|
|
53
|
-
$.on.Test.event(async (data, ctx) => {
|
|
54
|
-
ctx.log('Test event', data);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
// Source code is captured (variable names may be minified)
|
|
58
|
-
expect(workflow.definition.events[0]?.source).toBeDefined();
|
|
59
|
-
expect(workflow.definition.events[0]?.source.length).toBeGreaterThan(0);
|
|
60
|
-
expect(workflow.definition.events[0]?.source).toContain('Test event');
|
|
61
|
-
});
|
|
62
|
-
it('should deliver events to registered handlers', async () => {
|
|
63
|
-
const handler = vi.fn();
|
|
64
|
-
const workflow = Workflow($ => {
|
|
65
|
-
$.on.Customer.created(handler);
|
|
66
|
-
});
|
|
67
|
-
await workflow.start();
|
|
68
|
-
await workflow.send('Customer.created', { id: '123', name: 'John' });
|
|
69
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
70
|
-
expect(handler).toHaveBeenCalledWith({ id: '123', name: 'John' }, expect.objectContaining({
|
|
71
|
-
send: expect.any(Function),
|
|
72
|
-
log: expect.any(Function),
|
|
73
|
-
}));
|
|
74
|
-
});
|
|
75
|
-
it('should allow chained event sending from handlers', async () => {
|
|
76
|
-
const welcomeHandler = vi.fn();
|
|
77
|
-
const workflow = Workflow($ => {
|
|
78
|
-
$.on.Customer.created(async (customer, $) => {
|
|
79
|
-
await $.send('Email.welcome', { to: customer.email });
|
|
80
|
-
});
|
|
81
|
-
$.on.Email.welcome(welcomeHandler);
|
|
82
|
-
});
|
|
83
|
-
await workflow.start();
|
|
84
|
-
await workflow.send('Customer.created', { name: 'John', email: 'john@example.com' });
|
|
85
|
-
expect(welcomeHandler).toHaveBeenCalledWith({ to: 'john@example.com' }, expect.anything());
|
|
86
|
-
});
|
|
87
|
-
it('should track events in state history', async () => {
|
|
88
|
-
const workflow = Workflow($ => {
|
|
89
|
-
$.on.Test.event(() => { });
|
|
90
|
-
});
|
|
91
|
-
await workflow.start();
|
|
92
|
-
await workflow.send('Test.event', { data: 'test' });
|
|
93
|
-
expect(workflow.state.history).toHaveLength(1);
|
|
94
|
-
expect(workflow.state.history[0]).toMatchObject({
|
|
95
|
-
type: 'event',
|
|
96
|
-
name: 'Test.event',
|
|
97
|
-
data: { data: 'test' },
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
it('should trigger schedule handlers', async () => {
|
|
101
|
-
const handler = vi.fn();
|
|
102
|
-
const workflow = Workflow($ => {
|
|
103
|
-
$.every.seconds(1)(handler);
|
|
104
|
-
});
|
|
105
|
-
await workflow.start();
|
|
106
|
-
await vi.advanceTimersByTimeAsync(1000);
|
|
107
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
108
|
-
await vi.advanceTimersByTimeAsync(1000);
|
|
109
|
-
expect(handler).toHaveBeenCalledTimes(2);
|
|
110
|
-
await workflow.stop();
|
|
111
|
-
});
|
|
112
|
-
it('should stop schedule handlers on stop', async () => {
|
|
113
|
-
const handler = vi.fn();
|
|
114
|
-
const workflow = Workflow($ => {
|
|
115
|
-
$.every.seconds(1)(handler);
|
|
116
|
-
});
|
|
117
|
-
await workflow.start();
|
|
118
|
-
await vi.advanceTimersByTimeAsync(1000);
|
|
119
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
120
|
-
await workflow.stop();
|
|
121
|
-
await vi.advanceTimersByTimeAsync(5000);
|
|
122
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
123
|
-
});
|
|
124
|
-
it('should support $.set and $.get for context data', async () => {
|
|
125
|
-
const workflow = Workflow($ => {
|
|
126
|
-
$.on.Test.set(async (data, $) => {
|
|
127
|
-
$.set('value', data.value);
|
|
128
|
-
});
|
|
129
|
-
$.on.Test.get(async (_, $) => {
|
|
130
|
-
const value = $.get('value');
|
|
131
|
-
$.log('Got value', value);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
await workflow.start();
|
|
135
|
-
await workflow.send('Test.set', { value: 42 });
|
|
136
|
-
expect(workflow.state.context.value).toBe(42);
|
|
137
|
-
expect(workflow.$.get('value')).toBe(42);
|
|
138
|
-
});
|
|
139
|
-
it('should use initial context from options', () => {
|
|
140
|
-
const workflow = Workflow($ => { }, { context: { counter: 100 } });
|
|
141
|
-
expect(workflow.state.context.counter).toBe(100);
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
describe('parseEvent', () => {
|
|
145
|
-
it('should parse valid event strings', () => {
|
|
146
|
-
expect(parseEvent('Customer.created')).toEqual({
|
|
147
|
-
noun: 'Customer',
|
|
148
|
-
event: 'created',
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
it('should return null for invalid event strings', () => {
|
|
152
|
-
expect(parseEvent('invalid')).toBeNull();
|
|
153
|
-
expect(parseEvent('too.many.parts')).toBeNull();
|
|
154
|
-
expect(parseEvent('')).toBeNull();
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
describe('createTestContext', () => {
|
|
158
|
-
it('should create a $ context for testing', () => {
|
|
159
|
-
const $ = createTestContext();
|
|
160
|
-
expect($.send).toBeDefined();
|
|
161
|
-
expect($.on).toBeDefined();
|
|
162
|
-
expect($.every).toBeDefined();
|
|
163
|
-
expect($.log).toBeDefined();
|
|
164
|
-
expect($.get).toBeDefined();
|
|
165
|
-
expect($.set).toBeDefined();
|
|
166
|
-
expect($.getState).toBeDefined();
|
|
167
|
-
expect($.emittedEvents).toBeDefined();
|
|
168
|
-
});
|
|
169
|
-
it('should track emitted events', async () => {
|
|
170
|
-
const $ = createTestContext();
|
|
171
|
-
await $.send('Test.event1', { a: 1 });
|
|
172
|
-
await $.send('Test.event2', { b: 2 });
|
|
173
|
-
expect($.emittedEvents).toHaveLength(2);
|
|
174
|
-
expect($.emittedEvents[0]).toEqual({ event: 'Test.event1', data: { a: 1 } });
|
|
175
|
-
expect($.emittedEvents[1]).toEqual({ event: 'Test.event2', data: { b: 2 } });
|
|
176
|
-
});
|
|
177
|
-
it('should support get/set', () => {
|
|
178
|
-
const $ = createTestContext();
|
|
179
|
-
$.set('key', 'value');
|
|
180
|
-
expect($.get('key')).toBe('value');
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
describe('$.every patterns', () => {
|
|
184
|
-
it('should support $.every.hour', () => {
|
|
185
|
-
const workflow = Workflow($ => {
|
|
186
|
-
$.every.hour(() => { });
|
|
187
|
-
});
|
|
188
|
-
expect(workflow.definition.schedules[0]?.interval).toEqual({
|
|
189
|
-
type: 'cron',
|
|
190
|
-
expression: '0 * * * *',
|
|
191
|
-
natural: 'hour',
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
it('should support $.every.Monday.at9am', () => {
|
|
195
|
-
const workflow = Workflow($ => {
|
|
196
|
-
$.every.Monday.at9am(() => { });
|
|
197
|
-
});
|
|
198
|
-
expect(workflow.definition.schedules[0]?.interval).toEqual({
|
|
199
|
-
type: 'cron',
|
|
200
|
-
expression: '0 9 * * 1',
|
|
201
|
-
natural: 'Monday.at9am',
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
it('should support $.every.minutes(30)', () => {
|
|
205
|
-
const workflow = Workflow($ => {
|
|
206
|
-
$.every.minutes(30)(() => { });
|
|
207
|
-
});
|
|
208
|
-
expect(workflow.definition.schedules[0]?.interval).toEqual({
|
|
209
|
-
type: 'minute',
|
|
210
|
-
value: 30,
|
|
211
|
-
natural: '30 minutes',
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
it('should support $.every("natural language")', () => {
|
|
215
|
-
const workflow = Workflow($ => {
|
|
216
|
-
$.every('first Monday of the month', () => { });
|
|
217
|
-
});
|
|
218
|
-
expect(workflow.definition.schedules[0]?.interval).toEqual({
|
|
219
|
-
type: 'natural',
|
|
220
|
-
description: 'first Monday of the month',
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
});
|