ai-props 2.1.3 → 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 (73) hide show
  1. package/.dev.vars +2 -0
  2. package/CHANGELOG.md +11 -0
  3. package/README.md +2 -0
  4. package/package.json +39 -13
  5. package/src/ai.ts +12 -31
  6. package/src/cascade.ts +795 -0
  7. package/src/client.ts +440 -0
  8. package/src/durable-cascade.ts +743 -0
  9. package/src/event-bridge.ts +478 -0
  10. package/src/generate.ts +14 -12
  11. package/src/hoc.ts +15 -19
  12. package/src/hono-jsx.ts +675 -0
  13. package/src/index.ts +30 -0
  14. package/src/mdx-types.ts +169 -0
  15. package/src/mdx-utils.ts +437 -0
  16. package/src/mdx.ts +1008 -0
  17. package/src/rpc.ts +614 -0
  18. package/src/streaming.ts +618 -0
  19. package/src/validate.ts +15 -29
  20. package/src/worker.ts +547 -0
  21. package/test/cascade.test.ts +338 -0
  22. package/test/durable-cascade.test.ts +319 -0
  23. package/test/event-bridge.test.ts +351 -0
  24. package/test/generate.test.ts +6 -16
  25. package/test/mdx.test.ts +817 -0
  26. package/test/worker/capnweb-rpc.test.ts +1084 -0
  27. package/test/worker/full-flow.integration.test.ts +1463 -0
  28. package/test/worker/hono-jsx.test.ts +1258 -0
  29. package/test/worker/mdx-parsing.test.ts +1148 -0
  30. package/test/worker/setup.ts +56 -0
  31. package/test/worker.test.ts +595 -0
  32. package/tsconfig.json +2 -1
  33. package/vitest.config.js +6 -0
  34. package/vitest.config.ts +15 -1
  35. package/vitest.workers.config.ts +58 -0
  36. package/wrangler.jsonc +27 -0
  37. package/.turbo/turbo-build.log +0 -4
  38. package/LICENSE +0 -21
  39. package/dist/ai.d.ts +0 -125
  40. package/dist/ai.d.ts.map +0 -1
  41. package/dist/ai.js +0 -199
  42. package/dist/ai.js.map +0 -1
  43. package/dist/cache.d.ts +0 -66
  44. package/dist/cache.d.ts.map +0 -1
  45. package/dist/cache.js +0 -183
  46. package/dist/cache.js.map +0 -1
  47. package/dist/generate.d.ts +0 -69
  48. package/dist/generate.d.ts.map +0 -1
  49. package/dist/generate.js +0 -221
  50. package/dist/generate.js.map +0 -1
  51. package/dist/hoc.d.ts +0 -164
  52. package/dist/hoc.d.ts.map +0 -1
  53. package/dist/hoc.js +0 -236
  54. package/dist/hoc.js.map +0 -1
  55. package/dist/index.d.ts +0 -15
  56. package/dist/index.d.ts.map +0 -1
  57. package/dist/index.js +0 -21
  58. package/dist/index.js.map +0 -1
  59. package/dist/types.d.ts +0 -152
  60. package/dist/types.d.ts.map +0 -1
  61. package/dist/types.js +0 -7
  62. package/dist/types.js.map +0 -1
  63. package/dist/validate.d.ts +0 -58
  64. package/dist/validate.d.ts.map +0 -1
  65. package/dist/validate.js +0 -253
  66. package/dist/validate.js.map +0 -1
  67. package/src/ai.js +0 -198
  68. package/src/cache.js +0 -182
  69. package/src/generate.js +0 -220
  70. package/src/hoc.js +0 -235
  71. package/src/index.js +0 -20
  72. package/src/types.js +0 -6
  73. package/src/validate.js +0 -252
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Event Bridge Tests
3
+ */
4
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
5
+ import {
6
+ EventBridge,
7
+ createQueueHandler,
8
+ createEventBridge,
9
+ type QueuedEvent,
10
+ type Queue,
11
+ type MessageBatch,
12
+ type QueueMessage,
13
+ } from '../src/event-bridge.js'
14
+
15
+ // Mock queue implementation for testing
16
+ function createMockQueue<T = unknown>(): Queue<T> & { messages: T[] } {
17
+ const messages: T[] = []
18
+ return {
19
+ messages,
20
+ async send(message: T): Promise<void> {
21
+ messages.push(message)
22
+ },
23
+ async sendBatch(batch: Iterable<{ body: T }>): Promise<void> {
24
+ for (const { body } of batch) {
25
+ messages.push(body)
26
+ }
27
+ },
28
+ }
29
+ }
30
+
31
+ // Mock message for testing
32
+ function createMockMessage<T>(body: T): QueueMessage<T> {
33
+ return {
34
+ id: 'msg-1',
35
+ timestamp: new Date(),
36
+ body,
37
+ ack: vi.fn(),
38
+ retry: vi.fn(),
39
+ }
40
+ }
41
+
42
+ // Mock message batch for testing
43
+ function createMockBatch<T>(messages: T[]): MessageBatch<T> {
44
+ return {
45
+ queue: 'test-queue',
46
+ messages: messages.map((body) => createMockMessage(body)),
47
+ ackAll: vi.fn(),
48
+ retryAll: vi.fn(),
49
+ }
50
+ }
51
+
52
+ describe('EventBridge', () => {
53
+ describe('basic event handling (no queue)', () => {
54
+ it('should emit and handle events synchronously without a queue', async () => {
55
+ const bridge = new EventBridge()
56
+ const handler = vi.fn()
57
+
58
+ bridge.on('test.event', handler)
59
+ await bridge.emit('test.event', { foo: 'bar' })
60
+
61
+ expect(handler).toHaveBeenCalledTimes(1)
62
+ expect(handler).toHaveBeenCalledWith(
63
+ { foo: 'bar' },
64
+ expect.objectContaining({
65
+ type: 'test.event',
66
+ data: { foo: 'bar' },
67
+ })
68
+ )
69
+ })
70
+
71
+ it('should support multiple handlers for the same event', async () => {
72
+ const bridge = new EventBridge()
73
+ const handler1 = vi.fn()
74
+ const handler2 = vi.fn()
75
+
76
+ bridge.on('test.event', handler1)
77
+ bridge.on('test.event', handler2)
78
+ await bridge.emit('test.event', { value: 123 })
79
+
80
+ expect(handler1).toHaveBeenCalledTimes(1)
81
+ expect(handler2).toHaveBeenCalledTimes(1)
82
+ })
83
+
84
+ it('should support wildcard handlers', async () => {
85
+ const bridge = new EventBridge()
86
+ const wildcardHandler = vi.fn()
87
+ const specificHandler = vi.fn()
88
+
89
+ bridge.on('*', wildcardHandler)
90
+ bridge.on('specific.event', specificHandler)
91
+
92
+ await bridge.emit('specific.event', { data: 1 })
93
+ await bridge.emit('other.event', { data: 2 })
94
+
95
+ expect(wildcardHandler).toHaveBeenCalledTimes(2)
96
+ expect(specificHandler).toHaveBeenCalledTimes(1)
97
+ })
98
+
99
+ it('should remove handlers with off()', async () => {
100
+ const bridge = new EventBridge()
101
+ const handler = vi.fn()
102
+
103
+ bridge.on('test.event', handler)
104
+ bridge.off('test.event', handler)
105
+ await bridge.emit('test.event', { value: 1 })
106
+
107
+ expect(handler).not.toHaveBeenCalled()
108
+ })
109
+
110
+ it('should support once() for one-time handlers', async () => {
111
+ const bridge = new EventBridge()
112
+ const handler = vi.fn()
113
+
114
+ bridge.once('test.event', handler)
115
+ await bridge.emit('test.event', { value: 1 })
116
+ await bridge.emit('test.event', { value: 2 })
117
+
118
+ expect(handler).toHaveBeenCalledTimes(1)
119
+ expect(handler).toHaveBeenCalledWith({ value: 1 }, expect.anything())
120
+ })
121
+ })
122
+
123
+ describe('queue-based event handling', () => {
124
+ it('should send events to queue when configured', async () => {
125
+ const queue = createMockQueue<QueuedEvent>()
126
+ const bridge = new EventBridge(queue)
127
+
128
+ await bridge.emit('test.event', { foo: 'bar' })
129
+
130
+ expect(queue.messages).toHaveLength(1)
131
+ expect(queue.messages[0]).toMatchObject({
132
+ type: 'test.event',
133
+ data: { foo: 'bar' },
134
+ })
135
+ })
136
+
137
+ it('should send batch events to queue', async () => {
138
+ const queue = createMockQueue<QueuedEvent>()
139
+ const bridge = new EventBridge(queue)
140
+
141
+ await bridge.emitBatch([
142
+ { type: 'event.1', data: { a: 1 } },
143
+ { type: 'event.2', data: { b: 2 } },
144
+ ])
145
+
146
+ expect(queue.messages).toHaveLength(2)
147
+ expect(queue.messages[0].type).toBe('event.1')
148
+ expect(queue.messages[1].type).toBe('event.2')
149
+ })
150
+
151
+ it('should include delay when specified', async () => {
152
+ const queue = createMockQueue<QueuedEvent>()
153
+ const bridge = new EventBridge(queue)
154
+
155
+ await bridge.emit('test.event', { value: 1 }, { delaySeconds: 30 })
156
+
157
+ // The message should be queued (queue.send was called)
158
+ expect(queue.messages).toHaveLength(1)
159
+ })
160
+ })
161
+
162
+ describe('message handling', () => {
163
+ it('should process messages and ack on success', async () => {
164
+ const bridge = new EventBridge()
165
+ const handler = vi.fn()
166
+ bridge.on('test.event', handler)
167
+
168
+ const event: QueuedEvent = {
169
+ type: 'test.event',
170
+ data: { value: 1 },
171
+ timestamp: Date.now(),
172
+ id: 'event-1',
173
+ attempts: 0,
174
+ }
175
+ const message = createMockMessage(event)
176
+
177
+ await bridge.handleMessage(message)
178
+
179
+ expect(handler).toHaveBeenCalledTimes(1)
180
+ expect(message.ack).toHaveBeenCalled()
181
+ })
182
+
183
+ it('should retry on handler failure', async () => {
184
+ const bridge = new EventBridge()
185
+ bridge.on('test.event', () => {
186
+ throw new Error('Handler failed')
187
+ })
188
+
189
+ const event: QueuedEvent = {
190
+ type: 'test.event',
191
+ data: { value: 1 },
192
+ timestamp: Date.now(),
193
+ id: 'event-1',
194
+ attempts: 0,
195
+ }
196
+ const message = createMockMessage(event)
197
+
198
+ await bridge.handleMessage(message)
199
+
200
+ expect(message.retry).toHaveBeenCalled()
201
+ expect(message.ack).not.toHaveBeenCalled()
202
+ })
203
+
204
+ it('should send to DLQ after max retries', async () => {
205
+ const dlq = createMockQueue<QueuedEvent>()
206
+ const bridge = new EventBridge(undefined, {
207
+ maxRetries: 3,
208
+ deadLetterQueue: dlq,
209
+ })
210
+ bridge.on('test.event', () => {
211
+ throw new Error('Handler failed')
212
+ })
213
+
214
+ const event: QueuedEvent = {
215
+ type: 'test.event',
216
+ data: { value: 1 },
217
+ timestamp: Date.now(),
218
+ id: 'event-1',
219
+ attempts: 2, // Will be incremented to 3 during processing
220
+ }
221
+ const message = createMockMessage(event)
222
+
223
+ await bridge.handleMessage(message)
224
+
225
+ expect(dlq.messages).toHaveLength(1)
226
+ expect(dlq.messages[0]).toMatchObject({
227
+ type: 'test.event',
228
+ error: 'Handler failed',
229
+ })
230
+ expect(message.ack).toHaveBeenCalled() // Ack after DLQ
231
+ })
232
+
233
+ it('should handle message batch', async () => {
234
+ const bridge = new EventBridge()
235
+ const handler = vi.fn()
236
+ bridge.on('test.event', handler)
237
+
238
+ const events: QueuedEvent[] = [
239
+ { type: 'test.event', data: { n: 1 }, timestamp: Date.now(), id: '1' },
240
+ { type: 'test.event', data: { n: 2 }, timestamp: Date.now(), id: '2' },
241
+ ]
242
+ const batch = createMockBatch(events)
243
+
244
+ await bridge.handleBatch(batch)
245
+
246
+ expect(handler).toHaveBeenCalledTimes(2)
247
+ })
248
+ })
249
+
250
+ describe('validation', () => {
251
+ it('should validate events when validator is configured', async () => {
252
+ const bridge = new EventBridge(undefined, {
253
+ validator: (event) => event.type.startsWith('valid.'),
254
+ })
255
+ const handler = vi.fn()
256
+ bridge.on('valid.event', handler)
257
+ bridge.on('invalid.event', handler)
258
+
259
+ await bridge.emit('valid.event', { data: 1 })
260
+ expect(handler).toHaveBeenCalledTimes(1)
261
+
262
+ await expect(bridge.emit('invalid.event', { data: 2 })).rejects.toThrow(
263
+ 'Event validation failed'
264
+ )
265
+ })
266
+ })
267
+
268
+ describe('serialization', () => {
269
+ it('should use custom serializer/deserializer', async () => {
270
+ const bridge = new EventBridge(undefined, {
271
+ serializer: (data) => JSON.stringify(data),
272
+ deserializer: (data) => JSON.parse(data as string),
273
+ })
274
+ const handler = vi.fn()
275
+ bridge.on('test.event', handler)
276
+
277
+ await bridge.emit('test.event', { value: 42 })
278
+
279
+ // The handler receives the original data (not serialized)
280
+ // because sync mode doesn't go through deserialize
281
+ expect(handler).toHaveBeenCalledWith(expect.anything(), expect.anything())
282
+ })
283
+ })
284
+
285
+ describe('utility methods', () => {
286
+ it('should return registered event types', () => {
287
+ const bridge = new EventBridge()
288
+ bridge.on('event.a', () => {})
289
+ bridge.on('event.b', () => {})
290
+ bridge.on('event.c', () => {})
291
+
292
+ const types = bridge.getEventTypes()
293
+ expect(types).toContain('event.a')
294
+ expect(types).toContain('event.b')
295
+ expect(types).toContain('event.c')
296
+ })
297
+
298
+ it('should check if handlers exist', () => {
299
+ const bridge = new EventBridge()
300
+ bridge.on('has.handler', () => {})
301
+
302
+ expect(bridge.hasHandlers('has.handler')).toBe(true)
303
+ expect(bridge.hasHandlers('no.handler')).toBe(false)
304
+ })
305
+
306
+ it('should clear all handlers', () => {
307
+ const bridge = new EventBridge()
308
+ bridge.on('event.a', () => {})
309
+ bridge.on('event.b', () => {})
310
+ bridge.on('*', () => {})
311
+
312
+ bridge.clearHandlers()
313
+
314
+ expect(bridge.getEventTypes()).toHaveLength(0)
315
+ expect(bridge.hasHandlers('event.a')).toBe(false)
316
+ })
317
+ })
318
+ })
319
+
320
+ describe('createQueueHandler', () => {
321
+ it('should create a valid queue handler', async () => {
322
+ const bridge = new EventBridge()
323
+ const handler = vi.fn()
324
+ bridge.on('test.event', handler)
325
+
326
+ const queueHandler = createQueueHandler(bridge)
327
+
328
+ const events: QueuedEvent[] = [
329
+ { type: 'test.event', data: { n: 1 }, timestamp: Date.now(), id: '1' },
330
+ ]
331
+ const batch = createMockBatch(events)
332
+
333
+ await queueHandler.queue(batch)
334
+
335
+ expect(handler).toHaveBeenCalledTimes(1)
336
+ })
337
+ })
338
+
339
+ describe('createEventBridge', () => {
340
+ it('should create EventBridge with default config', () => {
341
+ const bridge = createEventBridge()
342
+ expect(bridge).toBeInstanceOf(EventBridge)
343
+ })
344
+
345
+ it('should create EventBridge with queue and DLQ', () => {
346
+ const queue = createMockQueue()
347
+ const dlq = createMockQueue()
348
+ const bridge = createEventBridge(queue, dlq)
349
+ expect(bridge).toBeInstanceOf(EventBridge)
350
+ })
351
+ })
@@ -83,7 +83,7 @@ describe('getConfig', () => {
83
83
  it('returns default config', () => {
84
84
  const config = getConfig()
85
85
 
86
- expect(config.model).toBe('sonnet')
86
+ expect(config.model).toBe('anthropic/claude-sonnet-4.5')
87
87
  expect(config.cache).toBe(true)
88
88
  expect(config.cacheTTL).toBe(5 * 60 * 1000)
89
89
  })
@@ -103,7 +103,7 @@ describe('resetConfig', () => {
103
103
  resetConfig()
104
104
 
105
105
  const config = getConfig()
106
- expect(config.model).toBe('sonnet')
106
+ expect(config.model).toBe('anthropic/claude-sonnet-4.5')
107
107
  expect(config.cache).toBe(true)
108
108
  })
109
109
  })
@@ -142,7 +142,7 @@ describe('generateProps', () => {
142
142
  })
143
143
 
144
144
  expect(result.metadata).toBeDefined()
145
- expect(result.metadata.model).toBe('sonnet')
145
+ expect(result.metadata.model).toBe('anthropic/claude-sonnet-4.5')
146
146
  })
147
147
 
148
148
  it('uses custom model', async () => {
@@ -289,10 +289,7 @@ describe('prefetchProps', () => {
289
289
  })
290
290
 
291
291
  it('prefetches multiple schemas', async () => {
292
- await prefetchProps([
293
- { schema: { name: 'User name' } },
294
- { schema: { title: 'Page title' } },
295
- ])
292
+ await prefetchProps([{ schema: { name: 'User name' } }, { schema: { title: 'Page title' } }])
296
293
 
297
294
  // Both should be cached now
298
295
  const name = getPropsSync({ name: 'User name' })
@@ -373,10 +370,7 @@ describe('mergeWithGenerated', () => {
373
370
  })
374
371
 
375
372
  it('generates all props when none provided', async () => {
376
- const result = await mergeWithGenerated(
377
- { name: 'User name', email: 'Email address' },
378
- {}
379
- )
373
+ const result = await mergeWithGenerated({ name: 'User name', email: 'Email address' }, {})
380
374
 
381
375
  expect(result.name).toBe('generated-name')
382
376
  expect(result.email).toBe('generated-email')
@@ -395,11 +389,7 @@ describe('mergeWithGenerated', () => {
395
389
  })
396
390
 
397
391
  it('accepts additional options', async () => {
398
- const result = await mergeWithGenerated(
399
- { name: 'User name' },
400
- {},
401
- { model: 'gpt-4' }
402
- )
392
+ const result = await mergeWithGenerated({ name: 'User name' }, {}, { model: 'gpt-4' })
403
393
 
404
394
  expect(result.name).toBe('generated-name')
405
395
  })