ai-workflows 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +17 -1
  3. package/README.md +305 -184
  4. package/dist/barrier.d.ts +159 -0
  5. package/dist/barrier.d.ts.map +1 -0
  6. package/dist/barrier.js +377 -0
  7. package/dist/barrier.js.map +1 -0
  8. package/dist/cascade-context.d.ts +149 -0
  9. package/dist/cascade-context.d.ts.map +1 -0
  10. package/dist/cascade-context.js +324 -0
  11. package/dist/cascade-context.js.map +1 -0
  12. package/dist/cascade-executor.d.ts +196 -0
  13. package/dist/cascade-executor.d.ts.map +1 -0
  14. package/dist/cascade-executor.js +384 -0
  15. package/dist/cascade-executor.js.map +1 -0
  16. package/dist/context.d.ts.map +1 -1
  17. package/dist/context.js +27 -8
  18. package/dist/context.js.map +1 -1
  19. package/dist/cron-parser.d.ts +65 -0
  20. package/dist/cron-parser.d.ts.map +1 -0
  21. package/dist/cron-parser.js +294 -0
  22. package/dist/cron-parser.js.map +1 -0
  23. package/dist/cron-scheduler.d.ts +117 -0
  24. package/dist/cron-scheduler.d.ts.map +1 -0
  25. package/dist/cron-scheduler.js +176 -0
  26. package/dist/cron-scheduler.js.map +1 -0
  27. package/dist/database-context.d.ts +184 -0
  28. package/dist/database-context.d.ts.map +1 -0
  29. package/dist/database-context.js +428 -0
  30. package/dist/database-context.js.map +1 -0
  31. package/dist/dependency-graph.d.ts +157 -0
  32. package/dist/dependency-graph.d.ts.map +1 -0
  33. package/dist/dependency-graph.js +382 -0
  34. package/dist/dependency-graph.js.map +1 -0
  35. package/dist/digital-objects-adapter.d.ts +159 -0
  36. package/dist/digital-objects-adapter.d.ts.map +1 -0
  37. package/dist/digital-objects-adapter.js +229 -0
  38. package/dist/digital-objects-adapter.js.map +1 -0
  39. package/dist/durable-execution-cloudflare.d.ts +427 -0
  40. package/dist/durable-execution-cloudflare.d.ts.map +1 -0
  41. package/dist/durable-execution-cloudflare.js +510 -0
  42. package/dist/durable-execution-cloudflare.js.map +1 -0
  43. package/dist/durable-execution.d.ts +482 -0
  44. package/dist/durable-execution.d.ts.map +1 -0
  45. package/dist/durable-execution.js +594 -0
  46. package/dist/durable-execution.js.map +1 -0
  47. package/dist/durable-workflow.d.ts +176 -0
  48. package/dist/durable-workflow.d.ts.map +1 -0
  49. package/dist/durable-workflow.js +552 -0
  50. package/dist/durable-workflow.js.map +1 -0
  51. package/dist/every.d.ts +31 -2
  52. package/dist/every.d.ts.map +1 -1
  53. package/dist/every.js +63 -32
  54. package/dist/every.js.map +1 -1
  55. package/dist/graph/index.d.ts +8 -0
  56. package/dist/graph/index.d.ts.map +1 -0
  57. package/dist/graph/index.js +8 -0
  58. package/dist/graph/index.js.map +1 -0
  59. package/dist/graph/topological-sort.d.ts +121 -0
  60. package/dist/graph/topological-sort.d.ts.map +1 -0
  61. package/dist/graph/topological-sort.js +292 -0
  62. package/dist/graph/topological-sort.js.map +1 -0
  63. package/dist/index.d.ts +10 -1
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +25 -0
  66. package/dist/index.js.map +1 -1
  67. package/dist/logger.d.ts +101 -0
  68. package/dist/logger.d.ts.map +1 -0
  69. package/dist/logger.js +115 -0
  70. package/dist/logger.js.map +1 -0
  71. package/dist/on.d.ts +35 -10
  72. package/dist/on.d.ts.map +1 -1
  73. package/dist/on.js +53 -19
  74. package/dist/on.js.map +1 -1
  75. package/dist/runtime.d.ts +169 -0
  76. package/dist/runtime.d.ts.map +1 -0
  77. package/dist/runtime.js +275 -0
  78. package/dist/runtime.js.map +1 -0
  79. package/dist/send.d.ts.map +1 -1
  80. package/dist/send.js +4 -3
  81. package/dist/send.js.map +1 -1
  82. package/dist/telemetry.d.ts +150 -0
  83. package/dist/telemetry.d.ts.map +1 -0
  84. package/dist/telemetry.js +388 -0
  85. package/dist/telemetry.js.map +1 -0
  86. package/dist/timer-registry.d.ts +77 -0
  87. package/dist/timer-registry.d.ts.map +1 -0
  88. package/dist/timer-registry.js +154 -0
  89. package/dist/timer-registry.js.map +1 -0
  90. package/dist/types.d.ts +105 -6
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/types.js +17 -1
  93. package/dist/types.js.map +1 -1
  94. package/dist/worker/durable-step.d.ts +481 -0
  95. package/dist/worker/durable-step.d.ts.map +1 -0
  96. package/dist/worker/durable-step.js +606 -0
  97. package/dist/worker/durable-step.js.map +1 -0
  98. package/dist/worker/index.d.ts +106 -0
  99. package/dist/worker/index.d.ts.map +1 -0
  100. package/dist/worker/index.js +124 -0
  101. package/dist/worker/index.js.map +1 -0
  102. package/dist/worker/state-adapter.d.ts +230 -0
  103. package/dist/worker/state-adapter.d.ts.map +1 -0
  104. package/dist/worker/state-adapter.js +409 -0
  105. package/dist/worker/state-adapter.js.map +1 -0
  106. package/dist/worker/topological-executor.d.ts +282 -0
  107. package/dist/worker/topological-executor.d.ts.map +1 -0
  108. package/dist/worker/topological-executor.js +396 -0
  109. package/dist/worker/topological-executor.js.map +1 -0
  110. package/dist/worker/workflow-builder.d.ts +286 -0
  111. package/dist/worker/workflow-builder.d.ts.map +1 -0
  112. package/dist/worker/workflow-builder.js +565 -0
  113. package/dist/worker/workflow-builder.js.map +1 -0
  114. package/dist/worker.d.ts +800 -0
  115. package/dist/worker.d.ts.map +1 -0
  116. package/dist/worker.js +2428 -0
  117. package/dist/worker.js.map +1 -0
  118. package/dist/workflow-builder.d.ts +287 -0
  119. package/dist/workflow-builder.d.ts.map +1 -0
  120. package/dist/workflow-builder.js +762 -0
  121. package/dist/workflow-builder.js.map +1 -0
  122. package/dist/workflow.d.ts +14 -30
  123. package/dist/workflow.d.ts.map +1 -1
  124. package/dist/workflow.js +136 -292
  125. package/dist/workflow.js.map +1 -1
  126. package/examples/01-ecommerce-order-pipeline.ts +358 -0
  127. package/examples/02-content-moderation-cascade.ts +454 -0
  128. package/examples/03-scheduled-reporting-dependencies.ts +479 -0
  129. package/examples/04-database-persistence.ts +518 -0
  130. package/examples/README.md +173 -0
  131. package/package.json +21 -4
  132. package/src/__tests__/digital-objects-adapter.test.ts +274 -0
  133. package/src/__tests__/durable-workflow.test.ts +297 -0
  134. package/src/barrier.ts +507 -0
  135. package/src/cascade-context.ts +495 -0
  136. package/src/cascade-executor.ts +588 -0
  137. package/src/context.ts +51 -17
  138. package/src/cron-parser.ts +347 -0
  139. package/src/cron-scheduler.ts +239 -0
  140. package/src/database-context.ts +658 -0
  141. package/src/dependency-graph.ts +518 -0
  142. package/src/digital-objects-adapter.ts +351 -0
  143. package/src/durable-execution-cloudflare.ts +855 -0
  144. package/src/durable-execution.ts +1042 -0
  145. package/src/durable-workflow.ts +717 -0
  146. package/src/every.ts +104 -35
  147. package/src/graph/index.ts +19 -0
  148. package/src/graph/topological-sort.ts +412 -0
  149. package/src/index.ts +147 -0
  150. package/src/logger.ts +148 -0
  151. package/src/on.ts +81 -26
  152. package/src/runtime.ts +436 -0
  153. package/src/send.ts +4 -5
  154. package/src/telemetry.ts +577 -0
  155. package/src/timer-registry.ts +179 -0
  156. package/src/types.ts +146 -10
  157. package/src/worker/durable-step.ts +976 -0
  158. package/src/worker/index.ts +216 -0
  159. package/src/worker/state-adapter.ts +589 -0
  160. package/src/worker/topological-executor.ts +625 -0
  161. package/src/worker/workflow-builder.ts +871 -0
  162. package/src/worker.ts +2906 -0
  163. package/src/workflow-builder.ts +1068 -0
  164. package/src/workflow.ts +199 -355
  165. package/test/barrier-join.test.ts +442 -0
  166. package/test/barrier-unhandled-rejections.test.ts +359 -0
  167. package/test/cascade-context.test.ts +390 -0
  168. package/test/cascade-executor.test.ts +852 -0
  169. package/test/cron-parser.test.ts +314 -0
  170. package/test/cron-scheduler.test.ts +291 -0
  171. package/test/database-context.test.ts +770 -0
  172. package/test/db-provider-adapter.test.ts +862 -0
  173. package/test/dependency-graph.test.ts +512 -0
  174. package/test/durable-execution-cloudflare.test.ts +606 -0
  175. package/test/durable-execution-in-process.test.ts +286 -0
  176. package/test/durable-execution.test.ts +247 -0
  177. package/test/e2e/workflow-scenarios.e2e.test.ts +1039 -0
  178. package/test/graph/topological-sort.test.ts +586 -0
  179. package/test/integration.test.ts +442 -0
  180. package/test/rpc-surface.test.ts +946 -0
  181. package/test/runtime.test.ts +262 -0
  182. package/test/schedule-timer-cleanup.test.ts +353 -0
  183. package/test/send-race-conditions.test.ts +400 -0
  184. package/test/type-safety-every.test.ts +303 -0
  185. package/test/worker/durable-cascade.test.ts +1117 -0
  186. package/test/worker/durable-step.test.ts +723 -0
  187. package/test/worker/topological-executor.test.ts +1240 -0
  188. package/test/worker/workflow-builder.test.ts +1067 -0
  189. package/test/worker.test.ts +608 -0
  190. package/test/workflow-builder.test.ts +1670 -0
  191. package/test/workflow-cron.test.ts +256 -0
  192. package/test/workflow-state-adapter.test.ts +923 -0
  193. package/test/workflow.test.ts +25 -22
  194. package/tsconfig.json +3 -1
  195. package/vitest.config.ts +38 -1
  196. package/vitest.workers.config.ts +44 -0
  197. package/wrangler.jsonc +22 -0
  198. package/.turbo/turbo-test.log +0 -7
  199. package/src/context.js +0 -83
  200. package/src/every.js +0 -267
  201. package/src/index.js +0 -71
  202. package/src/on.js +0 -79
  203. package/src/send.js +0 -111
  204. package/src/types.js +0 -4
  205. package/src/workflow.js +0 -455
  206. package/test/context.test.js +0 -116
  207. package/test/every.test.js +0 -282
  208. package/test/on.test.js +0 -80
  209. package/test/send.test.js +0 -89
  210. package/test/workflow.test.js +0 -224
  211. package/vitest.config.js +0 -7
package/src/index.ts CHANGED
@@ -63,6 +63,18 @@
63
63
  // Main Workflow API
64
64
  export { Workflow, createTestContext, parseEvent, type WorkflowInstance } from './workflow.js'
65
65
 
66
+ // WorkflowRuntime - single owner of the `$` runtime contract
67
+ // (composes cascade-context, database-context, on/send/every internally).
68
+ // New callers should reach for createWorkflowRuntime() to understand exactly
69
+ // what `$` contains end-to-end. The Workflow() function wraps a runtime with
70
+ // schedule/timer lifecycle; for tests, runtime.dispatch() is the canonical
71
+ // surface.
72
+ export {
73
+ createWorkflowRuntime,
74
+ type WorkflowRuntime,
75
+ type WorkflowRuntimeOptions,
76
+ } from './runtime.js'
77
+
66
78
  // Standalone event handling (for global registration)
67
79
  export { on, registerEventHandler, getEventHandlers, clearEventHandlers } from './on.js'
68
80
 
@@ -84,6 +96,139 @@ export { send, getEventBus } from './send.js'
84
96
  // Context
85
97
  export { createWorkflowContext, createIsolatedContext } from './context.js'
86
98
 
99
+ // Cascade Context - Correlation IDs and step metadata
100
+ export {
101
+ createCascadeContext,
102
+ recordStep,
103
+ withCascadeContext,
104
+ type CascadeContext,
105
+ type CascadeStep,
106
+ type CascadeContextOptions,
107
+ type SerializedCascadeContext,
108
+ type SerializedCascadeStep,
109
+ type TraceContext,
110
+ type FiveWHEvent,
111
+ type StepStatus,
112
+ } from './cascade-context.js'
113
+
114
+ // Dependency Graph
115
+ export {
116
+ DependencyGraph,
117
+ CircularDependencyError,
118
+ MissingDependencyError,
119
+ type GraphNode,
120
+ type ParallelGroup,
121
+ type GraphJSON,
122
+ type EventRegistrationWithDeps,
123
+ } from './dependency-graph.js'
124
+
125
+ // Topological Sort - Execution ordering algorithms
126
+ export {
127
+ topologicalSort,
128
+ topologicalSortKahn,
129
+ topologicalSortDFS,
130
+ getExecutionLevels,
131
+ CycleDetectedError,
132
+ MissingNodeError,
133
+ type SortableNode,
134
+ type ExecutionLevel,
135
+ type TopologicalSortOptions,
136
+ type TopologicalSortResult,
137
+ } from './graph/topological-sort.js'
138
+
139
+ // Barrier/Join Semantics - Parallel step coordination
140
+ export {
141
+ Barrier,
142
+ BarrierTimeoutError,
143
+ createBarrier,
144
+ waitForAll,
145
+ waitForAny,
146
+ withConcurrencyLimit,
147
+ type BarrierOptions,
148
+ type BarrierProgress,
149
+ type BarrierResult,
150
+ type WaitForAllOptions,
151
+ type WaitForAnyOptions,
152
+ type WaitForAnyResult,
153
+ type ConcurrencyOptions,
154
+ } from './barrier.js'
155
+
156
+ // Cascade Executor - code -> generative -> agentic -> human pattern
157
+ export {
158
+ CascadeExecutor,
159
+ CascadeTimeoutError,
160
+ TierSkippedError,
161
+ AllTiersFailedError,
162
+ TIER_ORDER,
163
+ DEFAULT_TIER_TIMEOUTS,
164
+ type CapabilityTier,
165
+ type TierHandler,
166
+ type TierContext,
167
+ type TierResult,
168
+ type TierRetryConfig,
169
+ type CascadeConfig,
170
+ type CascadeResult,
171
+ type CascadeMetrics,
172
+ type SkipCondition,
173
+ } from './cascade-executor.js'
174
+
175
+ // DurableStep - Cloudflare Workflows step wrapper
176
+ export {
177
+ DurableStep,
178
+ StepContext as DurableStepContext,
179
+ type StepConfig,
180
+ type StepMetadata,
181
+ type RetryConfig as DurableRetryConfig,
182
+ type WorkflowStep,
183
+ type StepFunction as DurableStepFunction,
184
+ // Cascade types
185
+ DurableCascadeStep,
186
+ AllTiersFailed,
187
+ CascadeTimeout,
188
+ DEFAULT_CASCADE_TIMEOUTS,
189
+ CASCADE_TIER_ORDER,
190
+ type CascadeTier,
191
+ type CascadeConfig as DurableCascadeConfig,
192
+ type CascadeTierConfig,
193
+ type CascadeTierResult,
194
+ type CascadeResult as DurableCascadeResult,
195
+ type CascadeContext as DurableCascadeContext,
196
+ type CascadeTierContext,
197
+ type AiBinding,
198
+ type HumanReviewRequest,
199
+ type CodeTierHandler,
200
+ type AiTierHandler,
201
+ type HumanTierHandler,
202
+ } from './worker/durable-step.js'
203
+
204
+ // WorkflowBuilder DSL - Fluent API for building durable workflows
205
+ export {
206
+ workflow,
207
+ WorkflowBuilder,
208
+ type StepDefinition,
209
+ type StepChain,
210
+ type ConditionalChain,
211
+ type LoopChain,
212
+ type BuiltWorkflow,
213
+ type StepContext,
214
+ type RetryConfig,
215
+ type StepFunction,
216
+ type ConditionFunction,
217
+ type ErrorHandler,
218
+ type LoopOptions,
219
+ type ForEachOptions,
220
+ } from './workflow-builder.js'
221
+
222
+ // WorkflowStateAdapter - Persistent state storage
223
+ export {
224
+ WorkflowStateAdapter,
225
+ type PersistedWorkflowState,
226
+ type StepCheckpoint,
227
+ type WorkflowHistoryEntry as StateHistoryEntry,
228
+ type SnapshotInfo,
229
+ type DatabaseConnection,
230
+ } from './worker/state-adapter.js'
231
+
87
232
  // Types
88
233
  export type {
89
234
  EventHandler,
@@ -103,4 +248,6 @@ export type {
103
248
  DatabaseContext,
104
249
  ActionData,
105
250
  ArtifactData,
251
+ DependencyConfig,
252
+ DependencyType,
106
253
  } from './types.js'
package/src/logger.ts ADDED
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Configurable Logger Interface
3
+ *
4
+ * Provides a pluggable logging abstraction for ai-workflows.
5
+ * By default uses console.log/warn/error, but can be replaced
6
+ * with any logging implementation.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { setLogger, getLogger } from 'ai-workflows'
11
+ *
12
+ * // Use default console logger
13
+ * const logger = getLogger()
14
+ * logger.log('[workflow] Starting', { workflowId: '123' })
15
+ *
16
+ * // Set a custom logger
17
+ * setLogger({
18
+ * log: (msg, data) => winston.info(msg, data),
19
+ * warn: (msg, data) => winston.warn(msg, data),
20
+ * error: (msg, error) => winston.error(msg, { error }),
21
+ * })
22
+ *
23
+ * // Disable logging
24
+ * setLogger({
25
+ * log: () => {},
26
+ * warn: () => {},
27
+ * error: () => {},
28
+ * })
29
+ * ```
30
+ */
31
+
32
+ /**
33
+ * Logger interface for ai-workflows
34
+ *
35
+ * Implementations must provide log, warn, and error methods.
36
+ * Data parameter is optional and can be any value for context.
37
+ */
38
+ export interface Logger {
39
+ /**
40
+ * Log informational messages
41
+ * @param msg - The log message
42
+ * @param data - Optional data for context
43
+ */
44
+ log(msg: string, data?: unknown): void
45
+
46
+ /**
47
+ * Log warning messages
48
+ * @param msg - The warning message
49
+ * @param data - Optional data for context
50
+ */
51
+ warn(msg: string, data?: unknown): void
52
+
53
+ /**
54
+ * Log error messages
55
+ * @param msg - The error message
56
+ * @param error - Optional error object or data
57
+ */
58
+ error(msg: string, error?: unknown): void
59
+ }
60
+
61
+ /**
62
+ * Default console logger implementation
63
+ *
64
+ * Uses console.log, console.warn, and console.error.
65
+ */
66
+ export const consoleLogger: Logger = {
67
+ log(msg: string, data?: unknown): void {
68
+ if (data !== undefined) {
69
+ console.log(msg, data)
70
+ } else {
71
+ console.log(msg)
72
+ }
73
+ },
74
+
75
+ warn(msg: string, data?: unknown): void {
76
+ if (data !== undefined) {
77
+ console.warn(msg, data)
78
+ } else {
79
+ console.warn(msg)
80
+ }
81
+ },
82
+
83
+ error(msg: string, error?: unknown): void {
84
+ if (error !== undefined) {
85
+ console.error(msg, error)
86
+ } else {
87
+ console.error(msg)
88
+ }
89
+ },
90
+ }
91
+
92
+ /**
93
+ * No-op logger that discards all messages
94
+ *
95
+ * Useful for silencing logs in tests or production.
96
+ */
97
+ export const noopLogger: Logger = {
98
+ log(): void {},
99
+ warn(): void {},
100
+ error(): void {},
101
+ }
102
+
103
+ /**
104
+ * Current logger instance
105
+ * Defaults to consoleLogger
106
+ */
107
+ let currentLogger: Logger = consoleLogger
108
+
109
+ /**
110
+ * Get the current logger instance
111
+ *
112
+ * @returns The current Logger implementation
113
+ */
114
+ export function getLogger(): Logger {
115
+ return currentLogger
116
+ }
117
+
118
+ /**
119
+ * Set the logger implementation
120
+ *
121
+ * @param logger - The Logger implementation to use
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * // Use a custom logger
126
+ * setLogger({
127
+ * log: (msg, data) => myLogger.info(msg, data),
128
+ * warn: (msg, data) => myLogger.warn(msg, data),
129
+ * error: (msg, error) => myLogger.error(msg, { error }),
130
+ * })
131
+ *
132
+ * // Disable logging
133
+ * setLogger(noopLogger)
134
+ *
135
+ * // Reset to console
136
+ * setLogger(consoleLogger)
137
+ * ```
138
+ */
139
+ export function setLogger(logger: Logger): void {
140
+ currentLogger = logger
141
+ }
142
+
143
+ /**
144
+ * Reset the logger to the default console logger
145
+ */
146
+ export function resetLogger(): void {
147
+ currentLogger = consoleLogger
148
+ }
package/src/on.ts CHANGED
@@ -5,9 +5,21 @@
5
5
  * on.Customer.created(customer => { ... })
6
6
  * on.Order.completed(order => { ... })
7
7
  * on.Payment.failed(payment => { ... })
8
+ *
9
+ * With dependencies:
10
+ * on.Step2.complete(handler, { dependsOn: 'Step1.complete' })
11
+ * on.Step3.complete(handler, { dependsOn: ['Step1.complete', 'Step2.complete'] })
8
12
  */
9
13
 
10
- import type { EventHandler, EventRegistration } from './types.js'
14
+ import type {
15
+ EventHandler,
16
+ EventRegistration,
17
+ DependencyConfig,
18
+ OnProxy,
19
+ NounEventProxy,
20
+ OnProxyHandler,
21
+ NounEventProxyHandler,
22
+ } from './types.js'
11
23
 
12
24
  /**
13
25
  * Registry of event handlers
@@ -34,50 +46,93 @@ export function clearEventHandlers(): void {
34
46
  export function registerEventHandler(
35
47
  noun: string,
36
48
  event: string,
37
- handler: EventHandler
49
+ handler: EventHandler,
50
+ dependencies?: DependencyConfig
38
51
  ): void {
39
52
  eventRegistry.push({
40
53
  noun,
41
54
  event,
42
55
  handler,
43
56
  source: handler.toString(),
57
+ ...(dependencies !== undefined && { dependencies }),
44
58
  })
45
59
  }
46
60
 
47
61
  /**
48
- * Event proxy type for on.Noun.event pattern
62
+ * Handler registration callback type
63
+ * Used by createTypedOnProxy to customize handler registration
49
64
  */
50
- type EventProxy = {
51
- [noun: string]: {
52
- [event: string]: (handler: EventHandler) => void
53
- }
54
- }
65
+ export type OnProxyRegistrationCallback = (
66
+ noun: string,
67
+ event: string,
68
+ handler: EventHandler,
69
+ dependencies?: DependencyConfig
70
+ ) => void
55
71
 
56
72
  /**
57
- * Create the `on` proxy
73
+ * Create a typed OnProxy with proper TypeScript generics
58
74
  *
59
- * This creates a proxy that allows:
60
- * on.Customer.created(handler)
61
- * on.Order.shipped(handler)
75
+ * This factory function creates a two-level proxy that allows:
76
+ * proxy.Customer.created(handler)
77
+ * proxy.Order.shipped(handler)
62
78
  *
63
79
  * The first property access captures the noun (Customer, Order)
64
80
  * The second property access captures the event (created, shipped)
65
- * The function call registers the handler
81
+ * The function call invokes the registration callback
82
+ *
83
+ * @param registerCallback - Function called when a handler is registered
84
+ * @returns A properly typed OnProxy
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * // Create proxy with custom registration
89
+ * const myOn = createTypedOnProxy((noun, event, handler, deps) => {
90
+ * myRegistry.push({ noun, event, handler, deps })
91
+ * })
92
+ *
93
+ * myOn.Customer.created(handler) // Properly typed!
94
+ * ```
66
95
  */
67
- function createOnProxy(): EventProxy {
68
- return new Proxy({} as EventProxy, {
69
- get(_target, noun: string) {
70
- // Return a proxy for the event level
71
- return new Proxy({}, {
72
- get(_eventTarget, event: string) {
73
- // Return a function that registers the handler
74
- return (handler: EventHandler) => {
75
- registerEventHandler(noun, event, handler)
76
- }
77
- }
78
- })
79
- }
96
+ export function createTypedOnProxy(registerCallback: OnProxyRegistrationCallback): OnProxy {
97
+ // Create typed handler for the noun level (event accessors)
98
+ const createNounHandler = (noun: string): NounEventProxyHandler => ({
99
+ get(
100
+ _target: Record<string, (handler: EventHandler, dependencies?: DependencyConfig) => void>,
101
+ event: string,
102
+ _receiver: unknown
103
+ ): (handler: EventHandler, dependencies?: DependencyConfig) => void {
104
+ // Return a function that registers the handler with optional dependencies
105
+ return (handler: EventHandler, dependencies?: DependencyConfig) => {
106
+ registerCallback(noun, event, handler, dependencies)
107
+ }
108
+ },
80
109
  })
110
+
111
+ // Create typed handler for the top-level proxy (noun accessors)
112
+ const onHandler: OnProxyHandler = {
113
+ get(_target: Record<string, NounEventProxy>, noun: string, _receiver: unknown): NounEventProxy {
114
+ // Return a proxy for the event level with typed handler
115
+ const eventTarget: Record<
116
+ string,
117
+ (handler: EventHandler, dependencies?: DependencyConfig) => void
118
+ > = {}
119
+ return new Proxy(eventTarget, createNounHandler(noun)) as NounEventProxy
120
+ },
121
+ }
122
+
123
+ // Create and return the typed OnProxy
124
+ const target: Record<string, NounEventProxy> = {}
125
+ return new Proxy(target, onHandler) as OnProxy
126
+ }
127
+
128
+ /**
129
+ * Create the `on` proxy using the global event registry
130
+ *
131
+ * This is the default implementation that uses registerEventHandler
132
+ * for backward compatibility with the standalone `on` export.
133
+ */
134
+ function createOnProxy(): OnProxy {
135
+ return createTypedOnProxy(registerEventHandler)
81
136
  }
82
137
 
83
138
  /**