ai-props 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/.dev.vars +2 -0
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +20 -0
- package/README.md +2 -0
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +4 -4
- package/dist/ai.js.map +1 -1
- package/dist/cascade.d.ts +329 -0
- package/dist/cascade.d.ts.map +1 -0
- package/dist/cascade.js +522 -0
- package/dist/cascade.js.map +1 -0
- package/dist/client.d.ts +233 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +191 -0
- package/dist/client.js.map +1 -0
- package/dist/durable-cascade.d.ts +280 -0
- package/dist/durable-cascade.d.ts.map +1 -0
- package/dist/durable-cascade.js +469 -0
- package/dist/durable-cascade.js.map +1 -0
- package/dist/event-bridge.d.ts +257 -0
- package/dist/event-bridge.d.ts.map +1 -0
- package/dist/event-bridge.js +317 -0
- package/dist/event-bridge.js.map +1 -0
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +12 -6
- package/dist/generate.js.map +1 -1
- package/dist/hoc.d.ts.map +1 -1
- package/dist/hoc.js +13 -13
- package/dist/hoc.js.map +1 -1
- package/dist/hono-jsx.d.ts +208 -0
- package/dist/hono-jsx.d.ts.map +1 -0
- package/dist/hono-jsx.js +459 -0
- package/dist/hono-jsx.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mdx-types.d.ts +152 -0
- package/dist/mdx-types.d.ts.map +1 -0
- package/dist/mdx-types.js +9 -0
- package/dist/mdx-types.js.map +1 -0
- package/dist/mdx-utils.d.ts +106 -0
- package/dist/mdx-utils.d.ts.map +1 -0
- package/dist/mdx-utils.js +384 -0
- package/dist/mdx-utils.js.map +1 -0
- package/dist/mdx.d.ts +230 -0
- package/dist/mdx.d.ts.map +1 -0
- package/dist/mdx.js +820 -0
- package/dist/mdx.js.map +1 -0
- package/dist/rpc.d.ts +313 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +359 -0
- package/dist/rpc.js.map +1 -0
- package/dist/streaming.d.ts +199 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +402 -0
- package/dist/streaming.js.map +1 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +11 -13
- package/dist/validate.js.map +1 -1
- package/dist/worker.d.ts +270 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +405 -0
- package/dist/worker.js.map +1 -0
- package/package.json +39 -13
- package/src/ai.ts +12 -31
- package/src/cascade.ts +795 -0
- package/src/client.ts +440 -0
- package/src/durable-cascade.ts +743 -0
- package/src/event-bridge.ts +478 -0
- package/src/generate.ts +14 -12
- package/src/hoc.ts +15 -19
- package/src/hono-jsx.ts +675 -0
- package/src/index.ts +30 -0
- package/src/mdx-types.ts +169 -0
- package/src/mdx-utils.ts +437 -0
- package/src/mdx.ts +1008 -0
- package/src/rpc.ts +614 -0
- package/src/streaming.ts +618 -0
- package/src/validate.ts +15 -29
- package/src/worker.ts +547 -0
- package/test/cascade.test.ts +338 -0
- package/test/durable-cascade.test.ts +319 -0
- package/test/event-bridge.test.ts +351 -0
- package/test/generate.test.ts +6 -16
- package/test/mdx.test.ts +817 -0
- package/test/worker/capnweb-rpc.test.ts +1084 -0
- package/test/worker/full-flow.integration.test.ts +1463 -0
- package/test/worker/hono-jsx.test.ts +1258 -0
- package/test/worker/mdx-parsing.test.ts +1148 -0
- package/test/worker/setup.ts +56 -0
- package/test/worker.test.ts +595 -0
- package/tsconfig.json +2 -1
- package/vitest.config.js +6 -0
- package/vitest.config.ts +15 -1
- package/vitest.workers.config.ts +58 -0
- package/wrangler.jsonc +27 -0
- package/LICENSE +0 -21
- package/src/ai.js +0 -198
- package/src/cache.js +0 -182
- package/src/generate.js +0 -220
- package/src/hoc.js +0 -235
- package/src/index.js +0 -20
- package/src/types.js +0 -6
- package/src/validate.js +0 -252
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventBridge - Event system with Cloudflare Queues support
|
|
3
|
+
*
|
|
4
|
+
* Provides both sync event handling and Queue-based async event delivery.
|
|
5
|
+
* Supports dead letter queues, event validation, and retry strategies.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Create an EventBridge with a queue
|
|
10
|
+
* const bridge = new EventBridge(env.MY_QUEUE)
|
|
11
|
+
*
|
|
12
|
+
* // Register event handlers
|
|
13
|
+
* bridge.on('user.created', async (data) => {
|
|
14
|
+
* console.log('New user:', data.id)
|
|
15
|
+
* })
|
|
16
|
+
*
|
|
17
|
+
* // Emit events (sent to queue for reliable delivery)
|
|
18
|
+
* await bridge.emit('user.created', { id: '123', name: 'John' })
|
|
19
|
+
*
|
|
20
|
+
* // In worker's queue handler:
|
|
21
|
+
* export default {
|
|
22
|
+
* async queue(batch, env) {
|
|
23
|
+
* const handler = createQueueHandler(bridge)
|
|
24
|
+
* await handler.queue(batch, env)
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @packageDocumentation
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Queued event structure
|
|
34
|
+
*/
|
|
35
|
+
export interface QueuedEvent<T = unknown> {
|
|
36
|
+
/** Event type identifier */
|
|
37
|
+
type: string
|
|
38
|
+
/** Event payload data */
|
|
39
|
+
data: T
|
|
40
|
+
/** Event creation timestamp */
|
|
41
|
+
timestamp: number
|
|
42
|
+
/** Unique event ID */
|
|
43
|
+
id: string
|
|
44
|
+
/** Optional metadata */
|
|
45
|
+
metadata?: Record<string, unknown> | undefined
|
|
46
|
+
/** Number of retry attempts */
|
|
47
|
+
attempts?: number | undefined
|
|
48
|
+
/** Error message (for DLQ events) */
|
|
49
|
+
error?: string | undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* EventBridge configuration options
|
|
54
|
+
*/
|
|
55
|
+
export interface EventBridgeConfig {
|
|
56
|
+
/** Maximum retry attempts before sending to DLQ */
|
|
57
|
+
maxRetries?: number | undefined
|
|
58
|
+
/** Dead letter queue for failed events */
|
|
59
|
+
deadLetterQueue?: Queue | undefined
|
|
60
|
+
/** Event schema validation function */
|
|
61
|
+
validator?: ((event: QueuedEvent) => boolean | Promise<boolean>) | undefined
|
|
62
|
+
/** Custom serializer for event data */
|
|
63
|
+
serializer?: (<T>(data: T) => unknown) | undefined
|
|
64
|
+
/** Custom deserializer for event data */
|
|
65
|
+
deserializer?: (<T>(data: unknown) => T) | undefined
|
|
66
|
+
/** Enable debug logging */
|
|
67
|
+
debug?: boolean | undefined
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Internal resolved config type
|
|
72
|
+
*/
|
|
73
|
+
interface ResolvedEventBridgeConfig {
|
|
74
|
+
maxRetries: number
|
|
75
|
+
deadLetterQueue: Queue | undefined
|
|
76
|
+
validator: ((event: QueuedEvent) => boolean | Promise<boolean>) | undefined
|
|
77
|
+
serializer: <T>(data: T) => unknown
|
|
78
|
+
deserializer: <T>(data: unknown) => T
|
|
79
|
+
debug: boolean
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Event handler function type
|
|
84
|
+
*/
|
|
85
|
+
export type EventHandler<T = unknown> = (data: T, event: QueuedEvent<T>) => void | Promise<void>
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Queue message interface (Cloudflare Queues)
|
|
89
|
+
*/
|
|
90
|
+
export interface QueueMessage<T = unknown> {
|
|
91
|
+
readonly id: string
|
|
92
|
+
readonly timestamp: Date
|
|
93
|
+
readonly body: T
|
|
94
|
+
ack(): void
|
|
95
|
+
retry(options?: { delaySeconds?: number | undefined }): void
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Message batch interface (Cloudflare Queues)
|
|
100
|
+
*/
|
|
101
|
+
export interface MessageBatch<T = unknown> {
|
|
102
|
+
readonly queue: string
|
|
103
|
+
readonly messages: readonly QueueMessage<T>[]
|
|
104
|
+
ackAll(): void
|
|
105
|
+
retryAll(options?: { delaySeconds?: number | undefined }): void
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Queue interface (Cloudflare Queues)
|
|
110
|
+
*/
|
|
111
|
+
export interface Queue<T = unknown> {
|
|
112
|
+
send(
|
|
113
|
+
message: T,
|
|
114
|
+
options?: { contentType?: string | undefined; delaySeconds?: number | undefined }
|
|
115
|
+
): Promise<void>
|
|
116
|
+
sendBatch(
|
|
117
|
+
messages: Iterable<{
|
|
118
|
+
body: T
|
|
119
|
+
contentType?: string | undefined
|
|
120
|
+
delaySeconds?: number | undefined
|
|
121
|
+
}>
|
|
122
|
+
): Promise<void>
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Emit options for customizing event delivery
|
|
127
|
+
*/
|
|
128
|
+
export interface EmitOptions {
|
|
129
|
+
/** Delay before the event is processed (in seconds) */
|
|
130
|
+
delaySeconds?: number | undefined
|
|
131
|
+
/** Additional metadata to attach to the event */
|
|
132
|
+
metadata?: Record<string, unknown> | undefined
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* EventBridge - Unified event system with Queue support
|
|
137
|
+
*
|
|
138
|
+
* Provides reliable event delivery via Cloudflare Queues with
|
|
139
|
+
* support for multiple handlers, dead letter queues, and retries.
|
|
140
|
+
*/
|
|
141
|
+
export class EventBridge {
|
|
142
|
+
private handlers = new Map<string, EventHandler[]>()
|
|
143
|
+
private wildcardHandlers: EventHandler[] = []
|
|
144
|
+
private config: ResolvedEventBridgeConfig
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Create a new EventBridge
|
|
148
|
+
*
|
|
149
|
+
* @param queue - Cloudflare Queue for event delivery
|
|
150
|
+
* @param config - Optional configuration
|
|
151
|
+
*/
|
|
152
|
+
constructor(private queue?: Queue, config: EventBridgeConfig = {}) {
|
|
153
|
+
this.config = {
|
|
154
|
+
maxRetries: config.maxRetries ?? 3,
|
|
155
|
+
deadLetterQueue: config.deadLetterQueue,
|
|
156
|
+
validator: config.validator,
|
|
157
|
+
serializer: config.serializer ?? ((data) => data),
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
159
|
+
deserializer: config.deserializer ?? ((data) => data as any),
|
|
160
|
+
debug: config.debug ?? false,
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Emit an event
|
|
166
|
+
*
|
|
167
|
+
* If a queue is configured, the event will be sent to the queue for
|
|
168
|
+
* reliable delivery. Otherwise, handlers are invoked synchronously.
|
|
169
|
+
*
|
|
170
|
+
* @param event - Event type identifier
|
|
171
|
+
* @param data - Event payload data
|
|
172
|
+
* @param options - Optional emit options
|
|
173
|
+
*/
|
|
174
|
+
async emit<T = unknown>(event: string, data: T, options?: EmitOptions): Promise<void> {
|
|
175
|
+
const queuedEvent: QueuedEvent<T> = {
|
|
176
|
+
type: event,
|
|
177
|
+
data: this.config.serializer(data) as T,
|
|
178
|
+
timestamp: Date.now(),
|
|
179
|
+
id: crypto.randomUUID(),
|
|
180
|
+
metadata: options?.metadata,
|
|
181
|
+
attempts: 0,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (this.config.debug) {
|
|
185
|
+
console.log(`[EventBridge] Emitting event: ${event}`, queuedEvent.id)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Validate event if validator is configured
|
|
189
|
+
if (this.config.validator) {
|
|
190
|
+
const isValid = await this.config.validator(queuedEvent)
|
|
191
|
+
if (!isValid) {
|
|
192
|
+
throw new Error(`Event validation failed for type: ${event}`)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (this.queue) {
|
|
197
|
+
// Send to queue for reliable delivery
|
|
198
|
+
const sendOptions: { contentType?: string | undefined; delaySeconds?: number | undefined } =
|
|
199
|
+
{}
|
|
200
|
+
if (options?.delaySeconds !== undefined) {
|
|
201
|
+
sendOptions.delaySeconds = options.delaySeconds
|
|
202
|
+
}
|
|
203
|
+
await this.queue.send(queuedEvent, sendOptions)
|
|
204
|
+
} else {
|
|
205
|
+
// No queue - invoke handlers synchronously
|
|
206
|
+
await this.invokeHandlers(queuedEvent)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Emit multiple events in a batch
|
|
212
|
+
*
|
|
213
|
+
* @param events - Array of events to emit
|
|
214
|
+
*/
|
|
215
|
+
async emitBatch<T = unknown>(
|
|
216
|
+
events: Array<{ type: string; data: T; options?: EmitOptions }>
|
|
217
|
+
): Promise<void> {
|
|
218
|
+
if (!this.queue) {
|
|
219
|
+
// No queue - invoke handlers for each event synchronously
|
|
220
|
+
for (const { type, data, options } of events) {
|
|
221
|
+
await this.emit(type, data, options)
|
|
222
|
+
}
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const messages = events.map(({ type, data, options }) => {
|
|
227
|
+
const msg: {
|
|
228
|
+
body: QueuedEvent<T>
|
|
229
|
+
contentType?: string | undefined
|
|
230
|
+
delaySeconds?: number | undefined
|
|
231
|
+
} = {
|
|
232
|
+
body: {
|
|
233
|
+
type,
|
|
234
|
+
data: this.config.serializer(data) as T,
|
|
235
|
+
timestamp: Date.now(),
|
|
236
|
+
id: crypto.randomUUID(),
|
|
237
|
+
metadata: options?.metadata,
|
|
238
|
+
attempts: 0,
|
|
239
|
+
},
|
|
240
|
+
}
|
|
241
|
+
if (options?.delaySeconds !== undefined) {
|
|
242
|
+
msg.delaySeconds = options.delaySeconds
|
|
243
|
+
}
|
|
244
|
+
return msg
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
await this.queue.sendBatch(messages)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Register an event handler
|
|
252
|
+
*
|
|
253
|
+
* @param event - Event type to handle (use '*' for all events)
|
|
254
|
+
* @param handler - Handler function
|
|
255
|
+
*/
|
|
256
|
+
on<T = unknown>(event: string, handler: EventHandler<T>): void {
|
|
257
|
+
if (event === '*') {
|
|
258
|
+
this.wildcardHandlers.push(handler as EventHandler)
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const handlers = this.handlers.get(event) ?? []
|
|
263
|
+
handlers.push(handler as EventHandler)
|
|
264
|
+
this.handlers.set(event, handlers)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Remove an event handler
|
|
269
|
+
*
|
|
270
|
+
* @param event - Event type
|
|
271
|
+
* @param handler - Handler to remove
|
|
272
|
+
*/
|
|
273
|
+
off<T = unknown>(event: string, handler: EventHandler<T>): void {
|
|
274
|
+
if (event === '*') {
|
|
275
|
+
const index = this.wildcardHandlers.indexOf(handler as EventHandler)
|
|
276
|
+
if (index !== -1) {
|
|
277
|
+
this.wildcardHandlers.splice(index, 1)
|
|
278
|
+
}
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const handlers = this.handlers.get(event)
|
|
283
|
+
if (handlers) {
|
|
284
|
+
const index = handlers.indexOf(handler as EventHandler)
|
|
285
|
+
if (index !== -1) {
|
|
286
|
+
handlers.splice(index, 1)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Register a one-time event handler
|
|
293
|
+
*
|
|
294
|
+
* @param event - Event type to handle
|
|
295
|
+
* @param handler - Handler function (called once then removed)
|
|
296
|
+
*/
|
|
297
|
+
once<T = unknown>(event: string, handler: EventHandler<T>): void {
|
|
298
|
+
const wrapper: EventHandler<T> = async (data, queuedEvent) => {
|
|
299
|
+
this.off(event, wrapper)
|
|
300
|
+
await handler(data, queuedEvent)
|
|
301
|
+
}
|
|
302
|
+
this.on(event, wrapper)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Handle a message from the queue
|
|
307
|
+
*
|
|
308
|
+
* This method should be called from your worker's queue handler.
|
|
309
|
+
*
|
|
310
|
+
* @param message - Queue message to process
|
|
311
|
+
*/
|
|
312
|
+
async handleMessage<T = unknown>(message: QueueMessage<QueuedEvent<T>>): Promise<void> {
|
|
313
|
+
const event = message.body
|
|
314
|
+
const attempts = (event.attempts ?? 0) + 1
|
|
315
|
+
const deserializedEvent: QueuedEvent<T> = {
|
|
316
|
+
...event,
|
|
317
|
+
data: this.config.deserializer(event.data),
|
|
318
|
+
attempts,
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (this.config.debug) {
|
|
322
|
+
console.log(`[EventBridge] Processing event: ${event.type}`, event.id, `attempt: ${attempts}`)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
await this.invokeHandlers(deserializedEvent)
|
|
327
|
+
message.ack()
|
|
328
|
+
} catch (error) {
|
|
329
|
+
if (this.config.debug) {
|
|
330
|
+
console.error(`[EventBridge] Error processing event: ${event.type}`, error)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Check if we should send to DLQ
|
|
334
|
+
if (attempts >= this.config.maxRetries) {
|
|
335
|
+
if (this.config.deadLetterQueue) {
|
|
336
|
+
await this.config.deadLetterQueue.send({
|
|
337
|
+
...deserializedEvent,
|
|
338
|
+
error: error instanceof Error ? error.message : String(error),
|
|
339
|
+
})
|
|
340
|
+
message.ack() // Ack after sending to DLQ
|
|
341
|
+
} else {
|
|
342
|
+
// No DLQ - let it fail (will be dropped after max retries)
|
|
343
|
+
message.ack()
|
|
344
|
+
}
|
|
345
|
+
} else {
|
|
346
|
+
// Retry with exponential backoff
|
|
347
|
+
const delaySeconds = Math.min(60, Math.pow(2, attempts))
|
|
348
|
+
message.retry({ delaySeconds })
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Handle a batch of messages from the queue
|
|
355
|
+
*
|
|
356
|
+
* @param batch - Message batch to process
|
|
357
|
+
*/
|
|
358
|
+
async handleBatch<T = unknown>(batch: MessageBatch<QueuedEvent<T>>): Promise<void> {
|
|
359
|
+
// Process messages in parallel
|
|
360
|
+
await Promise.all(batch.messages.map((message) => this.handleMessage(message)))
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Get all registered event types
|
|
365
|
+
*/
|
|
366
|
+
getEventTypes(): string[] {
|
|
367
|
+
return Array.from(this.handlers.keys())
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Check if there are handlers for an event type
|
|
372
|
+
*/
|
|
373
|
+
hasHandlers(event: string): boolean {
|
|
374
|
+
const handlers = this.handlers.get(event)
|
|
375
|
+
return (handlers && handlers.length > 0) || this.wildcardHandlers.length > 0
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Clear all handlers
|
|
380
|
+
*/
|
|
381
|
+
clearHandlers(): void {
|
|
382
|
+
this.handlers.clear()
|
|
383
|
+
this.wildcardHandlers = []
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Invoke handlers for an event
|
|
388
|
+
*/
|
|
389
|
+
private async invokeHandlers<T>(event: QueuedEvent<T>): Promise<void> {
|
|
390
|
+
const handlers = this.handlers.get(event.type) ?? []
|
|
391
|
+
const allHandlers = [...handlers, ...this.wildcardHandlers]
|
|
392
|
+
|
|
393
|
+
if (allHandlers.length === 0) {
|
|
394
|
+
if (this.config.debug) {
|
|
395
|
+
console.log(`[EventBridge] No handlers for event: ${event.type}`)
|
|
396
|
+
}
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Fan-out to all handlers
|
|
401
|
+
await Promise.all(allHandlers.map((handler) => handler(event.data, event)))
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Create a queue handler for use in a Cloudflare Worker
|
|
407
|
+
*
|
|
408
|
+
* @param bridge - EventBridge instance to handle events
|
|
409
|
+
* @returns Queue handler object with queue method
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* ```typescript
|
|
413
|
+
* const bridge = new EventBridge(env.MY_QUEUE)
|
|
414
|
+
* const handler = createQueueHandler(bridge)
|
|
415
|
+
*
|
|
416
|
+
* export default {
|
|
417
|
+
* async queue(batch, env, ctx) {
|
|
418
|
+
* await handler.queue(batch, env, ctx)
|
|
419
|
+
* }
|
|
420
|
+
* }
|
|
421
|
+
* ```
|
|
422
|
+
*/
|
|
423
|
+
export function createQueueHandler(bridge: EventBridge): {
|
|
424
|
+
queue: <T>(batch: MessageBatch<QueuedEvent<T>>, env?: unknown, ctx?: unknown) => Promise<void>
|
|
425
|
+
} {
|
|
426
|
+
return {
|
|
427
|
+
async queue<T>(
|
|
428
|
+
batch: MessageBatch<QueuedEvent<T>>,
|
|
429
|
+
_env?: unknown,
|
|
430
|
+
_ctx?: unknown
|
|
431
|
+
): Promise<void> {
|
|
432
|
+
await bridge.handleBatch(batch)
|
|
433
|
+
},
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Create an EventBridge with standard configuration
|
|
439
|
+
*
|
|
440
|
+
* @param queue - Optional queue for async delivery
|
|
441
|
+
* @param dlq - Optional dead letter queue
|
|
442
|
+
* @returns Configured EventBridge instance
|
|
443
|
+
*/
|
|
444
|
+
export function createEventBridge(queue?: Queue, dlq?: Queue): EventBridge {
|
|
445
|
+
const config: EventBridgeConfig = {
|
|
446
|
+
maxRetries: 3,
|
|
447
|
+
debug: false,
|
|
448
|
+
}
|
|
449
|
+
if (dlq) {
|
|
450
|
+
config.deadLetterQueue = dlq
|
|
451
|
+
}
|
|
452
|
+
return new EventBridge(queue, config)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Type helper for strongly-typed event maps
|
|
457
|
+
*
|
|
458
|
+
* @example
|
|
459
|
+
* ```typescript
|
|
460
|
+
* type MyEvents = {
|
|
461
|
+
* 'user.created': { id: string; name: string }
|
|
462
|
+
* 'user.deleted': { id: string }
|
|
463
|
+
* }
|
|
464
|
+
*
|
|
465
|
+
* const bridge = new EventBridge(queue) as TypedEventBridge<MyEvents>
|
|
466
|
+
* bridge.emit('user.created', { id: '1', name: 'John' }) // type-safe!
|
|
467
|
+
* ```
|
|
468
|
+
*/
|
|
469
|
+
export interface TypedEventBridge<TEvents extends Record<string, unknown>> {
|
|
470
|
+
emit<K extends keyof TEvents & string>(
|
|
471
|
+
event: K,
|
|
472
|
+
data: TEvents[K],
|
|
473
|
+
options?: EmitOptions
|
|
474
|
+
): Promise<void>
|
|
475
|
+
on<K extends keyof TEvents & string>(event: K, handler: EventHandler<TEvents[K]>): void
|
|
476
|
+
off<K extends keyof TEvents & string>(event: K, handler: EventHandler<TEvents[K]>): void
|
|
477
|
+
once<K extends keyof TEvents & string>(event: K, handler: EventHandler<TEvents[K]>): void
|
|
478
|
+
}
|
package/src/generate.ts
CHANGED
|
@@ -17,9 +17,10 @@ import { createCacheKey, getDefaultCache } from './cache.js'
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Default configuration
|
|
20
|
+
* Note: Use full model ID to avoid alias resolution issues in bundled environments
|
|
20
21
|
*/
|
|
21
22
|
const DEFAULT_CONFIG: AIPropsConfig = {
|
|
22
|
-
model: 'sonnet',
|
|
23
|
+
model: 'anthropic/claude-sonnet-4.5',
|
|
23
24
|
cache: true,
|
|
24
25
|
cacheTTL: 5 * 60 * 1000, // 5 minutes
|
|
25
26
|
}
|
|
@@ -159,11 +160,16 @@ export async function generateProps<T = Record<string, unknown>>(
|
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
// Generate using AI
|
|
163
|
+
const effectiveModel = model ?? config.model ?? DEFAULT_CONFIG.model!
|
|
162
164
|
const result = await generateObject({
|
|
163
|
-
model:
|
|
165
|
+
model: effectiveModel,
|
|
164
166
|
schema: resolvedSchema,
|
|
165
167
|
prompt: fullPrompt,
|
|
166
|
-
system
|
|
168
|
+
...(system !== undefined
|
|
169
|
+
? { system }
|
|
170
|
+
: config.system !== undefined
|
|
171
|
+
? { system: config.system }
|
|
172
|
+
: {}),
|
|
167
173
|
})
|
|
168
174
|
|
|
169
175
|
const props = result.object as T
|
|
@@ -179,7 +185,7 @@ export async function generateProps<T = Record<string, unknown>>(
|
|
|
179
185
|
props,
|
|
180
186
|
cached: false,
|
|
181
187
|
metadata: {
|
|
182
|
-
model:
|
|
188
|
+
model: effectiveModel,
|
|
183
189
|
duration: Date.now() - startTime,
|
|
184
190
|
},
|
|
185
191
|
}
|
|
@@ -200,9 +206,7 @@ export function getPropsSync<T = Record<string, unknown>>(
|
|
|
200
206
|
const cached = cache.get<T>(cacheKey)
|
|
201
207
|
|
|
202
208
|
if (!cached) {
|
|
203
|
-
throw new Error(
|
|
204
|
-
'Props not in cache. Use generateProps() first or ensure caching is enabled.'
|
|
205
|
-
)
|
|
209
|
+
throw new Error('Props not in cache. Use generateProps() first or ensure caching is enabled.')
|
|
206
210
|
}
|
|
207
211
|
|
|
208
212
|
return cached.props
|
|
@@ -219,9 +223,7 @@ export function getPropsSync<T = Record<string, unknown>>(
|
|
|
219
223
|
* ])
|
|
220
224
|
* ```
|
|
221
225
|
*/
|
|
222
|
-
export async function prefetchProps(
|
|
223
|
-
requests: GeneratePropsOptions[]
|
|
224
|
-
): Promise<void> {
|
|
226
|
+
export async function prefetchProps(requests: GeneratePropsOptions[]): Promise<void> {
|
|
225
227
|
await Promise.all(requests.map(generateProps))
|
|
226
228
|
}
|
|
227
229
|
|
|
@@ -231,7 +233,7 @@ export async function prefetchProps(
|
|
|
231
233
|
export async function generatePropsMany<T = Record<string, unknown>>(
|
|
232
234
|
requests: GeneratePropsOptions[]
|
|
233
235
|
): Promise<GeneratePropsResult<T>[]> {
|
|
234
|
-
return Promise.all(requests.map(req => generateProps<T>(req)))
|
|
236
|
+
return Promise.all(requests.map((req) => generateProps<T>(req)))
|
|
235
237
|
}
|
|
236
238
|
|
|
237
239
|
/**
|
|
@@ -248,7 +250,7 @@ export async function mergeWithGenerated<T extends Record<string, unknown>>(
|
|
|
248
250
|
const schemaObj = typeof schema === 'string' ? { value: schema } : schema
|
|
249
251
|
const schemaKeys = Object.keys(schemaObj as Record<string, unknown>)
|
|
250
252
|
const providedKeys = Object.keys(partialProps)
|
|
251
|
-
const missingKeys = schemaKeys.filter(k => !providedKeys.includes(k))
|
|
253
|
+
const missingKeys = schemaKeys.filter((k) => !providedKeys.includes(k))
|
|
252
254
|
|
|
253
255
|
// If all props are provided, return as-is
|
|
254
256
|
if (missingKeys.length === 0) {
|
package/src/hoc.ts
CHANGED
|
@@ -62,7 +62,7 @@ export function createPropsEnhancer<P extends Record<string, unknown>>(
|
|
|
62
62
|
|
|
63
63
|
// Check required props
|
|
64
64
|
const missingRequired = (required as string[]).filter(
|
|
65
|
-
key => propsWithDefaults[key as keyof P] === undefined
|
|
65
|
+
(key) => propsWithDefaults[key as keyof P] === undefined
|
|
66
66
|
)
|
|
67
67
|
if (missingRequired.length > 0) {
|
|
68
68
|
throw new Error(`Missing required props: ${missingRequired.join(', ')}`)
|
|
@@ -72,14 +72,10 @@ export function createPropsEnhancer<P extends Record<string, unknown>>(
|
|
|
72
72
|
const filteredSchema = filterSchemaKeys(schema, exclude as string[])
|
|
73
73
|
|
|
74
74
|
// Generate missing props
|
|
75
|
-
return await mergeWithGenerated<P>(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
model: config.model,
|
|
80
|
-
system: config.system,
|
|
81
|
-
}
|
|
82
|
-
)
|
|
75
|
+
return await mergeWithGenerated<P>(filteredSchema, propsWithDefaults as Partial<P>, {
|
|
76
|
+
...(config.model !== undefined && { model: config.model }),
|
|
77
|
+
...(config.system !== undefined && { system: config.system }),
|
|
78
|
+
})
|
|
83
79
|
} catch (error) {
|
|
84
80
|
if (fallback) {
|
|
85
81
|
return { ...defaults, ...fallback, ...partialProps } as P
|
|
@@ -154,7 +150,7 @@ export function createAsyncPropsProvider<P extends Record<string, unknown>>(
|
|
|
154
150
|
revalidate?: number
|
|
155
151
|
): Promise<{ props: P; revalidate?: number }> => {
|
|
156
152
|
const props = await enhancer(context)
|
|
157
|
-
return { props, revalidate }
|
|
153
|
+
return { props, ...(revalidate !== undefined && { revalidate }) }
|
|
158
154
|
},
|
|
159
155
|
}
|
|
160
156
|
}
|
|
@@ -194,8 +190,8 @@ export function createPropsTransformer<
|
|
|
194
190
|
const result = await generateProps({
|
|
195
191
|
schema,
|
|
196
192
|
context: input,
|
|
197
|
-
model: config.model,
|
|
198
|
-
system: config.system,
|
|
193
|
+
...(config.model !== undefined && { model: config.model }),
|
|
194
|
+
...(config.system !== undefined && { system: config.system }),
|
|
199
195
|
})
|
|
200
196
|
|
|
201
197
|
const generated = result.props as Record<string, unknown>
|
|
@@ -234,8 +230,8 @@ export function createConditionalGenerator<P extends Record<string, unknown>>(op
|
|
|
234
230
|
}
|
|
235
231
|
|
|
236
232
|
return mergeWithGenerated(schema, props, {
|
|
237
|
-
model: config.model,
|
|
238
|
-
system: config.system,
|
|
233
|
+
...(config.model !== undefined && { model: config.model }),
|
|
234
|
+
...(config.system !== undefined && { system: config.system }),
|
|
239
235
|
})
|
|
240
236
|
}
|
|
241
237
|
}
|
|
@@ -276,10 +272,10 @@ export function createBatchGenerator<P extends Record<string, unknown>>(options:
|
|
|
276
272
|
for (let i = 0; i < items.length; i += concurrency) {
|
|
277
273
|
const batch = items.slice(i, i + concurrency)
|
|
278
274
|
const batchResults = await Promise.all(
|
|
279
|
-
batch.map(item =>
|
|
275
|
+
batch.map((item) =>
|
|
280
276
|
mergeWithGenerated<P>(schema, item, {
|
|
281
|
-
model: config.model,
|
|
282
|
-
system: config.system,
|
|
277
|
+
...(config.model !== undefined && { model: config.model }),
|
|
278
|
+
...(config.system !== undefined && { system: config.system }),
|
|
283
279
|
})
|
|
284
280
|
)
|
|
285
281
|
)
|
|
@@ -297,8 +293,8 @@ export function createBatchGenerator<P extends Record<string, unknown>>(options:
|
|
|
297
293
|
|
|
298
294
|
for (const item of items) {
|
|
299
295
|
const result = await mergeWithGenerated<P>(schema, item, {
|
|
300
|
-
model: config.model,
|
|
301
|
-
system: config.system,
|
|
296
|
+
...(config.model !== undefined && { model: config.model }),
|
|
297
|
+
...(config.system !== undefined && { system: config.system }),
|
|
302
298
|
})
|
|
303
299
|
results.push(result)
|
|
304
300
|
}
|