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
package/src/types.ts ADDED
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Type definitions for ai-props
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ import type { SimpleSchema } from 'ai-functions'
8
+
9
+ /**
10
+ * Configuration for AI prop generation
11
+ */
12
+ export interface AIPropsConfig {
13
+ /** Model to use for generation (default: 'sonnet') */
14
+ model?: string
15
+ /** System prompt for generation context */
16
+ system?: string
17
+ /** Whether to cache generated props */
18
+ cache?: boolean
19
+ /** Cache TTL in milliseconds (default: 5 minutes) */
20
+ cacheTTL?: number
21
+ /** Custom generation function */
22
+ generate?: <T>(schema: SimpleSchema, context: Record<string, unknown>) => Promise<T>
23
+ }
24
+
25
+ /**
26
+ * Schema definition for component props
27
+ * Can be a SimpleSchema or a named type string
28
+ */
29
+ export type PropSchema = SimpleSchema | string
30
+
31
+ /**
32
+ * Options for prop generation
33
+ */
34
+ export interface GeneratePropsOptions {
35
+ /** Schema defining the props to generate */
36
+ schema: PropSchema
37
+ /** Partial props to use as context */
38
+ context?: Record<string, unknown>
39
+ /** Additional prompt context */
40
+ prompt?: string
41
+ /** Model to use */
42
+ model?: string
43
+ /** System prompt */
44
+ system?: string
45
+ }
46
+
47
+ /**
48
+ * Result of prop generation
49
+ */
50
+ export interface GeneratePropsResult<T> {
51
+ /** Generated props */
52
+ props: T
53
+ /** Whether props were from cache */
54
+ cached: boolean
55
+ /** Generation metadata */
56
+ metadata?: {
57
+ model: string
58
+ tokens?: number
59
+ duration?: number
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Cache entry for generated props
65
+ */
66
+ export interface PropsCacheEntry<T = unknown> {
67
+ props: T
68
+ timestamp: number
69
+ key: string
70
+ }
71
+
72
+ /**
73
+ * Props cache interface
74
+ */
75
+ export interface PropsCache {
76
+ get<T>(key: string): PropsCacheEntry<T> | undefined
77
+ set<T>(key: string, props: T): void
78
+ delete(key: string): boolean
79
+ clear(): void
80
+ size: number
81
+ }
82
+
83
+ /**
84
+ * Hook result for useAIProps
85
+ */
86
+ export interface UseAIPropsResult<T> {
87
+ /** Generated props (null while loading) */
88
+ props: T | null
89
+ /** Whether generation is in progress */
90
+ loading: boolean
91
+ /** Error if generation failed */
92
+ error: Error | null
93
+ /** Regenerate props */
94
+ regenerate: () => Promise<void>
95
+ /** Whether props were from cache */
96
+ cached: boolean
97
+ }
98
+
99
+ /**
100
+ * Options for useAIProps hook
101
+ */
102
+ export interface UseAIPropsOptions<T> extends Omit<GeneratePropsOptions, 'context'> {
103
+ /** Partial props to merge and use as context */
104
+ partialProps?: Partial<T>
105
+ /** Skip generation if all required props are provided */
106
+ skipIfComplete?: boolean
107
+ /** Dependencies that trigger regeneration */
108
+ deps?: unknown[]
109
+ }
110
+
111
+ /**
112
+ * Component wrapper options for AI()
113
+ */
114
+ export interface AIComponentOptions<P> {
115
+ /** Schema for the component props */
116
+ schema: PropSchema
117
+ /** Default props */
118
+ defaults?: Partial<P>
119
+ /** Props that are always required (never generated) */
120
+ required?: (keyof P)[]
121
+ /** Props to exclude from generation */
122
+ exclude?: (keyof P)[]
123
+ /** AI generation config */
124
+ config?: AIPropsConfig
125
+ }
126
+
127
+ /**
128
+ * Enhanced component type with AI capabilities
129
+ */
130
+ export interface AIComponent<P> {
131
+ (props: Partial<P>): Promise<P>
132
+ /** Original schema */
133
+ schema: PropSchema
134
+ /** Generate props without rendering */
135
+ generateProps: (context?: Partial<P>) => Promise<P>
136
+ /** Configuration */
137
+ config: AIPropsConfig
138
+ }
139
+
140
+ /**
141
+ * Type helper to extract props type from a schema
142
+ */
143
+ export type InferProps<S extends PropSchema> = S extends SimpleSchema
144
+ ? S extends string
145
+ ? string
146
+ : S extends { [key: string]: SimpleSchema }
147
+ ? { [K in keyof S]: InferProps<S[K]> }
148
+ : unknown
149
+ : unknown
150
+
151
+ /**
152
+ * Validation result for props
153
+ */
154
+ export interface ValidationResult {
155
+ valid: boolean
156
+ errors: ValidationError[]
157
+ }
158
+
159
+ /**
160
+ * Individual validation error
161
+ */
162
+ export interface ValidationError {
163
+ path: string
164
+ message: string
165
+ expected?: string
166
+ received?: unknown
167
+ }
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Validation utilities for ai-props
3
+ *
4
+ * Provides prop validation against schemas.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ import type { SimpleSchema } from 'ai-functions'
10
+ import type { PropSchema, ValidationResult, ValidationError } from './types.js'
11
+
12
+ /**
13
+ * Validate props against a schema
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const result = validateProps(
18
+ * { name: 'John', age: '25' }, // props
19
+ * { name: 'Name', age: 'Age (number)' } // schema
20
+ * )
21
+ *
22
+ * if (!result.valid) {
23
+ * console.log(result.errors)
24
+ * // [{ path: 'age', message: 'Expected number, got string' }]
25
+ * }
26
+ * ```
27
+ */
28
+ export function validateProps(
29
+ props: Record<string, unknown>,
30
+ schema: PropSchema
31
+ ): ValidationResult {
32
+ const errors: ValidationError[] = []
33
+
34
+ if (typeof schema === 'string') {
35
+ // Simple string schema - just check if value exists
36
+ if (!props.value) {
37
+ errors.push({
38
+ path: 'value',
39
+ message: 'Value is required',
40
+ expected: 'string',
41
+ received: props.value,
42
+ })
43
+ }
44
+ return { valid: errors.length === 0, errors }
45
+ }
46
+
47
+ // Object schema - validate each key
48
+ for (const [key, schemaDef] of Object.entries(schema)) {
49
+ const value = props[key]
50
+ const keyErrors = validateValue(key, value, schemaDef as string | SimpleSchema)
51
+ errors.push(...keyErrors)
52
+ }
53
+
54
+ return { valid: errors.length === 0, errors }
55
+ }
56
+
57
+ /**
58
+ * Validate a single value against a schema definition
59
+ */
60
+ function validateValue(
61
+ path: string,
62
+ value: unknown,
63
+ schema: string | SimpleSchema
64
+ ): ValidationError[] {
65
+ const errors: ValidationError[] = []
66
+
67
+ // String schema with type hint
68
+ if (typeof schema === 'string') {
69
+ const expectedType = extractTypeFromSchema(schema)
70
+
71
+ if (value === undefined || value === null) {
72
+ // Optional unless marked required
73
+ return errors
74
+ }
75
+
76
+ if (expectedType && !checkType(value, expectedType)) {
77
+ errors.push({
78
+ path,
79
+ message: `Expected ${expectedType}, got ${typeof value}`,
80
+ expected: expectedType,
81
+ received: value,
82
+ })
83
+ }
84
+
85
+ return errors
86
+ }
87
+
88
+ // Array schema
89
+ if (Array.isArray(schema)) {
90
+ if (!Array.isArray(value)) {
91
+ if (value !== undefined && value !== null) {
92
+ errors.push({
93
+ path,
94
+ message: `Expected array, got ${typeof value}`,
95
+ expected: 'array',
96
+ received: value,
97
+ })
98
+ }
99
+ return errors
100
+ }
101
+
102
+ // Validate array items if schema has item definition
103
+ if (schema.length > 0) {
104
+ const itemSchema = schema[0]
105
+ for (let i = 0; i < value.length; i++) {
106
+ const itemErrors = validateValue(
107
+ `${path}[${i}]`,
108
+ value[i],
109
+ itemSchema as string | SimpleSchema
110
+ )
111
+ errors.push(...itemErrors)
112
+ }
113
+ }
114
+
115
+ return errors
116
+ }
117
+
118
+ // Object schema
119
+ if (typeof schema === 'object' && schema !== null) {
120
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
121
+ if (value !== undefined && value !== null) {
122
+ errors.push({
123
+ path,
124
+ message: `Expected object, got ${Array.isArray(value) ? 'array' : typeof value}`,
125
+ expected: 'object',
126
+ received: value,
127
+ })
128
+ }
129
+ return errors
130
+ }
131
+
132
+ // Recursively validate nested object
133
+ for (const [key, nestedSchema] of Object.entries(schema)) {
134
+ const nestedValue = (value as Record<string, unknown>)[key]
135
+ const nestedErrors = validateValue(
136
+ `${path}.${key}`,
137
+ nestedValue,
138
+ nestedSchema as string | SimpleSchema
139
+ )
140
+ errors.push(...nestedErrors)
141
+ }
142
+ }
143
+
144
+ return errors
145
+ }
146
+
147
+ /**
148
+ * Extract type hint from schema string
149
+ * e.g., "Age (number)" -> "number"
150
+ */
151
+ function extractTypeFromSchema(schema: string): string | null {
152
+ const match = schema.match(/\((\w+)\)\s*$/)
153
+ if (match) {
154
+ return match[1]!.toLowerCase()
155
+ }
156
+
157
+ // Check for enum syntax
158
+ if (schema.includes(' | ')) {
159
+ return 'enum'
160
+ }
161
+
162
+ return null
163
+ }
164
+
165
+ /**
166
+ * Check if a value matches an expected type
167
+ */
168
+ function checkType(value: unknown, expectedType: string): boolean {
169
+ switch (expectedType) {
170
+ case 'string':
171
+ return typeof value === 'string'
172
+ case 'number':
173
+ case 'integer':
174
+ return typeof value === 'number'
175
+ case 'boolean':
176
+ return typeof value === 'boolean'
177
+ case 'array':
178
+ return Array.isArray(value)
179
+ case 'object':
180
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
181
+ case 'date':
182
+ return typeof value === 'string' || value instanceof Date
183
+ case 'enum':
184
+ return typeof value === 'string'
185
+ default:
186
+ return true
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Check if all required props are present
192
+ */
193
+ export function hasRequiredProps<P extends Record<string, unknown>>(
194
+ props: Partial<P>,
195
+ required: (keyof P)[]
196
+ ): boolean {
197
+ return required.every(key => props[key] !== undefined)
198
+ }
199
+
200
+ /**
201
+ * Get list of missing required props
202
+ */
203
+ export function getMissingProps<P extends Record<string, unknown>>(
204
+ props: Partial<P>,
205
+ required: (keyof P)[]
206
+ ): (keyof P)[] {
207
+ return required.filter(key => props[key] === undefined)
208
+ }
209
+
210
+ /**
211
+ * Check if props are complete according to schema
212
+ */
213
+ export function isComplete(
214
+ props: Record<string, unknown>,
215
+ schema: PropSchema
216
+ ): boolean {
217
+ if (typeof schema === 'string') {
218
+ return props.value !== undefined
219
+ }
220
+
221
+ return Object.keys(schema).every(key => props[key] !== undefined)
222
+ }
223
+
224
+ /**
225
+ * Get list of missing props according to schema
226
+ */
227
+ export function getMissingFromSchema(
228
+ props: Record<string, unknown>,
229
+ schema: PropSchema
230
+ ): string[] {
231
+ if (typeof schema === 'string') {
232
+ return props.value === undefined ? ['value'] : []
233
+ }
234
+
235
+ return Object.keys(schema).filter(key => props[key] === undefined)
236
+ }
237
+
238
+ /**
239
+ * Sanitize props by removing extra keys not in schema
240
+ */
241
+ export function sanitizeProps<P extends Record<string, unknown>>(
242
+ props: P,
243
+ schema: PropSchema
244
+ ): Partial<P> {
245
+ if (typeof schema === 'string') {
246
+ return { value: (props as Record<string, unknown>).value } as unknown as Partial<P>
247
+ }
248
+
249
+ const schemaKeys = new Set(Object.keys(schema))
250
+ const sanitized: Record<string, unknown> = {}
251
+
252
+ for (const [key, value] of Object.entries(props)) {
253
+ if (schemaKeys.has(key)) {
254
+ sanitized[key] = value
255
+ }
256
+ }
257
+
258
+ return sanitized as Partial<P>
259
+ }
260
+
261
+ /**
262
+ * Merge props with defaults, respecting schema types
263
+ */
264
+ export function mergeWithDefaults<P extends Record<string, unknown>>(
265
+ props: Partial<P>,
266
+ defaults: Partial<P>,
267
+ schema: PropSchema
268
+ ): Partial<P> {
269
+ const result = { ...defaults, ...props }
270
+
271
+ // Ensure types match schema
272
+ if (typeof schema !== 'string') {
273
+ for (const [key, schemaDef] of Object.entries(schema)) {
274
+ if (result[key as keyof P] === undefined) continue
275
+
276
+ const expectedType = typeof schemaDef === 'string'
277
+ ? extractTypeFromSchema(schemaDef)
278
+ : null
279
+
280
+ if (expectedType) {
281
+ result[key as keyof P] = coerceType(
282
+ result[key as keyof P],
283
+ expectedType
284
+ ) as P[keyof P]
285
+ }
286
+ }
287
+ }
288
+
289
+ return result
290
+ }
291
+
292
+ /**
293
+ * Attempt to coerce a value to an expected type
294
+ */
295
+ function coerceType(value: unknown, expectedType: string): unknown {
296
+ if (value === undefined || value === null) return value
297
+
298
+ switch (expectedType) {
299
+ case 'string':
300
+ return String(value)
301
+ case 'number':
302
+ return typeof value === 'number' ? value : Number(value)
303
+ case 'integer':
304
+ return typeof value === 'number' ? Math.floor(value) : parseInt(String(value), 10)
305
+ case 'boolean':
306
+ return Boolean(value)
307
+ default:
308
+ return value
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Create a props validator function
314
+ */
315
+ export function createValidator<P extends Record<string, unknown>>(
316
+ schema: PropSchema
317
+ ): (props: Partial<P>) => ValidationResult {
318
+ return (props: Partial<P>) => validateProps(props as Record<string, unknown>, schema)
319
+ }
320
+
321
+ /**
322
+ * Assert props are valid, throwing on error
323
+ */
324
+ export function assertValidProps(
325
+ props: Record<string, unknown>,
326
+ schema: PropSchema
327
+ ): void {
328
+ const result = validateProps(props, schema)
329
+ if (!result.valid) {
330
+ const messages = result.errors.map(e => `${e.path}: ${e.message}`).join(', ')
331
+ throw new Error(`Invalid props: ${messages}`)
332
+ }
333
+ }