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/ai.ts ADDED
@@ -0,0 +1,264 @@
1
+ /**
2
+ * AI() wrapper for components with intelligent prop generation
3
+ *
4
+ * The AI() function wraps a component definition and automatically
5
+ * generates missing props using AI when the component is rendered.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import type { SimpleSchema } from 'ai-functions'
11
+ import type {
12
+ PropSchema,
13
+ AIComponentOptions,
14
+ AIComponent,
15
+ AIPropsConfig,
16
+ } from './types.js'
17
+ import { generateProps, mergeWithGenerated } from './generate.js'
18
+
19
+ /**
20
+ * Create an AI-powered component wrapper
21
+ *
22
+ * The returned function accepts partial props and generates
23
+ * any missing props using AI based on the schema.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const UserCard = AI({
28
+ * schema: {
29
+ * name: 'Full name of the user',
30
+ * bio: 'A short biography',
31
+ * avatar: 'URL to avatar image',
32
+ * },
33
+ * defaults: {
34
+ * avatar: 'https://example.com/default-avatar.png',
35
+ * },
36
+ * })
37
+ *
38
+ * // Generate all props
39
+ * const props = await UserCard({})
40
+ *
41
+ * // Generate only missing props
42
+ * const props2 = await UserCard({ name: 'John Doe' })
43
+ * ```
44
+ */
45
+ export function AI<P extends Record<string, unknown>>(
46
+ options: AIComponentOptions<P>
47
+ ): AIComponent<P> {
48
+ const { schema, defaults = {}, required = [], exclude = [], config = {} } = options
49
+
50
+ // Build filtered schema (exclude specified props)
51
+ const filteredSchema = filterSchema(schema, exclude as string[])
52
+
53
+ /**
54
+ * The AI component function
55
+ */
56
+ const aiComponent = async (partialProps: Partial<P>): Promise<P> => {
57
+ // Merge with defaults
58
+ const propsWithDefaults = { ...defaults, ...partialProps }
59
+
60
+ // Check if all required props are provided
61
+ const missingRequired = (required as string[]).filter(
62
+ key => propsWithDefaults[key as keyof P] === undefined
63
+ )
64
+ if (missingRequired.length > 0) {
65
+ throw new Error(
66
+ `Missing required props: ${missingRequired.join(', ')}`
67
+ )
68
+ }
69
+
70
+ // Generate missing props
71
+ const fullProps = await mergeWithGenerated<P>(
72
+ filteredSchema,
73
+ propsWithDefaults as Partial<P>,
74
+ {
75
+ model: config.model,
76
+ system: config.system,
77
+ }
78
+ )
79
+
80
+ return fullProps
81
+ }
82
+
83
+ // Attach metadata
84
+ aiComponent.schema = schema
85
+ aiComponent.config = config
86
+
87
+ // Attach helper method
88
+ aiComponent.generateProps = async (context?: Partial<P>): Promise<P> => {
89
+ return aiComponent(context || {})
90
+ }
91
+
92
+ return aiComponent as AIComponent<P>
93
+ }
94
+
95
+ /**
96
+ * Filter schema to exclude certain keys
97
+ */
98
+ function filterSchema(schema: PropSchema, exclude: string[]): PropSchema {
99
+ if (typeof schema === 'string') {
100
+ return schema
101
+ }
102
+
103
+ const filtered: Record<string, unknown> = {}
104
+ for (const [key, value] of Object.entries(schema)) {
105
+ if (!exclude.includes(key)) {
106
+ filtered[key] = value
107
+ }
108
+ }
109
+ return filtered as SimpleSchema
110
+ }
111
+
112
+ /**
113
+ * Create a typed AI component with inference
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * const ProductCard = createAIComponent<{
118
+ * title: string
119
+ * price: number
120
+ * description: string
121
+ * }>({
122
+ * schema: {
123
+ * title: 'Product title',
124
+ * price: 'Price in USD (number)',
125
+ * description: 'Product description',
126
+ * },
127
+ * })
128
+ * ```
129
+ */
130
+ export function createAIComponent<P extends Record<string, unknown>>(
131
+ options: AIComponentOptions<P>
132
+ ): AIComponent<P> {
133
+ return AI<P>(options)
134
+ }
135
+
136
+ /**
137
+ * Define props schema with type inference
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * const userSchema = definePropsSchema({
142
+ * name: 'User name',
143
+ * email: 'Email address',
144
+ * age: 'Age (number)',
145
+ * })
146
+ * ```
147
+ */
148
+ export function definePropsSchema<T extends Record<string, string | SimpleSchema>>(
149
+ schema: T
150
+ ): T {
151
+ return schema
152
+ }
153
+
154
+ /**
155
+ * Create a component factory for generating multiple instances
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * const factory = createComponentFactory({
160
+ * schema: { name: 'Product name', price: 'Price (number)' },
161
+ * })
162
+ *
163
+ * const products = await factory.generateMany([
164
+ * { category: 'electronics' },
165
+ * { category: 'clothing' },
166
+ * { category: 'food' },
167
+ * ])
168
+ * ```
169
+ */
170
+ export function createComponentFactory<P extends Record<string, unknown>>(
171
+ options: AIComponentOptions<P>
172
+ ) {
173
+ const component = AI<P>(options)
174
+
175
+ return {
176
+ component,
177
+ schema: options.schema,
178
+
179
+ /**
180
+ * Generate a single instance
181
+ */
182
+ generate: (context?: Partial<P>) => component(context || {}),
183
+
184
+ /**
185
+ * Generate multiple instances
186
+ */
187
+ generateMany: async (contexts: Partial<P>[]): Promise<P[]> => {
188
+ return Promise.all(contexts.map(ctx => component(ctx)))
189
+ },
190
+
191
+ /**
192
+ * Generate with specific overrides
193
+ */
194
+ generateWith: async (
195
+ context: Partial<P>,
196
+ overrides: Partial<P>
197
+ ): Promise<P> => {
198
+ const generated = await component(context)
199
+ return { ...generated, ...overrides }
200
+ },
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Compose multiple AI components
206
+ *
207
+ * Creates a component that combines props from multiple schemas.
208
+ *
209
+ * @example
210
+ * ```ts
211
+ * const FullProfile = composeAIComponents({
212
+ * user: userSchema,
213
+ * settings: settingsSchema,
214
+ * preferences: preferencesSchema,
215
+ * })
216
+ *
217
+ * const profile = await FullProfile({
218
+ * user: { name: 'John' },
219
+ * settings: {},
220
+ * preferences: { theme: 'dark' },
221
+ * })
222
+ * ```
223
+ */
224
+ export function composeAIComponents<
225
+ T extends Record<string, AIComponentOptions<Record<string, unknown>>>
226
+ >(
227
+ components: T
228
+ ): AIComponent<{
229
+ [K in keyof T]: T[K] extends AIComponentOptions<infer P> ? P : never
230
+ }> {
231
+ type ResultProps = {
232
+ [K in keyof T]: T[K] extends AIComponentOptions<infer P> ? P : never
233
+ }
234
+
235
+ const aiComponent = async (
236
+ partialProps: Partial<ResultProps>
237
+ ): Promise<ResultProps> => {
238
+ const results: Record<string, unknown> = {}
239
+
240
+ // Generate each component's props
241
+ await Promise.all(
242
+ Object.entries(components).map(async ([key, options]) => {
243
+ const component = AI(options as AIComponentOptions<Record<string, unknown>>)
244
+ const partial = (partialProps as Record<string, unknown>)[key] || {}
245
+ results[key] = await component(partial as Partial<Record<string, unknown>>)
246
+ })
247
+ )
248
+
249
+ return results as ResultProps
250
+ }
251
+
252
+ // Compose schemas
253
+ const composedSchema: Record<string, unknown> = {}
254
+ for (const [key, options] of Object.entries(components)) {
255
+ composedSchema[key] = options.schema
256
+ }
257
+
258
+ aiComponent.schema = composedSchema as PropSchema
259
+ aiComponent.config = {}
260
+ aiComponent.generateProps = (context?: Partial<ResultProps>) =>
261
+ aiComponent(context || {})
262
+
263
+ return aiComponent as AIComponent<ResultProps>
264
+ }
package/src/cache.ts ADDED
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Props caching for ai-props
3
+ *
4
+ * Provides in-memory caching for generated props to avoid
5
+ * redundant AI calls with the same context.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import type { PropsCache, PropsCacheEntry } from './types.js'
11
+
12
+ /**
13
+ * Default cache TTL (5 minutes)
14
+ */
15
+ export const DEFAULT_CACHE_TTL = 5 * 60 * 1000
16
+
17
+ /**
18
+ * Create a cache key from schema and context
19
+ */
20
+ export function createCacheKey(
21
+ schema: unknown,
22
+ context?: Record<string, unknown>
23
+ ): string {
24
+ const schemaStr = typeof schema === 'string' ? schema : JSON.stringify(schema)
25
+ const contextStr = context ? JSON.stringify(sortObject(context)) : ''
26
+ return `${hashString(schemaStr)}:${hashString(contextStr)}`
27
+ }
28
+
29
+ /**
30
+ * Simple string hash function
31
+ */
32
+ function hashString(str: string): string {
33
+ let hash = 0
34
+ for (let i = 0; i < str.length; i++) {
35
+ const char = str.charCodeAt(i)
36
+ hash = ((hash << 5) - hash) + char
37
+ hash = hash & hash // Convert to 32-bit integer
38
+ }
39
+ return hash.toString(36)
40
+ }
41
+
42
+ /**
43
+ * Sort object keys for consistent hashing
44
+ */
45
+ function sortObject(obj: Record<string, unknown>): Record<string, unknown> {
46
+ const sorted: Record<string, unknown> = {}
47
+ for (const key of Object.keys(obj).sort()) {
48
+ const value = obj[key]
49
+ sorted[key] = value && typeof value === 'object' && !Array.isArray(value)
50
+ ? sortObject(value as Record<string, unknown>)
51
+ : value
52
+ }
53
+ return sorted
54
+ }
55
+
56
+ /**
57
+ * In-memory props cache implementation
58
+ */
59
+ export class MemoryPropsCache implements PropsCache {
60
+ private cache = new Map<string, PropsCacheEntry>()
61
+ private ttl: number
62
+
63
+ constructor(ttl: number = DEFAULT_CACHE_TTL) {
64
+ this.ttl = ttl
65
+ }
66
+
67
+ get<T>(key: string): PropsCacheEntry<T> | undefined {
68
+ const entry = this.cache.get(key)
69
+ if (!entry) return undefined
70
+
71
+ // Check if expired
72
+ if (Date.now() - entry.timestamp > this.ttl) {
73
+ this.cache.delete(key)
74
+ return undefined
75
+ }
76
+
77
+ return entry as PropsCacheEntry<T>
78
+ }
79
+
80
+ set<T>(key: string, props: T): void {
81
+ this.cache.set(key, {
82
+ props,
83
+ timestamp: Date.now(),
84
+ key
85
+ })
86
+ }
87
+
88
+ delete(key: string): boolean {
89
+ return this.cache.delete(key)
90
+ }
91
+
92
+ clear(): void {
93
+ this.cache.clear()
94
+ }
95
+
96
+ get size(): number {
97
+ return this.cache.size
98
+ }
99
+
100
+ /**
101
+ * Remove expired entries
102
+ */
103
+ cleanup(): number {
104
+ const now = Date.now()
105
+ let removed = 0
106
+
107
+ for (const [key, entry] of this.cache) {
108
+ if (now - entry.timestamp > this.ttl) {
109
+ this.cache.delete(key)
110
+ removed++
111
+ }
112
+ }
113
+
114
+ return removed
115
+ }
116
+
117
+ /**
118
+ * Get all entries (for debugging)
119
+ */
120
+ entries(): IterableIterator<[string, PropsCacheEntry]> {
121
+ return this.cache.entries()
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Global default cache instance
127
+ */
128
+ let defaultCache: MemoryPropsCache | null = null
129
+
130
+ /**
131
+ * Get or create the default cache
132
+ */
133
+ export function getDefaultCache(): MemoryPropsCache {
134
+ if (!defaultCache) {
135
+ defaultCache = new MemoryPropsCache()
136
+ }
137
+ return defaultCache
138
+ }
139
+
140
+ /**
141
+ * Configure the default cache
142
+ */
143
+ export function configureCache(ttl: number): void {
144
+ defaultCache = new MemoryPropsCache(ttl)
145
+ }
146
+
147
+ /**
148
+ * Clear the default cache
149
+ */
150
+ export function clearCache(): void {
151
+ if (defaultCache) {
152
+ defaultCache.clear()
153
+ }
154
+ }
155
+
156
+ /**
157
+ * LRU (Least Recently Used) cache implementation
158
+ * For scenarios where memory usage needs to be bounded
159
+ */
160
+ export class LRUPropsCache implements PropsCache {
161
+ private cache = new Map<string, PropsCacheEntry>()
162
+ private maxSize: number
163
+ private ttl: number
164
+
165
+ constructor(maxSize: number = 100, ttl: number = DEFAULT_CACHE_TTL) {
166
+ this.maxSize = maxSize
167
+ this.ttl = ttl
168
+ }
169
+
170
+ get<T>(key: string): PropsCacheEntry<T> | undefined {
171
+ const entry = this.cache.get(key)
172
+ if (!entry) return undefined
173
+
174
+ // Check if expired
175
+ if (Date.now() - entry.timestamp > this.ttl) {
176
+ this.cache.delete(key)
177
+ return undefined
178
+ }
179
+
180
+ // Move to end (most recently used)
181
+ this.cache.delete(key)
182
+ this.cache.set(key, entry)
183
+
184
+ return entry as PropsCacheEntry<T>
185
+ }
186
+
187
+ set<T>(key: string, props: T): void {
188
+ // Remove oldest entries if at capacity
189
+ while (this.cache.size >= this.maxSize) {
190
+ const oldest = this.cache.keys().next().value
191
+ if (oldest) {
192
+ this.cache.delete(oldest)
193
+ } else {
194
+ break
195
+ }
196
+ }
197
+
198
+ this.cache.set(key, {
199
+ props,
200
+ timestamp: Date.now(),
201
+ key
202
+ })
203
+ }
204
+
205
+ delete(key: string): boolean {
206
+ return this.cache.delete(key)
207
+ }
208
+
209
+ clear(): void {
210
+ this.cache.clear()
211
+ }
212
+
213
+ get size(): number {
214
+ return this.cache.size
215
+ }
216
+ }