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,411 @@
1
+ /**
2
+ * Tests for ai-props HOC utilities
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
6
+ import {
7
+ createPropsEnhancer,
8
+ createAsyncPropsProvider,
9
+ createPropsTransformer,
10
+ createConditionalGenerator,
11
+ createBatchGenerator,
12
+ } from '../src/hoc.js'
13
+ import { configureAIProps, resetConfig } from '../src/generate.js'
14
+ import { clearCache } from '../src/cache.js'
15
+
16
+ // Mock the ai-functions generateObject
17
+ vi.mock('ai-functions', () => ({
18
+ generateObject: vi.fn().mockImplementation(async ({ schema }) => {
19
+ // Generate mock data based on schema
20
+ const mockData: Record<string, unknown> = {}
21
+ for (const [key, value] of Object.entries(schema)) {
22
+ if (typeof value === 'string') {
23
+ if (value.includes('(number)')) {
24
+ mockData[key] = 42
25
+ } else if (value.includes('(boolean)')) {
26
+ mockData[key] = true
27
+ } else {
28
+ mockData[key] = `generated-${key}`
29
+ }
30
+ } else if (Array.isArray(value)) {
31
+ mockData[key] = ['item1', 'item2']
32
+ } else if (typeof value === 'object') {
33
+ mockData[key] = { nested: 'value' }
34
+ }
35
+ }
36
+ return { object: mockData }
37
+ }),
38
+ schema: vi.fn((s) => s),
39
+ }))
40
+
41
+ describe('createPropsEnhancer', () => {
42
+ beforeEach(() => {
43
+ resetConfig()
44
+ clearCache()
45
+ vi.clearAllMocks()
46
+ })
47
+
48
+ it('creates an enhancer function', () => {
49
+ const enhancer = createPropsEnhancer({
50
+ schema: { name: 'User name' },
51
+ })
52
+
53
+ expect(typeof enhancer).toBe('function')
54
+ })
55
+
56
+ it('generates missing props', async () => {
57
+ const enhancer = createPropsEnhancer({
58
+ schema: {
59
+ name: 'User name',
60
+ bio: 'User biography',
61
+ },
62
+ })
63
+
64
+ const props = await enhancer({})
65
+
66
+ expect(props.name).toBe('generated-name')
67
+ expect(props.bio).toBe('generated-bio')
68
+ })
69
+
70
+ it('preserves provided props', async () => {
71
+ const enhancer = createPropsEnhancer({
72
+ schema: {
73
+ name: 'User name',
74
+ bio: 'User biography',
75
+ },
76
+ })
77
+
78
+ const props = await enhancer({ name: 'John Doe' })
79
+
80
+ expect(props.name).toBe('John Doe')
81
+ expect(props.bio).toBe('generated-bio')
82
+ })
83
+
84
+ it('applies defaults', async () => {
85
+ const enhancer = createPropsEnhancer({
86
+ schema: {
87
+ name: 'User name',
88
+ role: 'User role',
89
+ },
90
+ defaults: {
91
+ role: 'member',
92
+ },
93
+ })
94
+
95
+ const props = await enhancer({})
96
+
97
+ expect(props.role).toBe('member')
98
+ })
99
+
100
+ it('throws for missing required props', async () => {
101
+ const enhancer = createPropsEnhancer({
102
+ schema: {
103
+ name: 'User name',
104
+ email: 'Email address',
105
+ },
106
+ required: ['email'],
107
+ })
108
+
109
+ await expect(enhancer({ name: 'John' })).rejects.toThrow(
110
+ 'Missing required props'
111
+ )
112
+ })
113
+
114
+ it('excludes specified props from generation', async () => {
115
+ const enhancer = createPropsEnhancer({
116
+ schema: {
117
+ name: 'User name',
118
+ internal: 'Internal data',
119
+ },
120
+ exclude: ['internal'],
121
+ })
122
+
123
+ const props = await enhancer({})
124
+
125
+ expect(props.name).toBe('generated-name')
126
+ expect(props.internal).toBeUndefined()
127
+ })
128
+
129
+ it('uses fallback on error', async () => {
130
+ const enhancer = createPropsEnhancer({
131
+ schema: {
132
+ name: 'User name',
133
+ email: 'Email',
134
+ },
135
+ required: ['email'],
136
+ fallback: {
137
+ name: 'Fallback Name',
138
+ email: 'fallback@example.com',
139
+ },
140
+ })
141
+
142
+ // Missing required prop should use fallback instead of throwing
143
+ const props = await enhancer({ name: 'John' })
144
+
145
+ expect(props.name).toBe('John')
146
+ expect(props.email).toBe('fallback@example.com')
147
+ })
148
+
149
+ it('respects config model', async () => {
150
+ const enhancer = createPropsEnhancer({
151
+ schema: { name: 'User name' },
152
+ config: { model: 'gpt-4' },
153
+ })
154
+
155
+ const props = await enhancer({})
156
+
157
+ expect(props.name).toBe('generated-name')
158
+ })
159
+ })
160
+
161
+ describe('createAsyncPropsProvider', () => {
162
+ beforeEach(() => {
163
+ resetConfig()
164
+ clearCache()
165
+ vi.clearAllMocks()
166
+ })
167
+
168
+ it('creates provider with getProps method', () => {
169
+ const provider = createAsyncPropsProvider({
170
+ schema: { title: 'Page title' },
171
+ })
172
+
173
+ expect(provider.getProps).toBeDefined()
174
+ expect(typeof provider.getProps).toBe('function')
175
+ })
176
+
177
+ it('creates provider with getManyProps method', () => {
178
+ const provider = createAsyncPropsProvider({
179
+ schema: { title: 'Page title' },
180
+ })
181
+
182
+ expect(provider.getManyProps).toBeDefined()
183
+ expect(typeof provider.getManyProps).toBe('function')
184
+ })
185
+
186
+ it('creates provider with getCachedProps method', () => {
187
+ const provider = createAsyncPropsProvider({
188
+ schema: { title: 'Page title' },
189
+ })
190
+
191
+ expect(provider.getCachedProps).toBeDefined()
192
+ expect(typeof provider.getCachedProps).toBe('function')
193
+ })
194
+
195
+ it('getProps generates props', async () => {
196
+ const provider = createAsyncPropsProvider({
197
+ schema: { title: 'Page title' },
198
+ })
199
+
200
+ const props = await provider.getProps({})
201
+
202
+ expect(props.title).toBe('generated-title')
203
+ })
204
+
205
+ it('getManyProps generates multiple prop sets', async () => {
206
+ const provider = createAsyncPropsProvider({
207
+ schema: { title: 'Page title' },
208
+ })
209
+
210
+ const results = await provider.getManyProps([
211
+ { slug: 'page-1' },
212
+ { slug: 'page-2' },
213
+ ])
214
+
215
+ expect(results).toHaveLength(2)
216
+ expect(results[0]?.title).toBe('generated-title')
217
+ expect(results[1]?.title).toBe('generated-title')
218
+ })
219
+
220
+ it('getCachedProps returns props with revalidate', async () => {
221
+ const provider = createAsyncPropsProvider({
222
+ schema: { title: 'Page title' },
223
+ })
224
+
225
+ const result = await provider.getCachedProps({}, 60)
226
+
227
+ expect(result.props.title).toBe('generated-title')
228
+ expect(result.revalidate).toBe(60)
229
+ })
230
+
231
+ it('getCachedProps without revalidate', async () => {
232
+ const provider = createAsyncPropsProvider({
233
+ schema: { title: 'Page title' },
234
+ })
235
+
236
+ const result = await provider.getCachedProps({})
237
+
238
+ expect(result.props.title).toBe('generated-title')
239
+ expect(result.revalidate).toBeUndefined()
240
+ })
241
+ })
242
+
243
+ describe('createPropsTransformer', () => {
244
+ beforeEach(() => {
245
+ resetConfig()
246
+ clearCache()
247
+ vi.clearAllMocks()
248
+ })
249
+
250
+ it('creates transformer function', () => {
251
+ const transformer = createPropsTransformer({
252
+ schema: { displayName: 'Display name' },
253
+ })
254
+
255
+ expect(typeof transformer).toBe('function')
256
+ })
257
+
258
+ it('transforms input props', async () => {
259
+ const transformer = createPropsTransformer({
260
+ schema: { displayName: 'Display name' },
261
+ })
262
+
263
+ const result = await transformer({ username: 'johndoe' })
264
+
265
+ expect(result.username).toBe('johndoe')
266
+ expect(result.displayName).toBe('generated-displayName')
267
+ })
268
+
269
+ it('applies custom transform function', async () => {
270
+ const transformer = createPropsTransformer({
271
+ schema: { displayName: 'Display name' },
272
+ transform: (input, generated) => ({
273
+ fullName: `${generated.displayName} (${input.username})`,
274
+ }),
275
+ })
276
+
277
+ const result = await transformer({ username: 'johndoe' })
278
+
279
+ expect(result.username).toBe('johndoe')
280
+ expect(result.fullName).toBe('generated-displayName (johndoe)')
281
+ })
282
+ })
283
+
284
+ describe('createConditionalGenerator', () => {
285
+ beforeEach(() => {
286
+ resetConfig()
287
+ clearCache()
288
+ vi.clearAllMocks()
289
+ })
290
+
291
+ it('generates when condition is true', async () => {
292
+ const generator = createConditionalGenerator({
293
+ schema: { summary: 'Article summary' },
294
+ condition: (props) => !props.summary,
295
+ })
296
+
297
+ const result = await generator({ title: 'Test' })
298
+
299
+ expect(result.summary).toBe('generated-summary')
300
+ })
301
+
302
+ it('returns props unchanged when condition is false', async () => {
303
+ const generator = createConditionalGenerator({
304
+ schema: { summary: 'Article summary' },
305
+ condition: (props) => !props.summary,
306
+ })
307
+
308
+ const result = await generator({ title: 'Test', summary: 'Existing' })
309
+
310
+ expect(result.summary).toBe('Existing')
311
+ expect(result.title).toBe('Test')
312
+ })
313
+
314
+ it('uses complex condition', async () => {
315
+ const generator = createConditionalGenerator<{ content?: string; summary?: string }>({
316
+ schema: { summary: 'Article summary' },
317
+ condition: (props) => !props.summary && (props.content?.length || 0) > 10,
318
+ })
319
+
320
+ // Short content - should not generate
321
+ const shortResult = await generator({ content: 'Short' })
322
+ expect(shortResult.summary).toBeUndefined()
323
+
324
+ // Long content without summary - should generate
325
+ const longResult = await generator({ content: 'This is a longer piece of content' })
326
+ expect(longResult.summary).toBe('generated-summary')
327
+ })
328
+ })
329
+
330
+ describe('createBatchGenerator', () => {
331
+ beforeEach(() => {
332
+ resetConfig()
333
+ clearCache()
334
+ vi.clearAllMocks()
335
+ })
336
+
337
+ it('creates batch generator with generate method', () => {
338
+ const batch = createBatchGenerator({
339
+ schema: { title: 'Item title' },
340
+ })
341
+
342
+ expect(batch.generate).toBeDefined()
343
+ expect(typeof batch.generate).toBe('function')
344
+ })
345
+
346
+ it('creates batch generator with generateSequential method', () => {
347
+ const batch = createBatchGenerator({
348
+ schema: { title: 'Item title' },
349
+ })
350
+
351
+ expect(batch.generateSequential).toBeDefined()
352
+ expect(typeof batch.generateSequential).toBe('function')
353
+ })
354
+
355
+ it('generates props for multiple items', async () => {
356
+ const batch = createBatchGenerator({
357
+ schema: { title: 'Item title' },
358
+ })
359
+
360
+ const results = await batch.generate([
361
+ { id: 1 },
362
+ { id: 2 },
363
+ { id: 3 },
364
+ ])
365
+
366
+ expect(results).toHaveLength(3)
367
+ expect(results[0]?.title).toBe('generated-title')
368
+ expect(results[0]?.id).toBe(1)
369
+ expect(results[2]?.id).toBe(3)
370
+ })
371
+
372
+ it('generates sequentially', async () => {
373
+ const batch = createBatchGenerator({
374
+ schema: { title: 'Item title' },
375
+ })
376
+
377
+ const results = await batch.generateSequential([
378
+ { id: 1 },
379
+ { id: 2 },
380
+ ])
381
+
382
+ expect(results).toHaveLength(2)
383
+ expect(results[0]?.title).toBe('generated-title')
384
+ })
385
+
386
+ it('respects concurrency setting', async () => {
387
+ const batch = createBatchGenerator({
388
+ schema: { title: 'Item title' },
389
+ concurrency: 2, // Process 2 at a time
390
+ })
391
+
392
+ const items = Array.from({ length: 5 }, (_, i) => ({ id: i + 1 }))
393
+ const results = await batch.generate(items)
394
+
395
+ expect(results).toHaveLength(5)
396
+ })
397
+
398
+ it('preserves input props', async () => {
399
+ const batch = createBatchGenerator({
400
+ schema: { title: 'Item title' },
401
+ })
402
+
403
+ const results = await batch.generate([
404
+ { id: 1, category: 'tech' },
405
+ { id: 2, category: 'science' },
406
+ ])
407
+
408
+ expect(results[0]?.category).toBe('tech')
409
+ expect(results[1]?.category).toBe('science')
410
+ })
411
+ })