ai-props 0.1.2 → 2.0.2

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 (69) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/CHANGELOG.md +15 -0
  3. package/README.md +345 -352
  4. package/dist/ai.d.ts +125 -0
  5. package/dist/ai.d.ts.map +1 -0
  6. package/dist/ai.js +199 -0
  7. package/dist/ai.js.map +1 -0
  8. package/dist/cache.d.ts +66 -0
  9. package/dist/cache.d.ts.map +1 -0
  10. package/dist/cache.js +183 -0
  11. package/dist/cache.js.map +1 -0
  12. package/dist/generate.d.ts +69 -0
  13. package/dist/generate.d.ts.map +1 -0
  14. package/dist/generate.js +221 -0
  15. package/dist/generate.js.map +1 -0
  16. package/dist/hoc.d.ts +164 -0
  17. package/dist/hoc.d.ts.map +1 -0
  18. package/dist/hoc.js +236 -0
  19. package/dist/hoc.js.map +1 -0
  20. package/dist/index.d.ts +14 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +21 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/types.d.ts +152 -0
  25. package/dist/types.d.ts.map +1 -0
  26. package/dist/types.js +7 -0
  27. package/dist/types.js.map +1 -0
  28. package/dist/validate.d.ts +58 -0
  29. package/dist/validate.d.ts.map +1 -0
  30. package/dist/validate.js +253 -0
  31. package/dist/validate.js.map +1 -0
  32. package/package.json +16 -63
  33. package/src/ai.ts +264 -0
  34. package/src/cache.ts +216 -0
  35. package/src/generate.ts +276 -0
  36. package/src/hoc.ts +309 -0
  37. package/src/index.ts +66 -0
  38. package/src/types.ts +167 -0
  39. package/src/validate.ts +333 -0
  40. package/test/ai.test.ts +327 -0
  41. package/test/cache.test.ts +236 -0
  42. package/test/generate.test.ts +406 -0
  43. package/test/hoc.test.ts +411 -0
  44. package/test/validate.test.ts +324 -0
  45. package/tsconfig.json +9 -0
  46. package/vitest.config.ts +15 -0
  47. package/LICENSE +0 -21
  48. package/dist/AI.d.ts +0 -27
  49. package/dist/AI.d.ts.map +0 -1
  50. package/dist/AI.test.d.ts +0 -2
  51. package/dist/AI.test.d.ts.map +0 -1
  52. package/dist/ai-props.es.js +0 -3697
  53. package/dist/ai-props.umd.js +0 -30
  54. package/dist/components/ErrorBoundary.d.ts +0 -17
  55. package/dist/components/ErrorBoundary.d.ts.map +0 -1
  56. package/dist/examples/BlogList.d.ts +0 -2
  57. package/dist/examples/BlogList.d.ts.map +0 -1
  58. package/dist/examples/BlogList.fixture.d.ts +0 -5
  59. package/dist/examples/BlogList.fixture.d.ts.map +0 -1
  60. package/dist/examples/HeroSection.d.ts +0 -2
  61. package/dist/examples/HeroSection.d.ts.map +0 -1
  62. package/dist/examples/HeroSection.fixture.d.ts +0 -5
  63. package/dist/examples/HeroSection.fixture.d.ts.map +0 -1
  64. package/dist/test/setup.d.ts +0 -2
  65. package/dist/test/setup.d.ts.map +0 -1
  66. package/dist/utils/schema.d.ts +0 -28
  67. package/dist/utils/schema.d.ts.map +0 -1
  68. package/dist/utils/styles.d.ts +0 -3
  69. package/dist/utils/styles.d.ts.map +0 -1
@@ -0,0 +1,406 @@
1
+ /**
2
+ * Tests for ai-props generation utilities
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
6
+ import {
7
+ generateProps,
8
+ getPropsSync,
9
+ prefetchProps,
10
+ generatePropsMany,
11
+ mergeWithGenerated,
12
+ configureAIProps,
13
+ getConfig,
14
+ resetConfig,
15
+ } from '../src/generate.js'
16
+ import { clearCache, getDefaultCache } from '../src/cache.js'
17
+
18
+ // Mock the ai-functions generateObject
19
+ vi.mock('ai-functions', () => ({
20
+ generateObject: vi.fn().mockImplementation(async ({ schema }) => {
21
+ // Generate mock data based on schema
22
+ const mockData: Record<string, unknown> = {}
23
+ for (const [key, value] of Object.entries(schema)) {
24
+ if (typeof value === 'string') {
25
+ if (value.includes('(number)')) {
26
+ mockData[key] = 42
27
+ } else if (value.includes('(boolean)')) {
28
+ mockData[key] = true
29
+ } else {
30
+ mockData[key] = `generated-${key}`
31
+ }
32
+ } else if (Array.isArray(value)) {
33
+ mockData[key] = ['item1', 'item2']
34
+ } else if (typeof value === 'object') {
35
+ mockData[key] = { nested: 'value' }
36
+ }
37
+ }
38
+ return { object: mockData }
39
+ }),
40
+ schema: vi.fn((s) => s),
41
+ }))
42
+
43
+ describe('configureAIProps', () => {
44
+ beforeEach(() => {
45
+ resetConfig()
46
+ })
47
+
48
+ it('sets model configuration', () => {
49
+ configureAIProps({ model: 'gpt-4' })
50
+ expect(getConfig().model).toBe('gpt-4')
51
+ })
52
+
53
+ it('sets cache configuration', () => {
54
+ configureAIProps({ cache: false })
55
+ expect(getConfig().cache).toBe(false)
56
+ })
57
+
58
+ it('sets cacheTTL configuration', () => {
59
+ configureAIProps({ cacheTTL: 10000 })
60
+ expect(getConfig().cacheTTL).toBe(10000)
61
+ })
62
+
63
+ it('sets system prompt configuration', () => {
64
+ configureAIProps({ system: 'Custom system prompt' })
65
+ expect(getConfig().system).toBe('Custom system prompt')
66
+ })
67
+
68
+ it('merges with existing config', () => {
69
+ configureAIProps({ model: 'gpt-4' })
70
+ configureAIProps({ cache: false })
71
+
72
+ const config = getConfig()
73
+ expect(config.model).toBe('gpt-4')
74
+ expect(config.cache).toBe(false)
75
+ })
76
+ })
77
+
78
+ describe('getConfig', () => {
79
+ beforeEach(() => {
80
+ resetConfig()
81
+ })
82
+
83
+ it('returns default config', () => {
84
+ const config = getConfig()
85
+
86
+ expect(config.model).toBe('sonnet')
87
+ expect(config.cache).toBe(true)
88
+ expect(config.cacheTTL).toBe(5 * 60 * 1000)
89
+ })
90
+
91
+ it('returns a copy of config', () => {
92
+ const config1 = getConfig()
93
+ const config2 = getConfig()
94
+
95
+ expect(config1).not.toBe(config2)
96
+ expect(config1).toEqual(config2)
97
+ })
98
+ })
99
+
100
+ describe('resetConfig', () => {
101
+ it('resets to default values', () => {
102
+ configureAIProps({ model: 'gpt-4', cache: false })
103
+ resetConfig()
104
+
105
+ const config = getConfig()
106
+ expect(config.model).toBe('sonnet')
107
+ expect(config.cache).toBe(true)
108
+ })
109
+ })
110
+
111
+ describe('generateProps', () => {
112
+ beforeEach(() => {
113
+ resetConfig()
114
+ clearCache()
115
+ vi.clearAllMocks()
116
+ })
117
+
118
+ it('generates props from schema', async () => {
119
+ const result = await generateProps({
120
+ schema: {
121
+ title: 'Page title',
122
+ description: 'Page description',
123
+ },
124
+ })
125
+
126
+ expect(result.props).toBeDefined()
127
+ expect(result.props.title).toBe('generated-title')
128
+ expect(result.props.description).toBe('generated-description')
129
+ })
130
+
131
+ it('returns cached flag', async () => {
132
+ const result = await generateProps({
133
+ schema: { name: 'User name' },
134
+ })
135
+
136
+ expect(result.cached).toBe(false)
137
+ })
138
+
139
+ it('returns metadata', async () => {
140
+ const result = await generateProps({
141
+ schema: { name: 'User name' },
142
+ })
143
+
144
+ expect(result.metadata).toBeDefined()
145
+ expect(result.metadata.model).toBe('sonnet')
146
+ })
147
+
148
+ it('uses custom model', async () => {
149
+ const result = await generateProps({
150
+ schema: { name: 'User name' },
151
+ model: 'gpt-4',
152
+ })
153
+
154
+ expect(result.metadata.model).toBe('gpt-4')
155
+ })
156
+
157
+ it('caches results', async () => {
158
+ const schema = { name: 'User name' }
159
+
160
+ // First call
161
+ await generateProps({ schema })
162
+
163
+ // Second call should be cached
164
+ const result = await generateProps({ schema })
165
+
166
+ expect(result.cached).toBe(true)
167
+ })
168
+
169
+ it('respects cache disabled', async () => {
170
+ configureAIProps({ cache: false })
171
+
172
+ const schema = { name: 'User name' }
173
+
174
+ // First call
175
+ await generateProps({ schema })
176
+
177
+ // Second call should not be cached
178
+ const result = await generateProps({ schema })
179
+
180
+ expect(result.cached).toBe(false)
181
+ })
182
+
183
+ it('handles string schema', async () => {
184
+ const result = await generateProps({
185
+ schema: 'A user name',
186
+ })
187
+
188
+ expect(result.props).toBeDefined()
189
+ expect(result.props.value).toBe('generated-value')
190
+ })
191
+
192
+ it('uses custom generator', async () => {
193
+ configureAIProps({
194
+ generate: async <T>() => ({ custom: 'value' } as T),
195
+ })
196
+
197
+ const result = await generateProps({
198
+ schema: { name: 'User name' },
199
+ })
200
+
201
+ expect(result.props.custom).toBe('value')
202
+ expect(result.metadata.model).toBe('custom')
203
+ })
204
+
205
+ it('includes context in generation', async () => {
206
+ const result = await generateProps({
207
+ schema: { name: 'User name' },
208
+ context: { userId: '123' },
209
+ })
210
+
211
+ expect(result.props).toBeDefined()
212
+ })
213
+
214
+ it('includes prompt in generation', async () => {
215
+ const result = await generateProps({
216
+ schema: { name: 'User name' },
217
+ prompt: 'Generate a creative name',
218
+ })
219
+
220
+ expect(result.props).toBeDefined()
221
+ })
222
+
223
+ it('includes system in generation', async () => {
224
+ const result = await generateProps({
225
+ schema: { name: 'User name' },
226
+ system: 'You are a naming expert',
227
+ })
228
+
229
+ expect(result.props).toBeDefined()
230
+ })
231
+
232
+ it('tracks duration in metadata', async () => {
233
+ const result = await generateProps({
234
+ schema: { name: 'User name' },
235
+ })
236
+
237
+ expect(result.metadata.duration).toBeDefined()
238
+ expect(typeof result.metadata.duration).toBe('number')
239
+ })
240
+ })
241
+
242
+ describe('getPropsSync', () => {
243
+ beforeEach(() => {
244
+ resetConfig()
245
+ clearCache()
246
+ })
247
+
248
+ it('returns cached props', async () => {
249
+ const schema = { name: 'User name' }
250
+
251
+ // Pre-populate cache
252
+ await generateProps({ schema })
253
+
254
+ // Get synchronously
255
+ const props = getPropsSync(schema)
256
+
257
+ expect(props.name).toBe('generated-name')
258
+ })
259
+
260
+ it('throws when not cached', () => {
261
+ expect(() => {
262
+ getPropsSync({ name: 'User name' })
263
+ }).toThrow('Props not in cache')
264
+ })
265
+
266
+ it('respects context in cache key', async () => {
267
+ const schema = { name: 'User name' }
268
+ const context = { userId: '123' }
269
+
270
+ // Pre-populate cache with context
271
+ await generateProps({ schema, context })
272
+
273
+ // Get synchronously with same context
274
+ const props = getPropsSync(schema, context)
275
+ expect(props.name).toBe('generated-name')
276
+
277
+ // Different context should throw
278
+ expect(() => {
279
+ getPropsSync(schema, { userId: '456' })
280
+ }).toThrow('Props not in cache')
281
+ })
282
+ })
283
+
284
+ describe('prefetchProps', () => {
285
+ beforeEach(() => {
286
+ resetConfig()
287
+ clearCache()
288
+ vi.clearAllMocks()
289
+ })
290
+
291
+ it('prefetches multiple schemas', async () => {
292
+ await prefetchProps([
293
+ { schema: { name: 'User name' } },
294
+ { schema: { title: 'Page title' } },
295
+ ])
296
+
297
+ // Both should be cached now
298
+ const name = getPropsSync({ name: 'User name' })
299
+ const title = getPropsSync({ title: 'Page title' })
300
+
301
+ expect(name.name).toBe('generated-name')
302
+ expect(title.title).toBe('generated-title')
303
+ })
304
+
305
+ it('prefetches in parallel', async () => {
306
+ const startTime = Date.now()
307
+
308
+ await prefetchProps([
309
+ { schema: { a: 'Value A' } },
310
+ { schema: { b: 'Value B' } },
311
+ { schema: { c: 'Value C' } },
312
+ ])
313
+
314
+ // Should complete quickly since they run in parallel
315
+ expect(Date.now() - startTime).toBeLessThan(1000)
316
+ })
317
+ })
318
+
319
+ describe('generatePropsMany', () => {
320
+ beforeEach(() => {
321
+ resetConfig()
322
+ clearCache()
323
+ vi.clearAllMocks()
324
+ })
325
+
326
+ it('generates multiple prop sets', async () => {
327
+ const results = await generatePropsMany([
328
+ { schema: { name: 'User name' } },
329
+ { schema: { title: 'Page title' } },
330
+ ])
331
+
332
+ expect(results).toHaveLength(2)
333
+ expect(results[0]?.props.name).toBe('generated-name')
334
+ expect(results[1]?.props.title).toBe('generated-title')
335
+ })
336
+
337
+ it('returns all results with metadata', async () => {
338
+ const results = await generatePropsMany([
339
+ { schema: { a: 'Value A' } },
340
+ { schema: { b: 'Value B' } },
341
+ ])
342
+
343
+ expect(results[0]?.metadata).toBeDefined()
344
+ expect(results[1]?.metadata).toBeDefined()
345
+ })
346
+ })
347
+
348
+ describe('mergeWithGenerated', () => {
349
+ beforeEach(() => {
350
+ resetConfig()
351
+ clearCache()
352
+ vi.clearAllMocks()
353
+ })
354
+
355
+ it('generates only missing props', async () => {
356
+ const result = await mergeWithGenerated(
357
+ { name: 'User name', email: 'Email address' },
358
+ { name: 'John Doe' }
359
+ )
360
+
361
+ expect(result.name).toBe('John Doe') // Preserved
362
+ expect(result.email).toBe('generated-email') // Generated
363
+ })
364
+
365
+ it('returns as-is when all props provided', async () => {
366
+ const result = await mergeWithGenerated(
367
+ { name: 'User name', email: 'Email address' },
368
+ { name: 'John Doe', email: 'john@example.com' }
369
+ )
370
+
371
+ expect(result.name).toBe('John Doe')
372
+ expect(result.email).toBe('john@example.com')
373
+ })
374
+
375
+ it('generates all props when none provided', async () => {
376
+ const result = await mergeWithGenerated(
377
+ { name: 'User name', email: 'Email address' },
378
+ {}
379
+ )
380
+
381
+ expect(result.name).toBe('generated-name')
382
+ expect(result.email).toBe('generated-email')
383
+ })
384
+
385
+ it('handles string schema', async () => {
386
+ const result = await mergeWithGenerated('A value', {})
387
+
388
+ expect(result.value).toBe('generated-value')
389
+ })
390
+
391
+ it('preserves provided values for string schema', async () => {
392
+ const result = await mergeWithGenerated('A value', { value: 'provided' })
393
+
394
+ expect(result.value).toBe('provided')
395
+ })
396
+
397
+ it('accepts additional options', async () => {
398
+ const result = await mergeWithGenerated(
399
+ { name: 'User name' },
400
+ {},
401
+ { model: 'gpt-4' }
402
+ )
403
+
404
+ expect(result.name).toBe('generated-name')
405
+ })
406
+ })