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.
- package/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +15 -0
- package/README.md +345 -352
- package/dist/ai.d.ts +125 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +199 -0
- package/dist/ai.js.map +1 -0
- package/dist/cache.d.ts +66 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +183 -0
- package/dist/cache.js.map +1 -0
- package/dist/generate.d.ts +69 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +221 -0
- package/dist/generate.js.map +1 -0
- package/dist/hoc.d.ts +164 -0
- package/dist/hoc.d.ts.map +1 -0
- package/dist/hoc.js +236 -0
- package/dist/hoc.js.map +1 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +152 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +58 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +253 -0
- package/dist/validate.js.map +1 -0
- package/package.json +16 -63
- package/src/ai.ts +264 -0
- package/src/cache.ts +216 -0
- package/src/generate.ts +276 -0
- package/src/hoc.ts +309 -0
- package/src/index.ts +66 -0
- package/src/types.ts +167 -0
- package/src/validate.ts +333 -0
- package/test/ai.test.ts +327 -0
- package/test/cache.test.ts +236 -0
- package/test/generate.test.ts +406 -0
- package/test/hoc.test.ts +411 -0
- package/test/validate.test.ts +324 -0
- package/tsconfig.json +9 -0
- package/vitest.config.ts +15 -0
- package/LICENSE +0 -21
- package/dist/AI.d.ts +0 -27
- package/dist/AI.d.ts.map +0 -1
- package/dist/AI.test.d.ts +0 -2
- package/dist/AI.test.d.ts.map +0 -1
- package/dist/ai-props.es.js +0 -3697
- package/dist/ai-props.umd.js +0 -30
- package/dist/components/ErrorBoundary.d.ts +0 -17
- package/dist/components/ErrorBoundary.d.ts.map +0 -1
- package/dist/examples/BlogList.d.ts +0 -2
- package/dist/examples/BlogList.d.ts.map +0 -1
- package/dist/examples/BlogList.fixture.d.ts +0 -5
- package/dist/examples/BlogList.fixture.d.ts.map +0 -1
- package/dist/examples/HeroSection.d.ts +0 -2
- package/dist/examples/HeroSection.d.ts.map +0 -1
- package/dist/examples/HeroSection.fixture.d.ts +0 -5
- package/dist/examples/HeroSection.fixture.d.ts.map +0 -1
- package/dist/test/setup.d.ts +0 -2
- package/dist/test/setup.d.ts.map +0 -1
- package/dist/utils/schema.d.ts +0 -28
- package/dist/utils/schema.d.ts.map +0 -1
- package/dist/utils/styles.d.ts +0 -3
- 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
|
+
})
|