ai-workflows 2.0.1 → 2.1.1
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 +4 -5
- package/.turbo/turbo-test.log +7 -0
- package/CHANGELOG.md +22 -0
- package/dist/send.d.ts +0 -5
- package/dist/send.d.ts.map +1 -1
- package/dist/send.js +1 -14
- package/dist/send.js.map +1 -1
- package/dist/types.d.ts +83 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +7 -7
- package/dist/workflow.js.map +1 -1
- package/package.json +2 -6
- package/src/context.js +83 -0
- package/src/every.js +267 -0
- package/src/index.js +71 -0
- package/src/on.js +79 -0
- package/src/send.js +111 -0
- package/src/send.ts +1 -16
- package/src/types.js +4 -0
- package/src/types.ts +97 -11
- package/src/workflow.js +455 -0
- package/src/workflow.ts +9 -7
- package/test/context.test.js +116 -0
- package/test/every.test.js +282 -0
- package/test/on.test.js +80 -0
- package/test/send.test.js +89 -0
- package/test/types-event-handler.test.ts +225 -0
- package/test/types-proxy-autocomplete.test.ts +345 -0
- package/test/workflow.test.js +224 -0
- package/vitest.config.js +7 -0
package/src/every.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schedule registration using natural language
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* every.hour($ => { ... })
|
|
6
|
+
* every.Thursday.at8am($ => { ... })
|
|
7
|
+
* every.weekday.at9am($ => { ... })
|
|
8
|
+
* every('hour during business hours', $ => { ... })
|
|
9
|
+
* every('first Monday of the month at 9am', $ => { ... })
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Registry of schedule handlers
|
|
13
|
+
*/
|
|
14
|
+
const scheduleRegistry = [];
|
|
15
|
+
/**
|
|
16
|
+
* Get all registered schedule handlers
|
|
17
|
+
*/
|
|
18
|
+
export function getScheduleHandlers() {
|
|
19
|
+
return [...scheduleRegistry];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Clear all registered schedule handlers
|
|
23
|
+
*/
|
|
24
|
+
export function clearScheduleHandlers() {
|
|
25
|
+
scheduleRegistry.length = 0;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Register a schedule handler directly
|
|
29
|
+
*/
|
|
30
|
+
export function registerScheduleHandler(interval, handler) {
|
|
31
|
+
scheduleRegistry.push({
|
|
32
|
+
interval,
|
|
33
|
+
handler,
|
|
34
|
+
source: handler.toString(),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Well-known cron patterns for common schedules
|
|
39
|
+
*/
|
|
40
|
+
const KNOWN_PATTERNS = {
|
|
41
|
+
// Time units
|
|
42
|
+
'second': '* * * * * *',
|
|
43
|
+
'minute': '* * * * *',
|
|
44
|
+
'hour': '0 * * * *',
|
|
45
|
+
'day': '0 0 * * *',
|
|
46
|
+
'week': '0 0 * * 0',
|
|
47
|
+
'month': '0 0 1 * *',
|
|
48
|
+
'year': '0 0 1 1 *',
|
|
49
|
+
// Days of week
|
|
50
|
+
'Monday': '0 0 * * 1',
|
|
51
|
+
'Tuesday': '0 0 * * 2',
|
|
52
|
+
'Wednesday': '0 0 * * 3',
|
|
53
|
+
'Thursday': '0 0 * * 4',
|
|
54
|
+
'Friday': '0 0 * * 5',
|
|
55
|
+
'Saturday': '0 0 * * 6',
|
|
56
|
+
'Sunday': '0 0 * * 0',
|
|
57
|
+
// Common patterns
|
|
58
|
+
'weekday': '0 0 * * 1-5',
|
|
59
|
+
'weekend': '0 0 * * 0,6',
|
|
60
|
+
'midnight': '0 0 * * *',
|
|
61
|
+
'noon': '0 12 * * *',
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Time suffixes for day-based schedules
|
|
65
|
+
*/
|
|
66
|
+
const TIME_PATTERNS = {
|
|
67
|
+
'at6am': { hour: 6, minute: 0 },
|
|
68
|
+
'at7am': { hour: 7, minute: 0 },
|
|
69
|
+
'at8am': { hour: 8, minute: 0 },
|
|
70
|
+
'at9am': { hour: 9, minute: 0 },
|
|
71
|
+
'at10am': { hour: 10, minute: 0 },
|
|
72
|
+
'at11am': { hour: 11, minute: 0 },
|
|
73
|
+
'at12pm': { hour: 12, minute: 0 },
|
|
74
|
+
'atnoon': { hour: 12, minute: 0 },
|
|
75
|
+
'at1pm': { hour: 13, minute: 0 },
|
|
76
|
+
'at2pm': { hour: 14, minute: 0 },
|
|
77
|
+
'at3pm': { hour: 15, minute: 0 },
|
|
78
|
+
'at4pm': { hour: 16, minute: 0 },
|
|
79
|
+
'at5pm': { hour: 17, minute: 0 },
|
|
80
|
+
'at6pm': { hour: 18, minute: 0 },
|
|
81
|
+
'at7pm': { hour: 19, minute: 0 },
|
|
82
|
+
'at8pm': { hour: 20, minute: 0 },
|
|
83
|
+
'at9pm': { hour: 21, minute: 0 },
|
|
84
|
+
'atmidnight': { hour: 0, minute: 0 },
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Parse a known pattern or return null
|
|
88
|
+
*/
|
|
89
|
+
function parseKnownPattern(pattern) {
|
|
90
|
+
return KNOWN_PATTERNS[pattern] ?? null;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Combine a day pattern with a time pattern
|
|
94
|
+
*/
|
|
95
|
+
function combineWithTime(baseCron, time) {
|
|
96
|
+
const parts = baseCron.split(' ');
|
|
97
|
+
parts[0] = String(time.minute);
|
|
98
|
+
parts[1] = String(time.hour);
|
|
99
|
+
return parts.join(' ');
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* AI-powered cron conversion (placeholder - will use ai-functions)
|
|
103
|
+
*/
|
|
104
|
+
let cronConverter = null;
|
|
105
|
+
/**
|
|
106
|
+
* Set the AI cron converter function
|
|
107
|
+
*/
|
|
108
|
+
export function setCronConverter(converter) {
|
|
109
|
+
cronConverter = converter;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Convert natural language to cron expression
|
|
113
|
+
*/
|
|
114
|
+
export async function toCron(description) {
|
|
115
|
+
// First check known patterns
|
|
116
|
+
const known = parseKnownPattern(description);
|
|
117
|
+
if (known)
|
|
118
|
+
return known;
|
|
119
|
+
// If we have an AI converter, use it
|
|
120
|
+
if (cronConverter) {
|
|
121
|
+
return cronConverter(description);
|
|
122
|
+
}
|
|
123
|
+
// Otherwise, assume it's already a cron expression or throw
|
|
124
|
+
if (/^[\d\*\-\/\,\s]+$/.test(description)) {
|
|
125
|
+
return description;
|
|
126
|
+
}
|
|
127
|
+
throw new Error(`Unknown schedule pattern: "${description}". ` +
|
|
128
|
+
`Set up AI conversion with setCronConverter() for natural language schedules.`);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Create the `every` proxy
|
|
132
|
+
*/
|
|
133
|
+
function createEveryProxy() {
|
|
134
|
+
const handler = {
|
|
135
|
+
get(_target, prop) {
|
|
136
|
+
// Check if it's a known pattern
|
|
137
|
+
const pattern = KNOWN_PATTERNS[prop];
|
|
138
|
+
if (pattern) {
|
|
139
|
+
// Return an object that can either be called directly or have time accessors
|
|
140
|
+
const result = (handlerFn) => {
|
|
141
|
+
registerScheduleHandler({ type: 'cron', expression: pattern, natural: prop }, handlerFn);
|
|
142
|
+
};
|
|
143
|
+
// Add time accessors
|
|
144
|
+
return new Proxy(result, {
|
|
145
|
+
get(_t, timeKey) {
|
|
146
|
+
const time = TIME_PATTERNS[timeKey];
|
|
147
|
+
if (time) {
|
|
148
|
+
const cron = combineWithTime(pattern, time);
|
|
149
|
+
return (handlerFn) => {
|
|
150
|
+
registerScheduleHandler({ type: 'cron', expression: cron, natural: `${prop}.${timeKey}` }, handlerFn);
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
},
|
|
155
|
+
apply(_t, _thisArg, args) {
|
|
156
|
+
registerScheduleHandler({ type: 'cron', expression: pattern, natural: prop }, args[0]);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
// Check for plural time units (e.g., seconds(5), minutes(30))
|
|
161
|
+
const pluralUnits = {
|
|
162
|
+
seconds: 'second',
|
|
163
|
+
minutes: 'minute',
|
|
164
|
+
hours: 'hour',
|
|
165
|
+
days: 'day',
|
|
166
|
+
weeks: 'week',
|
|
167
|
+
};
|
|
168
|
+
if (pluralUnits[prop]) {
|
|
169
|
+
return (value) => (handlerFn) => {
|
|
170
|
+
registerScheduleHandler({ type: pluralUnits[prop], value, natural: `${value} ${prop}` }, handlerFn);
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
},
|
|
175
|
+
apply(_target, _thisArg, args) {
|
|
176
|
+
// Called as every('natural language description', handler)
|
|
177
|
+
const [description, handler] = args;
|
|
178
|
+
if (typeof description === 'string' && typeof handler === 'function') {
|
|
179
|
+
// Register with natural language - will be converted to cron at runtime
|
|
180
|
+
registerScheduleHandler({ type: 'natural', description }, handler);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
return new Proxy(function () { }, handler);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The `every` function/object for registering scheduled handlers
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```ts
|
|
191
|
+
* import { every } from 'ai-workflows'
|
|
192
|
+
*
|
|
193
|
+
* // Simple intervals
|
|
194
|
+
* every.hour($ => $.log('Hourly task'))
|
|
195
|
+
* every.day($ => $.log('Daily task'))
|
|
196
|
+
*
|
|
197
|
+
* // Day + time combinations
|
|
198
|
+
* every.Monday.at9am($ => $.log('Monday morning standup'))
|
|
199
|
+
* every.weekday.at8am($ => $.log('Workday start'))
|
|
200
|
+
* every.Friday.at5pm($ => $.log('End of week report'))
|
|
201
|
+
*
|
|
202
|
+
* // Plural intervals with values
|
|
203
|
+
* every.minutes(30)($ => $.log('Every 30 minutes'))
|
|
204
|
+
* every.hours(4)($ => $.log('Every 4 hours'))
|
|
205
|
+
*
|
|
206
|
+
* // Natural language (requires AI converter)
|
|
207
|
+
* every('hour during business hours', $ => { ... })
|
|
208
|
+
* every('first Monday of the month at 9am', $ => { ... })
|
|
209
|
+
* every('every 15 minutes between 9am and 5pm on weekdays', $ => { ... })
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
export const every = createEveryProxy();
|
|
213
|
+
/**
|
|
214
|
+
* Convert interval to milliseconds (for simulation/testing)
|
|
215
|
+
*/
|
|
216
|
+
export function intervalToMs(interval) {
|
|
217
|
+
switch (interval.type) {
|
|
218
|
+
case 'second':
|
|
219
|
+
return (interval.value ?? 1) * 1000;
|
|
220
|
+
case 'minute':
|
|
221
|
+
return (interval.value ?? 1) * 60 * 1000;
|
|
222
|
+
case 'hour':
|
|
223
|
+
return (interval.value ?? 1) * 60 * 60 * 1000;
|
|
224
|
+
case 'day':
|
|
225
|
+
return (interval.value ?? 1) * 24 * 60 * 60 * 1000;
|
|
226
|
+
case 'week':
|
|
227
|
+
return (interval.value ?? 1) * 7 * 24 * 60 * 60 * 1000;
|
|
228
|
+
case 'cron':
|
|
229
|
+
case 'natural':
|
|
230
|
+
// Cron/natural expressions need special handling
|
|
231
|
+
return 0;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Format interval for display
|
|
236
|
+
*/
|
|
237
|
+
export function formatInterval(interval) {
|
|
238
|
+
if ('natural' in interval && interval.natural) {
|
|
239
|
+
return interval.natural;
|
|
240
|
+
}
|
|
241
|
+
switch (interval.type) {
|
|
242
|
+
case 'second':
|
|
243
|
+
return interval.value && interval.value > 1
|
|
244
|
+
? `every ${interval.value} seconds`
|
|
245
|
+
: 'every second';
|
|
246
|
+
case 'minute':
|
|
247
|
+
return interval.value && interval.value > 1
|
|
248
|
+
? `every ${interval.value} minutes`
|
|
249
|
+
: 'every minute';
|
|
250
|
+
case 'hour':
|
|
251
|
+
return interval.value && interval.value > 1
|
|
252
|
+
? `every ${interval.value} hours`
|
|
253
|
+
: 'every hour';
|
|
254
|
+
case 'day':
|
|
255
|
+
return interval.value && interval.value > 1
|
|
256
|
+
? `every ${interval.value} days`
|
|
257
|
+
: 'every day';
|
|
258
|
+
case 'week':
|
|
259
|
+
return interval.value && interval.value > 1
|
|
260
|
+
? `every ${interval.value} weeks`
|
|
261
|
+
: 'every week';
|
|
262
|
+
case 'cron':
|
|
263
|
+
return `cron: ${interval.expression}`;
|
|
264
|
+
case 'natural':
|
|
265
|
+
return interval.description;
|
|
266
|
+
}
|
|
267
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai-workflows - Event-driven workflows with $ context
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { Workflow } from 'ai-workflows'
|
|
7
|
+
*
|
|
8
|
+
* // Create a workflow with $ context
|
|
9
|
+
* const workflow = Workflow($ => {
|
|
10
|
+
* // Register event handlers
|
|
11
|
+
* $.on.Customer.created(async (customer, $) => {
|
|
12
|
+
* $.log('New customer:', customer.name)
|
|
13
|
+
* await $.send('Email.welcome', { to: customer.email })
|
|
14
|
+
* })
|
|
15
|
+
*
|
|
16
|
+
* $.on.Order.completed(async (order, $) => {
|
|
17
|
+
* $.log('Order completed:', order.id)
|
|
18
|
+
* })
|
|
19
|
+
*
|
|
20
|
+
* // Register scheduled tasks
|
|
21
|
+
* $.every.hour(async ($) => {
|
|
22
|
+
* $.log('Hourly check')
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* $.every.Monday.at9am(async ($) => {
|
|
26
|
+
* $.log('Weekly standup reminder')
|
|
27
|
+
* })
|
|
28
|
+
*
|
|
29
|
+
* $.every.minutes(30)(async ($) => {
|
|
30
|
+
* $.log('Every 30 minutes')
|
|
31
|
+
* })
|
|
32
|
+
*
|
|
33
|
+
* // Natural language scheduling
|
|
34
|
+
* $.every('first Monday of the month', async ($) => {
|
|
35
|
+
* $.log('Monthly report')
|
|
36
|
+
* })
|
|
37
|
+
* })
|
|
38
|
+
*
|
|
39
|
+
* // Start the workflow
|
|
40
|
+
* await workflow.start()
|
|
41
|
+
*
|
|
42
|
+
* // Emit events
|
|
43
|
+
* await workflow.send('Customer.created', { name: 'John', email: 'john@example.com' })
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // Alternative: Use standalone on/every for global registration
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { on, every, send } from 'ai-workflows'
|
|
50
|
+
*
|
|
51
|
+
* on.Customer.created(async (customer, $) => {
|
|
52
|
+
* await $.send('Email.welcome', { to: customer.email })
|
|
53
|
+
* })
|
|
54
|
+
*
|
|
55
|
+
* every.hour(async ($) => {
|
|
56
|
+
* $.log('Hourly task')
|
|
57
|
+
* })
|
|
58
|
+
*
|
|
59
|
+
* await send('Customer.created', { name: 'John' })
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
// Main Workflow API
|
|
63
|
+
export { Workflow, createTestContext, parseEvent } from './workflow.js';
|
|
64
|
+
// Standalone event handling (for global registration)
|
|
65
|
+
export { on, registerEventHandler, getEventHandlers, clearEventHandlers } from './on.js';
|
|
66
|
+
// Standalone scheduling (for global registration)
|
|
67
|
+
export { every, registerScheduleHandler, getScheduleHandlers, clearScheduleHandlers, setCronConverter, toCron, intervalToMs, formatInterval, } from './every.js';
|
|
68
|
+
// Event emission
|
|
69
|
+
export { send, getEventBus } from './send.js';
|
|
70
|
+
// Context
|
|
71
|
+
export { createWorkflowContext, createIsolatedContext } from './context.js';
|
package/src/on.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event handler registration using on.Noun.event syntax
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* on.Customer.created(customer => { ... })
|
|
6
|
+
* on.Order.completed(order => { ... })
|
|
7
|
+
* on.Payment.failed(payment => { ... })
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Registry of event handlers
|
|
11
|
+
*/
|
|
12
|
+
const eventRegistry = [];
|
|
13
|
+
/**
|
|
14
|
+
* Get all registered event handlers
|
|
15
|
+
*/
|
|
16
|
+
export function getEventHandlers() {
|
|
17
|
+
return [...eventRegistry];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Clear all registered event handlers
|
|
21
|
+
*/
|
|
22
|
+
export function clearEventHandlers() {
|
|
23
|
+
eventRegistry.length = 0;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Register an event handler directly
|
|
27
|
+
*/
|
|
28
|
+
export function registerEventHandler(noun, event, handler) {
|
|
29
|
+
eventRegistry.push({
|
|
30
|
+
noun,
|
|
31
|
+
event,
|
|
32
|
+
handler,
|
|
33
|
+
source: handler.toString(),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create the `on` proxy
|
|
38
|
+
*
|
|
39
|
+
* This creates a proxy that allows:
|
|
40
|
+
* on.Customer.created(handler)
|
|
41
|
+
* on.Order.shipped(handler)
|
|
42
|
+
*
|
|
43
|
+
* The first property access captures the noun (Customer, Order)
|
|
44
|
+
* The second property access captures the event (created, shipped)
|
|
45
|
+
* The function call registers the handler
|
|
46
|
+
*/
|
|
47
|
+
function createOnProxy() {
|
|
48
|
+
return new Proxy({}, {
|
|
49
|
+
get(_target, noun) {
|
|
50
|
+
// Return a proxy for the event level
|
|
51
|
+
return new Proxy({}, {
|
|
52
|
+
get(_eventTarget, event) {
|
|
53
|
+
// Return a function that registers the handler
|
|
54
|
+
return (handler) => {
|
|
55
|
+
registerEventHandler(noun, event, handler);
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The `on` object for registering event handlers
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* import { on } from 'ai-workflows'
|
|
68
|
+
*
|
|
69
|
+
* on.Customer.created(async (customer, $) => {
|
|
70
|
+
* $.log('Customer created:', customer.name)
|
|
71
|
+
* await $.send('Email.welcome', { to: customer.email })
|
|
72
|
+
* })
|
|
73
|
+
*
|
|
74
|
+
* on.Order.completed(async (order, $) => {
|
|
75
|
+
* $.log('Order completed:', order.id)
|
|
76
|
+
* })
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export const on = createOnProxy();
|
package/src/send.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event emission using send('Noun.event', data)
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* send('Customer.created', customer)
|
|
6
|
+
* send('Order.completed', order)
|
|
7
|
+
*/
|
|
8
|
+
import { getEventHandlers } from './on.js';
|
|
9
|
+
import { createWorkflowContext } from './context.js';
|
|
10
|
+
/**
|
|
11
|
+
* Event bus for managing event delivery
|
|
12
|
+
*/
|
|
13
|
+
class EventBus {
|
|
14
|
+
pending = [];
|
|
15
|
+
processing = false;
|
|
16
|
+
/**
|
|
17
|
+
* Emit an event
|
|
18
|
+
*/
|
|
19
|
+
async emit(event, data) {
|
|
20
|
+
this.pending.push({ event, data });
|
|
21
|
+
if (!this.processing) {
|
|
22
|
+
await this.process();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Process pending events
|
|
27
|
+
*/
|
|
28
|
+
async process() {
|
|
29
|
+
this.processing = true;
|
|
30
|
+
while (this.pending.length > 0) {
|
|
31
|
+
const item = this.pending.shift();
|
|
32
|
+
await this.deliver(item.event, item.data);
|
|
33
|
+
}
|
|
34
|
+
this.processing = false;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Deliver an event to matching handlers
|
|
38
|
+
*/
|
|
39
|
+
async deliver(event, data) {
|
|
40
|
+
const parsed = parseEvent(event);
|
|
41
|
+
if (!parsed) {
|
|
42
|
+
console.warn(`Invalid event format: ${event}. Expected Noun.event`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const handlers = getEventHandlers();
|
|
46
|
+
const matching = handlers.filter(h => h.noun === parsed.noun && h.event === parsed.event);
|
|
47
|
+
if (matching.length === 0) {
|
|
48
|
+
// No handlers registered - that's okay, event is just not handled
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Create workflow context for handlers
|
|
52
|
+
const ctx = createWorkflowContext(this);
|
|
53
|
+
// Execute all matching handlers
|
|
54
|
+
await Promise.all(matching.map(async ({ handler }) => {
|
|
55
|
+
try {
|
|
56
|
+
await handler(data, ctx);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error(`Error in handler for ${event}:`, error);
|
|
60
|
+
}
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Parse event string into noun and event
|
|
66
|
+
*/
|
|
67
|
+
export function parseEvent(event) {
|
|
68
|
+
const parts = event.split('.');
|
|
69
|
+
if (parts.length !== 2) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const [noun, eventName] = parts;
|
|
73
|
+
if (!noun || !eventName) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return { noun, event: eventName };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Global event bus instance
|
|
80
|
+
*/
|
|
81
|
+
const globalEventBus = new EventBus();
|
|
82
|
+
/**
|
|
83
|
+
* Send an event
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* import { send } from 'ai-workflows'
|
|
88
|
+
*
|
|
89
|
+
* // Emit a customer created event
|
|
90
|
+
* await send('Customer.created', {
|
|
91
|
+
* id: '123',
|
|
92
|
+
* name: 'John Doe',
|
|
93
|
+
* email: 'john@example.com'
|
|
94
|
+
* })
|
|
95
|
+
*
|
|
96
|
+
* // Emit an order completed event
|
|
97
|
+
* await send('Order.completed', {
|
|
98
|
+
* id: 'order-456',
|
|
99
|
+
* total: 99.99
|
|
100
|
+
* })
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export async function send(event, data) {
|
|
104
|
+
await globalEventBus.emit(event, data);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get the global event bus (for testing/internal use)
|
|
108
|
+
*/
|
|
109
|
+
export function getEventBus() {
|
|
110
|
+
return globalEventBus;
|
|
111
|
+
}
|
package/src/send.ts
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* send('Order.completed', order)
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { ParsedEvent } from './types.js'
|
|
10
9
|
import { getEventHandlers } from './on.js'
|
|
11
10
|
import { createWorkflowContext } from './context.js'
|
|
11
|
+
import { parseEvent } from './workflow.js'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Event bus for managing event delivery
|
|
@@ -78,21 +78,6 @@ class EventBus {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
/**
|
|
82
|
-
* Parse event string into noun and event
|
|
83
|
-
*/
|
|
84
|
-
export function parseEvent(event: string): ParsedEvent | null {
|
|
85
|
-
const parts = event.split('.')
|
|
86
|
-
if (parts.length !== 2) {
|
|
87
|
-
return null
|
|
88
|
-
}
|
|
89
|
-
const [noun, eventName] = parts
|
|
90
|
-
if (!noun || !eventName) {
|
|
91
|
-
return null
|
|
92
|
-
}
|
|
93
|
-
return { noun, event: eventName }
|
|
94
|
-
}
|
|
95
|
-
|
|
96
81
|
/**
|
|
97
82
|
* Global event bus instance
|
|
98
83
|
*/
|
package/src/types.js
ADDED