ai-workflows 2.0.2 → 2.1.3

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 (98) hide show
  1. package/.turbo/turbo-build.log +4 -5
  2. package/.turbo/turbo-test.log +169 -0
  3. package/CHANGELOG.md +29 -0
  4. package/LICENSE +21 -0
  5. package/README.md +303 -184
  6. package/dist/barrier.d.ts +153 -0
  7. package/dist/barrier.d.ts.map +1 -0
  8. package/dist/barrier.js +339 -0
  9. package/dist/barrier.js.map +1 -0
  10. package/dist/cascade-context.d.ts +149 -0
  11. package/dist/cascade-context.d.ts.map +1 -0
  12. package/dist/cascade-context.js +324 -0
  13. package/dist/cascade-context.js.map +1 -0
  14. package/dist/cascade-executor.d.ts +196 -0
  15. package/dist/cascade-executor.d.ts.map +1 -0
  16. package/dist/cascade-executor.js +384 -0
  17. package/dist/cascade-executor.js.map +1 -0
  18. package/dist/context.d.ts.map +1 -1
  19. package/dist/context.js +4 -1
  20. package/dist/context.js.map +1 -1
  21. package/dist/dependency-graph.d.ts +157 -0
  22. package/dist/dependency-graph.d.ts.map +1 -0
  23. package/dist/dependency-graph.js +382 -0
  24. package/dist/dependency-graph.js.map +1 -0
  25. package/dist/every.d.ts +31 -2
  26. package/dist/every.d.ts.map +1 -1
  27. package/dist/every.js +63 -32
  28. package/dist/every.js.map +1 -1
  29. package/dist/graph/index.d.ts +8 -0
  30. package/dist/graph/index.d.ts.map +1 -0
  31. package/dist/graph/index.js +8 -0
  32. package/dist/graph/index.js.map +1 -0
  33. package/dist/graph/topological-sort.d.ts +121 -0
  34. package/dist/graph/topological-sort.d.ts.map +1 -0
  35. package/dist/graph/topological-sort.js +292 -0
  36. package/dist/graph/topological-sort.js.map +1 -0
  37. package/dist/index.d.ts +6 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +10 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/on.d.ts +35 -10
  42. package/dist/on.d.ts.map +1 -1
  43. package/dist/on.js +52 -18
  44. package/dist/on.js.map +1 -1
  45. package/dist/send.d.ts +0 -5
  46. package/dist/send.d.ts.map +1 -1
  47. package/dist/send.js +1 -14
  48. package/dist/send.js.map +1 -1
  49. package/dist/timer-registry.d.ts +52 -0
  50. package/dist/timer-registry.d.ts.map +1 -0
  51. package/dist/timer-registry.js +120 -0
  52. package/dist/timer-registry.js.map +1 -0
  53. package/dist/types.d.ts +171 -9
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/types.js +17 -1
  56. package/dist/types.js.map +1 -1
  57. package/dist/workflow.d.ts.map +1 -1
  58. package/dist/workflow.js +22 -18
  59. package/dist/workflow.js.map +1 -1
  60. package/package.json +12 -16
  61. package/src/barrier.ts +466 -0
  62. package/src/cascade-context.ts +488 -0
  63. package/src/cascade-executor.ts +587 -0
  64. package/src/context.js +83 -0
  65. package/src/context.ts +12 -7
  66. package/src/dependency-graph.ts +518 -0
  67. package/src/every.js +267 -0
  68. package/src/every.ts +104 -35
  69. package/src/graph/index.ts +19 -0
  70. package/src/graph/topological-sort.ts +414 -0
  71. package/src/index.js +71 -0
  72. package/src/index.ts +78 -0
  73. package/src/on.js +79 -0
  74. package/src/on.ts +81 -25
  75. package/src/send.js +111 -0
  76. package/src/send.ts +1 -16
  77. package/src/timer-registry.ts +145 -0
  78. package/src/types.js +4 -0
  79. package/src/types.ts +218 -11
  80. package/src/workflow.js +455 -0
  81. package/src/workflow.ts +32 -23
  82. package/test/barrier-join.test.ts +434 -0
  83. package/test/barrier-unhandled-rejections.test.ts +359 -0
  84. package/test/cascade-context.test.ts +390 -0
  85. package/test/cascade-executor.test.ts +859 -0
  86. package/test/context.test.js +116 -0
  87. package/test/dependency-graph.test.ts +512 -0
  88. package/test/every.test.js +282 -0
  89. package/test/graph/topological-sort.test.ts +586 -0
  90. package/test/on.test.js +80 -0
  91. package/test/schedule-timer-cleanup.test.ts +344 -0
  92. package/test/send-race-conditions.test.ts +410 -0
  93. package/test/send.test.js +89 -0
  94. package/test/type-safety-every.test.ts +303 -0
  95. package/test/types-event-handler.test.ts +225 -0
  96. package/test/types-proxy-autocomplete.test.ts +345 -0
  97. package/test/workflow.test.js +224 -0
  98. package/vitest.config.js +7 -0
@@ -0,0 +1,414 @@
1
+ /**
2
+ * Topological Sort Implementation for Workflow Execution Ordering
3
+ *
4
+ * Provides multiple algorithms for topologically sorting workflow steps:
5
+ * - Kahn's algorithm (BFS-based, good for detecting cycles)
6
+ * - DFS-based algorithm (classic approach, provides cycle path)
7
+ *
8
+ * Features:
9
+ * - Cycle detection with path reporting
10
+ * - Parallel execution level grouping
11
+ * - Stable, deterministic ordering
12
+ * - Support for missing dependency handling
13
+ */
14
+
15
+ /**
16
+ * A node that can be topologically sorted
17
+ */
18
+ export interface SortableNode {
19
+ /** Unique identifier for the node */
20
+ id: string
21
+ /** IDs of nodes this node depends on */
22
+ dependencies: string[]
23
+ }
24
+
25
+ /**
26
+ * Execution level containing nodes that can run in parallel
27
+ */
28
+ export interface ExecutionLevel {
29
+ /** Level number (0 = no dependencies, 1 = depends on level 0, etc.) */
30
+ level: number
31
+ /** Node IDs that can run concurrently at this level */
32
+ nodes: string[]
33
+ }
34
+
35
+ /**
36
+ * Options for topological sort
37
+ */
38
+ export interface TopologicalSortOptions {
39
+ /** Throw CycleDetectedError instead of returning hasCycle: true (default: false) */
40
+ throwOnCycle?: boolean
41
+ /** Which algorithm to use (default: 'kahn') */
42
+ algorithm?: 'kahn' | 'dfs'
43
+ /** Throw on missing dependencies (default: false) */
44
+ strict?: boolean
45
+ }
46
+
47
+ /**
48
+ * Result of topological sort operation
49
+ */
50
+ export interface TopologicalSortResult {
51
+ /** Sorted node IDs in execution order */
52
+ order: string[]
53
+ /** Whether a cycle was detected */
54
+ hasCycle: boolean
55
+ /** Path of nodes forming the cycle (if detected) */
56
+ cyclePath?: string[]
57
+ /** Additional metadata from the algorithm */
58
+ metadata?: {
59
+ /** In-degrees for each node (Kahn's algorithm) */
60
+ inDegrees?: Record<string, number>
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Error thrown when a cycle is detected in the dependency graph
66
+ */
67
+ export class CycleDetectedError extends Error {
68
+ /** The path of nodes forming the cycle */
69
+ cyclePath: string[]
70
+
71
+ constructor(cyclePath: string[]) {
72
+ const pathStr = cyclePath.join(' -> ')
73
+ super(`Cycle detected in dependency graph: ${pathStr}`)
74
+ this.name = 'CycleDetectedError'
75
+ this.cyclePath = cyclePath
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Error thrown when a dependency references a non-existent node
81
+ */
82
+ export class MissingNodeError extends Error {
83
+ /** The missing node ID */
84
+ missingNode: string
85
+ /** The node that references the missing node */
86
+ referencedBy: string
87
+
88
+ constructor(missingNode: string, referencedBy: string) {
89
+ super(
90
+ `Missing dependency '${missingNode}' referenced by '${referencedBy}'`
91
+ )
92
+ this.name = 'MissingNodeError'
93
+ this.missingNode = missingNode
94
+ this.referencedBy = referencedBy
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Build adjacency list and compute in-degrees for Kahn's algorithm
100
+ */
101
+ function buildGraph(
102
+ nodes: SortableNode[],
103
+ strict: boolean
104
+ ): {
105
+ adjacencyList: Map<string, string[]>
106
+ inDegrees: Map<string, number>
107
+ nodeSet: Set<string>
108
+ } {
109
+ const nodeSet = new Set(nodes.map((n) => n.id))
110
+ const adjacencyList = new Map<string, string[]>()
111
+ const inDegrees = new Map<string, number>()
112
+
113
+ // Initialize all nodes
114
+ for (const node of nodes) {
115
+ adjacencyList.set(node.id, [])
116
+ inDegrees.set(node.id, 0)
117
+ }
118
+
119
+ // Build edges (dependency -> dependent)
120
+ for (const node of nodes) {
121
+ // Deduplicate dependencies
122
+ const uniqueDeps = [...new Set(node.dependencies)]
123
+
124
+ for (const dep of uniqueDeps) {
125
+ if (!nodeSet.has(dep)) {
126
+ if (strict) {
127
+ throw new MissingNodeError(dep, node.id)
128
+ }
129
+ // Skip missing dependencies in non-strict mode
130
+ continue
131
+ }
132
+
133
+ // Add edge from dependency to this node
134
+ adjacencyList.get(dep)!.push(node.id)
135
+ inDegrees.set(node.id, inDegrees.get(node.id)! + 1)
136
+ }
137
+ }
138
+
139
+ return { adjacencyList, inDegrees, nodeSet }
140
+ }
141
+
142
+ /**
143
+ * Topological sort using Kahn's algorithm (BFS-based)
144
+ *
145
+ * Algorithm:
146
+ * 1. Calculate in-degree for each node
147
+ * 2. Add all nodes with in-degree 0 to queue
148
+ * 3. While queue not empty:
149
+ * - Remove node from queue, add to result
150
+ * - Decrease in-degree of all dependents
151
+ * - Add nodes with in-degree 0 to queue
152
+ * 4. If result size < node count, cycle exists
153
+ */
154
+ export function topologicalSortKahn(
155
+ nodes: SortableNode[],
156
+ options: TopologicalSortOptions = {}
157
+ ): TopologicalSortResult {
158
+ const { strict = false } = options
159
+
160
+ if (nodes.length === 0) {
161
+ return { order: [], hasCycle: false }
162
+ }
163
+
164
+ const { adjacencyList, inDegrees, nodeSet } = buildGraph(nodes, strict)
165
+
166
+ const order: string[] = []
167
+ const inDegreesCopy = new Map(inDegrees)
168
+
169
+ // Start with nodes that have no dependencies (in-degree 0)
170
+ // Sort alphabetically for determinism
171
+ const queue: string[] = [...nodeSet]
172
+ .filter((id) => inDegreesCopy.get(id) === 0)
173
+ .sort()
174
+
175
+ while (queue.length > 0) {
176
+ // Sort queue for deterministic ordering
177
+ queue.sort()
178
+ const current = queue.shift()!
179
+ order.push(current)
180
+
181
+ // Decrease in-degree for all dependents
182
+ for (const dependent of adjacencyList.get(current) || []) {
183
+ const newDegree = inDegreesCopy.get(dependent)! - 1
184
+ inDegreesCopy.set(dependent, newDegree)
185
+
186
+ if (newDegree === 0) {
187
+ queue.push(dependent)
188
+ }
189
+ }
190
+ }
191
+
192
+ // If we didn't process all nodes, there's a cycle
193
+ const hasCycle = order.length < nodeSet.size
194
+
195
+ // Convert in-degrees to record
196
+ const inDegreesRecord: Record<string, number> = {}
197
+ for (const [id, degree] of inDegrees) {
198
+ inDegreesRecord[id] = degree
199
+ }
200
+
201
+ return {
202
+ order,
203
+ hasCycle,
204
+ metadata: {
205
+ inDegrees: inDegreesRecord,
206
+ },
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Topological sort using DFS-based algorithm
212
+ *
213
+ * Algorithm:
214
+ * 1. For each unvisited node:
215
+ * - Mark as "in progress"
216
+ * - Recursively visit all dependencies
217
+ * - Mark as "visited" and add to result (in reverse)
218
+ * 2. If we encounter a node "in progress", we found a cycle
219
+ */
220
+ export function topologicalSortDFS(
221
+ nodes: SortableNode[],
222
+ options: TopologicalSortOptions = {}
223
+ ): TopologicalSortResult {
224
+ const { strict = false } = options
225
+
226
+ if (nodes.length === 0) {
227
+ return { order: [], hasCycle: false }
228
+ }
229
+
230
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]))
231
+ const nodeSet = new Set(nodes.map((n) => n.id))
232
+
233
+ const visited = new Set<string>()
234
+ const inProgress = new Set<string>()
235
+ const order: string[] = []
236
+ let cyclePath: string[] | undefined
237
+
238
+ function dfs(nodeId: string, path: string[]): boolean {
239
+ if (inProgress.has(nodeId)) {
240
+ // Found a cycle - construct the cycle path
241
+ const cycleStart = path.indexOf(nodeId)
242
+ cyclePath = [...path.slice(cycleStart), nodeId]
243
+ return true // Cycle detected
244
+ }
245
+
246
+ if (visited.has(nodeId)) {
247
+ return false // Already processed
248
+ }
249
+
250
+ inProgress.add(nodeId)
251
+ path.push(nodeId)
252
+
253
+ const node = nodeMap.get(nodeId)
254
+ if (node) {
255
+ // Deduplicate and sort dependencies for determinism
256
+ const uniqueDeps = [...new Set(node.dependencies)].sort()
257
+
258
+ for (const dep of uniqueDeps) {
259
+ if (!nodeSet.has(dep)) {
260
+ if (strict) {
261
+ throw new MissingNodeError(dep, nodeId)
262
+ }
263
+ continue // Skip missing deps in non-strict mode
264
+ }
265
+
266
+ if (dfs(dep, path)) {
267
+ return true // Propagate cycle detection
268
+ }
269
+ }
270
+ }
271
+
272
+ path.pop()
273
+ inProgress.delete(nodeId)
274
+ visited.add(nodeId)
275
+ order.push(nodeId)
276
+
277
+ return false
278
+ }
279
+
280
+ // Process nodes in sorted order for determinism
281
+ const sortedNodeIds = [...nodeSet].sort()
282
+
283
+ for (const nodeId of sortedNodeIds) {
284
+ if (!visited.has(nodeId)) {
285
+ if (dfs(nodeId, [])) {
286
+ break // Stop on first cycle
287
+ }
288
+ }
289
+ }
290
+
291
+ const hasCycle = cyclePath !== undefined
292
+
293
+ return {
294
+ order: hasCycle ? order : order, // DFS produces correct order
295
+ hasCycle,
296
+ cyclePath,
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Main topological sort function with algorithm selection
302
+ *
303
+ * @param nodes - Array of nodes to sort
304
+ * @param options - Sort options
305
+ * @returns Sorted result with order and cycle information
306
+ */
307
+ export function topologicalSort(
308
+ nodes: SortableNode[],
309
+ options: TopologicalSortOptions = {}
310
+ ): TopologicalSortResult {
311
+ const { algorithm = 'dfs', throwOnCycle = false } = options
312
+
313
+ let result: TopologicalSortResult
314
+
315
+ if (algorithm === 'kahn') {
316
+ result = topologicalSortKahn(nodes, options)
317
+
318
+ // Kahn's algorithm doesn't provide cycle path, so detect it separately
319
+ if (result.hasCycle) {
320
+ const dfsResult = topologicalSortDFS(nodes, options)
321
+ result.cyclePath = dfsResult.cyclePath
322
+ }
323
+ } else {
324
+ result = topologicalSortDFS(nodes, options)
325
+ }
326
+
327
+ if (result.hasCycle && throwOnCycle) {
328
+ throw new CycleDetectedError(result.cyclePath || ['unknown'])
329
+ }
330
+
331
+ return result
332
+ }
333
+
334
+ /**
335
+ * Get execution levels for parallel execution
336
+ *
337
+ * Groups nodes by their execution level:
338
+ * - Level 0: Nodes with no dependencies
339
+ * - Level N: Nodes whose dependencies are all at level < N
340
+ *
341
+ * @param nodes - Array of nodes to analyze
342
+ * @returns Array of execution levels, sorted by level number
343
+ * @throws CycleDetectedError if a cycle is detected
344
+ */
345
+ export function getExecutionLevels(nodes: SortableNode[]): ExecutionLevel[] {
346
+ if (nodes.length === 0) {
347
+ return []
348
+ }
349
+
350
+ // First, verify no cycles exist
351
+ const sortResult = topologicalSort(nodes, { throwOnCycle: true })
352
+
353
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]))
354
+ const nodeSet = new Set(nodes.map((n) => n.id))
355
+ const levels = new Map<string, number>()
356
+
357
+ // Calculate level for each node
358
+ function calculateLevel(nodeId: string): number {
359
+ if (levels.has(nodeId)) {
360
+ return levels.get(nodeId)!
361
+ }
362
+
363
+ const node = nodeMap.get(nodeId)
364
+ if (!node) {
365
+ return 0
366
+ }
367
+
368
+ // Filter to only existing dependencies
369
+ const validDeps = node.dependencies.filter((d) => nodeSet.has(d))
370
+
371
+ if (validDeps.length === 0) {
372
+ levels.set(nodeId, 0)
373
+ return 0
374
+ }
375
+
376
+ let maxDepLevel = -1
377
+ for (const dep of validDeps) {
378
+ const depLevel = calculateLevel(dep)
379
+ maxDepLevel = Math.max(maxDepLevel, depLevel)
380
+ }
381
+
382
+ const level = maxDepLevel + 1
383
+ levels.set(nodeId, level)
384
+ return level
385
+ }
386
+
387
+ // Calculate levels for all nodes
388
+ for (const node of nodes) {
389
+ calculateLevel(node.id)
390
+ }
391
+
392
+ // Group nodes by level
393
+ const levelGroups = new Map<number, string[]>()
394
+ for (const [nodeId, level] of levels) {
395
+ if (!levelGroups.has(level)) {
396
+ levelGroups.set(level, [])
397
+ }
398
+ levelGroups.get(level)!.push(nodeId)
399
+ }
400
+
401
+ // Convert to sorted array of ExecutionLevels
402
+ const result: ExecutionLevel[] = []
403
+ const sortedLevels = [...levelGroups.keys()].sort((a, b) => a - b)
404
+
405
+ for (const level of sortedLevels) {
406
+ result.push({
407
+ level,
408
+ // Sort nodes within level for determinism
409
+ nodes: levelGroups.get(level)!.sort(),
410
+ })
411
+ }
412
+
413
+ return result
414
+ }
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/index.ts CHANGED
@@ -84,6 +84,82 @@ export { send, getEventBus } from './send.js'
84
84
  // Context
85
85
  export { createWorkflowContext, createIsolatedContext } from './context.js'
86
86
 
87
+ // Cascade Context - Correlation IDs and step metadata
88
+ export {
89
+ createCascadeContext,
90
+ recordStep,
91
+ withCascadeContext,
92
+ type CascadeContext,
93
+ type CascadeStep,
94
+ type CascadeContextOptions,
95
+ type SerializedCascadeContext,
96
+ type SerializedCascadeStep,
97
+ type TraceContext,
98
+ type FiveWHEvent,
99
+ type StepStatus,
100
+ } from './cascade-context.js'
101
+
102
+ // Dependency Graph
103
+ export {
104
+ DependencyGraph,
105
+ CircularDependencyError,
106
+ MissingDependencyError,
107
+ type GraphNode,
108
+ type ParallelGroup,
109
+ type GraphJSON,
110
+ type EventRegistrationWithDeps,
111
+ } from './dependency-graph.js'
112
+
113
+ // Topological Sort - Execution ordering algorithms
114
+ export {
115
+ topologicalSort,
116
+ topologicalSortKahn,
117
+ topologicalSortDFS,
118
+ getExecutionLevels,
119
+ CycleDetectedError,
120
+ MissingNodeError,
121
+ type SortableNode,
122
+ type ExecutionLevel,
123
+ type TopologicalSortOptions,
124
+ type TopologicalSortResult,
125
+ } from './graph/topological-sort.js'
126
+
127
+ // Barrier/Join Semantics - Parallel step coordination
128
+ export {
129
+ Barrier,
130
+ BarrierTimeoutError,
131
+ createBarrier,
132
+ waitForAll,
133
+ waitForAny,
134
+ withConcurrencyLimit,
135
+ type BarrierOptions,
136
+ type BarrierProgress,
137
+ type BarrierResult,
138
+ type WaitForAllOptions,
139
+ type WaitForAnyOptions,
140
+ type WaitForAnyResult,
141
+ type ConcurrencyOptions,
142
+ } from './barrier.js'
143
+
144
+ // Cascade Executor - code -> generative -> agentic -> human pattern
145
+ export {
146
+ CascadeExecutor,
147
+ CascadeTimeoutError,
148
+ TierSkippedError,
149
+ AllTiersFailedError,
150
+ TIER_ORDER,
151
+ DEFAULT_TIER_TIMEOUTS,
152
+ type CapabilityTier,
153
+ type TierHandler,
154
+ type TierContext,
155
+ type TierResult,
156
+ type TierRetryConfig,
157
+ type CascadeConfig,
158
+ type CascadeResult,
159
+ type CascadeMetrics,
160
+ type SkipCondition,
161
+ } from './cascade-executor.js'
162
+
87
163
  // Types
88
164
  export type {
89
165
  EventHandler,
@@ -103,4 +179,6 @@ export type {
103
179
  DatabaseContext,
104
180
  ActionData,
105
181
  ArtifactData,
182
+ DependencyConfig,
183
+ DependencyType,
106
184
  } from './types.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();