@happyvertical/smrt-core 0.36.5 → 0.36.7
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/dist/collection.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/consumer-plugin/index.d.ts.map +1 -1
- package/dist/consumer-plugin/index.js +7 -3
- package/dist/consumer-plugin/index.js.map +1 -1
- package/dist/database.d.ts +3 -3
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js.map +1 -1
- package/dist/embeddings/provider.d.ts +14 -2
- package/dist/embeddings/provider.d.ts.map +1 -1
- package/dist/embeddings/provider.js +3 -3
- package/dist/embeddings/provider.js.map +1 -1
- package/dist/embeddings/storage.js.map +1 -1
- package/dist/errors.d.ts +22 -22
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +5 -4
- package/dist/errors.js.map +1 -1
- package/dist/generators/cli.d.ts +4 -22
- package/dist/generators/cli.d.ts.map +1 -1
- package/dist/generators/cli.js +9 -5
- package/dist/generators/cli.js.map +1 -1
- package/dist/generators/mcp-runtime-template.d.ts +1 -1
- package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
- package/dist/generators/mcp-runtime-template.js.map +1 -1
- package/dist/generators/mcp.d.ts +16 -4
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +25 -9
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/rest.d.ts +6 -5
- package/dist/generators/rest.d.ts.map +1 -1
- package/dist/generators/rest.js +8 -5
- package/dist/generators/rest.js.map +1 -1
- package/dist/generators/swagger.d.ts +12 -2
- package/dist/generators/swagger.d.ts.map +1 -1
- package/dist/generators/swagger.js +6 -3
- package/dist/generators/swagger.js.map +1 -1
- package/dist/knowledge.d.ts +12 -1
- package/dist/knowledge.d.ts.map +1 -1
- package/dist/knowledge.js.map +1 -1
- package/dist/lazy-config.d.ts.map +1 -1
- package/dist/lazy-config.js.map +1 -1
- package/dist/manifest/generator.d.ts.map +1 -1
- package/dist/manifest/generator.js +14 -12
- package/dist/manifest/generator.js.map +1 -1
- package/dist/manifest/manager.d.ts +3 -3
- package/dist/manifest/manager.d.ts.map +1 -1
- package/dist/manifest/manager.js.map +1 -1
- package/dist/manifest/manifest-loader.d.ts +3 -2
- package/dist/manifest/manifest-loader.d.ts.map +1 -1
- package/dist/manifest/manifest-loader.js +3 -2
- package/dist/manifest/manifest-loader.js.map +1 -1
- package/dist/manifest/static-manifest.js +2 -2
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +2 -2
- package/dist/manifest/test-manifest-stub.js +2 -2
- package/dist/manifest/test-manifest-stub.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/mcp-advisor/index.d.ts +1 -1
- package/dist/mcp-advisor/index.d.ts.map +1 -1
- package/dist/mcp-advisor/tools/get-object-config.d.ts.map +1 -1
- package/dist/mcp-advisor/types.d.ts +5 -5
- package/dist/mcp-advisor/types.d.ts.map +1 -1
- package/dist/migrations/differ.js.map +1 -1
- package/dist/scanner/manifest-generator.d.ts +11 -3
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +19 -15
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/scanner/types.d.ts +60 -6
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/schema/code-generator.d.ts.map +1 -1
- package/dist/schema/ddl/base-strategy.d.ts +2 -2
- package/dist/schema/ddl/base-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/base-strategy.js.map +1 -1
- package/dist/schema/ddl/sqlite-strategy.d.ts +1 -1
- package/dist/schema/ddl/sqlite-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/sqlite-strategy.js.map +1 -1
- package/dist/schema/ddl/types.d.ts +1 -1
- package/dist/schema/ddl/types.d.ts.map +1 -1
- package/dist/schema/generator.d.ts +27 -4
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +3 -2
- package/dist/schema/generator.js.map +1 -1
- package/dist/schema/override-system.d.ts +3 -3
- package/dist/schema/override-system.d.ts.map +1 -1
- package/dist/schema/schema-aggregator.d.ts.map +1 -1
- package/dist/schema/schema-manager.d.ts.map +1 -1
- package/dist/schema/schema-manager.js +12 -4
- package/dist/schema/schema-manager.js.map +1 -1
- package/dist/schema/types.d.ts +1 -1
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/utils.d.ts +6 -1
- package/dist/schema/utils.d.ts.map +1 -1
- package/dist/schema/utils.js +2 -1
- package/dist/schema/utils.js.map +1 -1
- package/dist/signals/sanitizer.d.ts +1 -1
- package/dist/signals/sanitizer.d.ts.map +1 -1
- package/dist/signals/sanitizer.js +12 -2
- package/dist/signals/sanitizer.js.map +1 -1
- package/dist/smrt-knowledge.json +4 -4
- package/dist/system/types.d.ts +2 -2
- package/dist/system/types.d.ts.map +1 -1
- package/dist/system-fields.d.ts +5 -43
- package/dist/system-fields.d.ts.map +1 -1
- package/dist/system-fields.js +2 -1
- package/dist/system-fields.js.map +1 -1
- package/dist/test-utils.d.ts +39 -13
- package/dist/test-utils.d.ts.map +1 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js.map +1 -1
- package/dist/tools/tool-executor.d.ts +19 -5
- package/dist/tools/tool-executor.d.ts.map +1 -1
- package/dist/tools/tool-executor.js +4 -2
- package/dist/tools/tool-executor.js.map +1 -1
- package/dist/tools/tool-generator.d.ts +8 -1
- package/dist/tools/tool-generator.d.ts.map +1 -1
- package/dist/tools/tool-generator.js +10 -11
- package/dist/tools/tool-generator.js.map +1 -1
- package/dist/utils/json.js.map +1 -1
- package/dist/utils.d.ts +16 -8
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +9 -7
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +4 -3
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/templates/default-ui.ts +20 -6
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest.js","sources":["../../src/generators/rest.ts"],"sourcesContent":["/**\n * High-performance REST API generator for smrt objects using Node.js HTTP server\n *\n * Designed for minimal bundle size and maximum performance\n */\n\nimport http from 'node:http';\nimport type { SmrtCollection } from '../collection';\nimport type { SmrtObject } from '../object';\nimport { ObjectRegistry } from '../registry';\n\nexport interface APIConfig {\n basePath?: string;\n enableCors?: boolean;\n /**\n * Explicit CORS origin allowlist (#1540). Required when `enableCors` is true —\n * the generator never emits `Access-Control-Allow-Origin: *`. A request's\n * `Origin` is echoed only when it appears here.\n */\n allowedOrigins?: string[];\n customRoutes?: Record<string, (req: Request) => Promise<Response>>;\n authMiddleware?: (\n objectName: string,\n action: string,\n ) => (req: Request) => Promise<Request | Response>;\n port?: number;\n hostname?: string;\n}\n\nexport interface APIContext {\n db?: any;\n ai?: any;\n user?: {\n id: string;\n username?: string;\n roles?: string[];\n };\n}\n\n/**\n * High-performance API generator using native Bun\n */\nexport class APIGenerator {\n private config: APIConfig;\n private collections = new Map<string, SmrtCollection<any>>();\n private context: APIContext;\n\n constructor(config: APIConfig = {}, context: APIContext = {}) {\n this.config = {\n basePath: '/api/v1',\n // Security defaults (#1540): bind to loopback and keep CORS off unless an\n // explicit origin allowlist is supplied. No `Access-Control-Allow-Origin: *`.\n enableCors: false,\n port: 3000,\n hostname: '127.0.0.1',\n ...config,\n };\n this.context = context;\n }\n\n /**\n * Register a pre-configured collection instance for API exposure\n *\n * @param name - URL path segment for the collection (e.g., 'products' for /api/products)\n * @param collection - Pre-initialized SmrtCollection instance\n */\n registerCollection(name: string, collection: SmrtCollection<any>): void {\n this.collections.set(name, collection);\n }\n\n /**\n * Create Node.js HTTP server with all routes\n */\n createServer(): { server: any; url: string } {\n const server = http.createServer(async (req, res) => {\n try {\n const request = await this.nodeRequestToWebRequest(req);\n const response = await this.handleRequest(request);\n await this.webResponseToNodeResponse(response, res);\n } catch (_error) {\n res.statusCode = 500;\n res.end('Internal Server Error');\n }\n });\n\n server.listen(this.config.port, this.config.hostname);\n\n return {\n server,\n url: `http://${this.config.hostname}:${this.config.port}`,\n };\n }\n\n /**\n * Convert stream to string\n */\n private async streamToString(stream: http.IncomingMessage): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of stream) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n return Buffer.concat(chunks).toString('utf-8');\n }\n\n /**\n * Convert Node.js IncomingMessage to Web Request\n */\n private async nodeRequestToWebRequest(\n req: http.IncomingMessage,\n ): Promise<Request> {\n const url = `http://${this.config.hostname}:${this.config.port}${req.url}`;\n const method = req.method || 'GET';\n const headers = new Headers();\n\n for (const [key, value] of Object.entries(req.headers)) {\n if (value) {\n headers.set(key, Array.isArray(value) ? value[0] : value);\n }\n }\n\n let body: string | undefined;\n if (method !== 'GET' && method !== 'HEAD') {\n body = await this.streamToString(req);\n }\n\n return new Request(url, {\n method,\n headers,\n body,\n });\n }\n\n /**\n * Convert Web Response to Node.js ServerResponse\n */\n private async webResponseToNodeResponse(\n webResponse: Response,\n res: http.ServerResponse,\n ): Promise<void> {\n res.statusCode = webResponse.status;\n\n // Set headers\n webResponse.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n\n // Send body\n if (webResponse.body) {\n const reader = webResponse.body.getReader();\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n res.write(value);\n }\n }\n\n res.end();\n }\n\n /**\n * Generate fetch handler function (for serverless environments)\n */\n generateHandler(): (req: Request) => Promise<Response> {\n return (req) => this.handleRequest(req);\n }\n\n /**\n * Main request handler using native Bun APIs\n */\n private async handleRequest(req: Request): Promise<Response> {\n const url = new URL(req.url);\n\n // Handle CORS preflight\n if (req.method === 'OPTIONS' && this.config.enableCors) {\n return this.createCorsResponse(req);\n }\n\n // Handle custom routes first\n if (this.config.customRoutes) {\n for (const [path, handler] of Object.entries(this.config.customRoutes)) {\n if (url.pathname === `${this.config.basePath}${path}`) {\n const response = await handler(req);\n return this.addCorsHeaders(response, req);\n }\n }\n }\n\n // Handle object routes\n if (url.pathname.startsWith(this.config.basePath || '')) {\n const response = await this.handleObjectRoute(req, url);\n return this.addCorsHeaders(response, req);\n }\n\n // Not found\n return this.createErrorResponse(404, 'Not found');\n }\n\n /**\n * Handle CRUD routes for SMRT objects\n */\n private async handleObjectRoute(req: Request, url: URL): Promise<Response> {\n const pathParts = url.pathname\n .replace(this.config.basePath || '', '')\n .split('/')\n .filter(Boolean);\n\n if (pathParts.length === 0) {\n return this.createErrorResponse(400, 'Object type required');\n }\n\n const objectType = pathParts[0];\n const objectId = pathParts[1];\n\n // Check for explicitly registered collection first\n if (this.collections.has(objectType)) {\n const collection = this.collections.get(objectType);\n if (!collection) throw new Error(`Collection ${objectType} not found`);\n\n const objectName = this.getCollectionObjectName(collection) || objectType;\n\n // Apply auth middleware if configured\n if (this.config.authMiddleware) {\n const authCheck = this.config.authMiddleware(\n objectName,\n req.method.toLowerCase(),\n );\n const authResult = await authCheck(req);\n if (authResult instanceof Response) {\n return authResult; // Auth failed\n }\n // Auth passed, use the potentially modified request\n req = authResult;\n } else if (!this.isRoutePublic(objectName, req.method)) {\n // Fail-closed (#1540): no auth middleware wired and the object isn't\n // marked `@smrt({ api: { public } })` → refuse rather than serve open.\n return this.createErrorResponse(401, 'Authentication required');\n }\n\n // Use registered collection directly\n return await this.executeCrudOperation(\n req,\n collection,\n objectId,\n url,\n objectName,\n );\n }\n\n // Fall back to auto-discovery via ObjectRegistry\n const registeredClasses = ObjectRegistry.getAllClasses();\n const pluralName = this.pluralize(objectType);\n\n let classInfo: any = null;\n for (const [_key, info] of registeredClasses) {\n // Issue #951: Use simple name (info.name) for URL matching, not the map key\n // which may be a qualified name like '@happyvertical/smrt-events:Event'\n if (this.pluralize((info.name || _key).toLowerCase()) === pluralName) {\n classInfo = info;\n break;\n }\n }\n\n if (!classInfo) {\n return this.createErrorResponse(\n 404,\n `Object type '${objectType}' not found`,\n );\n }\n\n // Apply auth middleware if configured\n if (this.config.authMiddleware) {\n const authCheck = this.config.authMiddleware(\n classInfo.name,\n req.method.toLowerCase(),\n );\n const authResult = await authCheck(req);\n if (authResult instanceof Response) {\n return authResult; // Auth failed\n }\n // Auth passed, use the potentially modified request\n req = authResult;\n } else if (!this.isRoutePublic(classInfo.name, req.method)) {\n // Fail-closed (#1540): see registered-collection branch above.\n return this.createErrorResponse(401, 'Authentication required');\n }\n\n // Get or create collection\n const collection = this.getCollection(classInfo);\n\n return await this.executeCrudOperation(\n req,\n collection,\n objectId,\n url,\n classInfo.name,\n );\n }\n\n /**\n * Execute CRUD operation on a collection\n */\n private async executeCrudOperation(\n req: Request,\n collection: SmrtCollection<any>,\n objectId: string | undefined,\n url: URL,\n objectName?: string,\n ): Promise<Response> {\n try {\n const action = this.getCrudAction(req.method, objectId);\n if (action && !this.isApiActionEnabled(objectName, action)) {\n return this.createErrorResponse(405, 'Method not allowed');\n }\n\n // Handle special /count endpoint\n if (objectId === 'count' && req.method === 'GET') {\n return await this.handleCount(collection, url.searchParams);\n }\n\n // Route to appropriate CRUD operation\n switch (req.method) {\n case 'GET':\n return objectId\n ? await this.handleGet(collection, objectId)\n : await this.handleList(collection, url.searchParams);\n\n case 'POST':\n return await this.handleCreate(collection, req, objectName);\n\n case 'PUT':\n case 'PATCH':\n if (!objectId) {\n return this.createErrorResponse(\n 400,\n 'Object ID required for update',\n );\n }\n return await this.handleUpdate(collection, objectId, req, objectName);\n\n case 'DELETE':\n if (!objectId) {\n return this.createErrorResponse(\n 400,\n 'Object ID required for delete',\n );\n }\n return await this.handleDelete(collection, objectId);\n\n default:\n return this.createErrorResponse(405, 'Method not allowed');\n }\n } catch (error) {\n console.error('API Error:', error);\n return this.createErrorResponse(500, 'Internal server error');\n }\n }\n\n private getCrudAction(\n method: string,\n objectId: string | undefined,\n ): 'list' | 'get' | 'create' | 'update' | 'delete' | null {\n switch (method) {\n case 'GET':\n return objectId && objectId !== 'count' ? 'get' : 'list';\n case 'POST':\n return 'create';\n case 'PUT':\n case 'PATCH':\n return 'update';\n case 'DELETE':\n return 'delete';\n default:\n return null;\n }\n }\n\n /**\n * Fail-closed authorization posture (#1540). Returns true only when the\n * object opts out of auth via `@smrt({ api: { public } })`:\n * - `public: true` → all methods are public.\n * - `public: 'read'` → only safe (GET) methods are public.\n * Everything else requires an `authMiddleware` to be configured.\n */\n private isRoutePublic(\n objectName: string | undefined,\n method: string,\n ): boolean {\n if (!objectName) return false;\n const apiConfig = ObjectRegistry.getConfig(objectName)?.api;\n if (!apiConfig || typeof apiConfig !== 'object') return false;\n const publicAccess = (apiConfig as { public?: boolean | 'read' }).public;\n if (publicAccess === true) return true;\n if (publicAccess === 'read') return method.toUpperCase() === 'GET';\n return false;\n }\n\n private isApiActionEnabled(\n objectName: string | undefined,\n action: 'list' | 'get' | 'create' | 'update' | 'delete',\n ): boolean {\n if (!objectName) {\n return true;\n }\n\n const config = ObjectRegistry.getConfig(objectName);\n const apiConfig = config.api;\n\n if (apiConfig === false) {\n return false;\n }\n\n if (apiConfig && typeof apiConfig === 'object') {\n if (apiConfig.include && !apiConfig.include.includes(action)) {\n return false;\n }\n\n if (apiConfig.exclude?.includes(action)) {\n return false;\n }\n }\n\n return true;\n }\n\n private getCollectionObjectName(\n collection: SmrtCollection<any>,\n ): string | undefined {\n const itemClass =\n (collection as any)._itemClass ||\n (collection.constructor as any)?._itemClass;\n\n if (!itemClass) {\n return undefined;\n }\n\n const registered = ObjectRegistry.getClassByConstructor(itemClass);\n return registered?.qualifiedName || registered?.name || itemClass.name;\n }\n\n /**\n * Handle GET /objects/:id\n */\n private async handleGet(\n collection: SmrtCollection<any>,\n id: string,\n ): Promise<Response> {\n const object = await collection.get(id);\n if (!object) {\n return this.createErrorResponse(404, 'Object not found');\n }\n return this.createJsonResponse(this.toPublicData(object));\n }\n\n /**\n * Handle GET /objects (list with query params)\n */\n private async handleList(\n collection: SmrtCollection<any>,\n params: URLSearchParams,\n ): Promise<Response> {\n const limit = Number.parseInt(params.get('limit') || '50', 10);\n const offset = Number.parseInt(params.get('offset') || '0', 10);\n const orderBy = params.get('orderBy') || 'created_at DESC';\n\n // Build where clause from query params\n // Convert REST-style operators (price[gt]) to SQL-style (price >)\n const where: any = {};\n for (const [key, value] of params.entries()) {\n if (!['limit', 'offset', 'orderBy'].includes(key)) {\n // Parse REST operator format: field[operator]\n const match = key.match(/^(.+)\\[(.+)\\]$/);\n if (match) {\n const field = match[1];\n const operator = match[2];\n // Map REST operators to SQL operators\n const operatorMap: Record<string, string> = {\n gt: '>',\n gte: '>=',\n lt: '<',\n lte: '<=',\n ne: '!=',\n in: 'in',\n like: 'like',\n };\n const sqlOperator = operatorMap[operator] || operator;\n const sqlKey = `${field} ${sqlOperator}`;\n // Handle 'in' operator - convert comma-separated string to array\n where[sqlKey] = operator === 'in' ? value.split(',') : value;\n } else {\n where[key] = value;\n }\n }\n }\n\n const objects = await collection.list({\n where: Object.keys(where).length > 0 ? where : undefined,\n limit,\n offset,\n orderBy,\n });\n\n return this.createJsonResponse(\n objects.map((object: any) => this.toPublicData(object)),\n );\n }\n\n /**\n * Handle GET /objects/count\n */\n private async handleCount(\n collection: SmrtCollection<any>,\n params: URLSearchParams,\n ): Promise<Response> {\n // Build where clause from query params (same logic as handleList)\n const where: any = {};\n for (const [key, value] of params.entries()) {\n // Parse REST operator format: field[operator]\n const match = key.match(/^(.+)\\[(.+)\\]$/);\n if (match) {\n const field = match[1];\n const operator = match[2];\n // Map REST operators to SQL operators\n const operatorMap: Record<string, string> = {\n gt: '>',\n gte: '>=',\n lt: '<',\n lte: '<=',\n ne: '!=',\n in: 'in',\n like: 'like',\n };\n const sqlOperator = operatorMap[operator] || operator;\n const sqlKey = `${field} ${sqlOperator}`;\n // Handle 'in' operator - convert comma-separated string to array\n where[sqlKey] = operator === 'in' ? value.split(',') : value;\n } else {\n where[key] = value;\n }\n }\n\n const count = await collection.count({\n where: Object.keys(where).length > 0 ? where : undefined,\n });\n\n return this.createJsonResponse({ count });\n }\n\n /**\n * Handle POST /objects\n */\n private async handleCreate(\n collection: SmrtCollection<any>,\n req: Request,\n objectName?: string,\n ): Promise<Response> {\n const data = this.applyWritablePolicy(objectName, await req.json());\n const object = await collection.create({ ...data, _skipLoad: true });\n await object.save();\n return this.createJsonResponse(this.toPublicData(object), 201);\n }\n\n /**\n * Handle PUT/PATCH /objects/:id\n */\n private async handleUpdate(\n collection: SmrtCollection<any>,\n id: string,\n req: Request,\n objectName?: string,\n ): Promise<Response> {\n const data = this.applyWritablePolicy(objectName, await req.json());\n const object = await collection.get(id);\n\n if (!object) {\n return this.createErrorResponse(404, 'Object not found');\n }\n\n // Update object properties\n Object.assign(object, data);\n await object.save();\n\n return this.createJsonResponse(this.toPublicData(object));\n }\n\n /**\n * Handle DELETE /objects/:id\n */\n private async handleDelete(\n collection: SmrtCollection<any>,\n id: string,\n ): Promise<Response> {\n const object = await collection.get(id);\n\n if (!object) {\n return this.createErrorResponse(404, 'Object not found');\n }\n\n await object.delete();\n return new Response(null, { status: 204 });\n }\n\n /**\n * Get or create collection instance\n */\n private getCollection(classInfo: any): SmrtCollection<any> {\n if (!this.collections.has(classInfo.name)) {\n const collection = new classInfo.collectionConstructor({\n ai: this.context.ai,\n db: this.context.db,\n });\n this.collections.set(classInfo.name, collection);\n }\n const collection = this.collections.get(classInfo.name);\n if (!collection) throw new Error(`Collection ${classInfo.name} not found`);\n return collection;\n }\n\n /**\n * Mass-assignment guard (#1540): strip framework/server-managed and\n * `@field({ readonly: true })` fields from a create/update body, and — when an\n * `@smrt({ api: { writable: [...] } })` allowlist is set — intersect with it.\n */\n private applyWritablePolicy(\n objectName: string | undefined,\n data: any,\n ): Record<string, any> {\n if (!data || typeof data !== 'object') {\n return {};\n }\n\n const serverManaged = new Set([\n 'id',\n 'tenantId',\n 'tenant_id',\n 'createdAt',\n 'created_at',\n 'updatedAt',\n 'updated_at',\n ]);\n\n const readonly = new Set<string>();\n let writable: string[] | null = null;\n\n if (objectName) {\n const apiConfig = ObjectRegistry.getConfig(objectName)?.api;\n if (\n apiConfig &&\n typeof apiConfig === 'object' &&\n Array.isArray((apiConfig as { writable?: unknown }).writable)\n ) {\n writable = (apiConfig as { writable: string[] }).writable;\n }\n\n for (const [name, def] of ObjectRegistry.getFields(objectName)) {\n if (def && (def.readonly === true || def._meta?.readonly === true)) {\n readonly.add(name);\n }\n }\n }\n\n const result: Record<string, any> = {};\n for (const [key, value] of Object.entries(data)) {\n if (key.startsWith('_')) continue;\n if (serverManaged.has(key)) continue;\n if (readonly.has(key)) continue;\n if (writable && !writable.includes(key)) continue;\n result[key] = value;\n }\n return result;\n }\n\n /**\n * Serialize a SmrtObject for a network response, excluding sensitive fields\n * (#1540). Falls back to the value unchanged for non-SmrtObject payloads.\n */\n private toPublicData(object: any): any {\n return typeof object?.toPublicJSON === 'function'\n ? object.toPublicJSON()\n : object;\n }\n\n /**\n * Create JSON response with proper headers\n */\n private createJsonResponse(data: any, status = 200): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n }\n\n /**\n * Create error response\n */\n private createErrorResponse(status: number, message: string): Response {\n return new Response(JSON.stringify({ error: message }), {\n status,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n }\n\n /**\n * Resolve the allowed `Access-Control-Allow-Origin` for a request (#1540).\n * Returns the request's `Origin` only when it is in the configured allowlist;\n * never `*`. Returns null when CORS should not be applied.\n */\n private resolveAllowedOrigin(req: Request): string | null {\n if (!this.config.enableCors) return null;\n const allowed = this.config.allowedOrigins;\n if (!allowed || allowed.length === 0) return null;\n const origin = req.headers.get('origin');\n return origin && allowed.includes(origin) ? origin : null;\n }\n\n /**\n * Create CORS preflight response\n */\n private createCorsResponse(req: Request): Response {\n const headers: Record<string, string> = {\n 'Access-Control-Allow-Methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type,Authorization',\n 'Access-Control-Max-Age': '86400',\n };\n const origin = this.resolveAllowedOrigin(req);\n if (origin) {\n headers['Access-Control-Allow-Origin'] = origin;\n headers.Vary = 'Origin';\n }\n return new Response(null, { status: 200, headers });\n }\n\n /**\n * Add CORS headers to response\n */\n private addCorsHeaders(response: Response, req: Request): Response {\n const origin = this.resolveAllowedOrigin(req);\n if (!origin) return response;\n\n const headers = new Headers(response.headers);\n headers.set('Access-Control-Allow-Origin', origin);\n headers.set('Vary', 'Origin');\n headers.set(\n 'Access-Control-Allow-Methods',\n 'GET,POST,PUT,PATCH,DELETE,OPTIONS',\n );\n headers.set('Access-Control-Allow-Headers', 'Content-Type,Authorization');\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n\n /**\n * Simple pluralization (basic implementation)\n */\n private pluralize(word: string): string {\n if (word.endsWith('y')) {\n return `${word.slice(0, -1)}ies`;\n }\n if (word.endsWith('s') || word.endsWith('sh') || word.endsWith('ch')) {\n return `${word}es`;\n }\n return `${word}s`;\n }\n}\n\n// REST Server Utilities\n\nexport interface RestServerConfig extends APIConfig {\n healthCheck?: {\n enabled?: boolean;\n path?: string;\n customChecks?: (() => Promise<boolean>)[];\n };\n}\n\n/**\n * Create REST server with health checks using Bun\n */\nexport function createRestServer(\n objects: (typeof SmrtObject)[],\n context: APIContext = {},\n config: RestServerConfig = {},\n): { server: any; url: string } {\n // Register objects if not already registered\n objects.forEach((obj) => {\n if (!ObjectRegistry.hasClass(obj.name)) {\n console.warn(`Object ${obj.name} not registered with @smrt decorator`);\n }\n });\n\n const generator = new APIGenerator(config, context);\n const { server, url } = generator.createServer();\n\n console.log(`🚀 smrt REST API server running at ${url}`);\n\n return { server, url };\n}\n\n/**\n * Start server with graceful shutdown\n */\nexport function startRestServer(\n objects: (typeof SmrtObject)[],\n context: APIContext = {},\n config: RestServerConfig = {},\n): Promise<() => Promise<void>> {\n return new Promise((resolve) => {\n const { server, url } = createRestServer(objects, context, config);\n\n // Graceful shutdown function\n const shutdown = (): Promise<void> => {\n return new Promise((shutdownResolve) => {\n console.log('🛑 Shutting down server gracefully...');\n server.stop();\n console.log('✅ Server shut down complete');\n shutdownResolve();\n });\n };\n\n // Handle shutdown signals\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n\n resolve(shutdown);\n });\n}\n"],"names":["collection"],"mappings":";;AA0CO,MAAM,aAAa;AAAA,EAChB;AAAA,EACA,kCAAkB,IAAA;AAAA,EAClB;AAAA,EAER,YAAY,SAAoB,IAAI,UAAsB,CAAA,GAAI;AAC5D,SAAK,SAAS;AAAA,MACZ,UAAU;AAAA;AAAA;AAAA,MAGV,YAAY;AAAA,MACZ,MAAM;AAAA,MACN,UAAU;AAAA,MACV,GAAG;AAAA,IAAA;AAEL,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,mBAAmB,MAAc,YAAuC;AACtE,SAAK,YAAY,IAAI,MAAM,UAAU;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,eAA6C;AAC3C,UAAM,SAAS,KAAK,aAAa,OAAO,KAAK,QAAQ;AACnD,UAAI;AACF,cAAM,UAAU,MAAM,KAAK,wBAAwB,GAAG;AACtD,cAAM,WAAW,MAAM,KAAK,cAAc,OAAO;AACjD,cAAM,KAAK,0BAA0B,UAAU,GAAG;AAAA,MACpD,SAAS,QAAQ;AACf,YAAI,aAAa;AACjB,YAAI,IAAI,uBAAuB;AAAA,MACjC;AAAA,IACF,CAAC;AAED,WAAO,OAAO,KAAK,OAAO,MAAM,KAAK,OAAO,QAAQ;AAEpD,WAAO;AAAA,MACL;AAAA,MACA,KAAK,UAAU,KAAK,OAAO,QAAQ,IAAI,KAAK,OAAO,IAAI;AAAA,IAAA;AAAA,EAE3D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,eAAe,QAA+C;AAC1E,UAAM,SAAmB,CAAA;AACzB,qBAAiB,SAAS,QAAQ;AAChC,aAAO,KAAK,OAAO,SAAS,KAAK,IAAI,QAAQ,OAAO,KAAK,KAAK,CAAC;AAAA,IACjE;AACA,WAAO,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,wBACZ,KACkB;AAClB,UAAM,MAAM,UAAU,KAAK,OAAO,QAAQ,IAAI,KAAK,OAAO,IAAI,GAAG,IAAI,GAAG;AACxE,UAAM,SAAS,IAAI,UAAU;AAC7B,UAAM,UAAU,IAAI,QAAA;AAEpB,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AACtD,UAAI,OAAO;AACT,gBAAQ,IAAI,KAAK,MAAM,QAAQ,KAAK,IAAI,MAAM,CAAC,IAAI,KAAK;AAAA,MAC1D;AAAA,IACF;AAEA,QAAI;AACJ,QAAI,WAAW,SAAS,WAAW,QAAQ;AACzC,aAAO,MAAM,KAAK,eAAe,GAAG;AAAA,IACtC;AAEA,WAAO,IAAI,QAAQ,KAAK;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,0BACZ,aACA,KACe;AACf,QAAI,aAAa,YAAY;AAG7B,gBAAY,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AAC1C,UAAI,UAAU,KAAK,KAAK;AAAA,IAC1B,CAAC;AAGD,QAAI,YAAY,MAAM;AACpB,YAAM,SAAS,YAAY,KAAK,UAAA;AAChC,aAAO,MAAM;AACX,cAAM,EAAE,MAAM,MAAA,IAAU,MAAM,OAAO,KAAA;AACrC,YAAI,KAAM;AACV,YAAI,MAAM,KAAK;AAAA,MACjB;AAAA,IACF;AAEA,QAAI,IAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAuD;AACrD,WAAO,CAAC,QAAQ,KAAK,cAAc,GAAG;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,cAAc,KAAiC;AAC3D,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAG3B,QAAI,IAAI,WAAW,aAAa,KAAK,OAAO,YAAY;AACtD,aAAO,KAAK,mBAAmB,GAAG;AAAA,IACpC;AAGA,QAAI,KAAK,OAAO,cAAc;AAC5B,iBAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,KAAK,OAAO,YAAY,GAAG;AACtE,YAAI,IAAI,aAAa,GAAG,KAAK,OAAO,QAAQ,GAAG,IAAI,IAAI;AACrD,gBAAM,WAAW,MAAM,QAAQ,GAAG;AAClC,iBAAO,KAAK,eAAe,UAAU,GAAG;AAAA,QAC1C;AAAA,MACF;AAAA,IACF;AAGA,QAAI,IAAI,SAAS,WAAW,KAAK,OAAO,YAAY,EAAE,GAAG;AACvD,YAAM,WAAW,MAAM,KAAK,kBAAkB,KAAK,GAAG;AACtD,aAAO,KAAK,eAAe,UAAU,GAAG;AAAA,IAC1C;AAGA,WAAO,KAAK,oBAAoB,KAAK,WAAW;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,kBAAkB,KAAc,KAA6B;AACzE,UAAM,YAAY,IAAI,SACnB,QAAQ,KAAK,OAAO,YAAY,IAAI,EAAE,EACtC,MAAM,GAAG,EACT,OAAO,OAAO;AAEjB,QAAI,UAAU,WAAW,GAAG;AAC1B,aAAO,KAAK,oBAAoB,KAAK,sBAAsB;AAAA,IAC7D;AAEA,UAAM,aAAa,UAAU,CAAC;AAC9B,UAAM,WAAW,UAAU,CAAC;AAG5B,QAAI,KAAK,YAAY,IAAI,UAAU,GAAG;AACpC,YAAMA,cAAa,KAAK,YAAY,IAAI,UAAU;AAClD,UAAI,CAACA,YAAY,OAAM,IAAI,MAAM,cAAc,UAAU,YAAY;AAErE,YAAM,aAAa,KAAK,wBAAwBA,WAAU,KAAK;AAG/D,UAAI,KAAK,OAAO,gBAAgB;AAC9B,cAAM,YAAY,KAAK,OAAO;AAAA,UAC5B;AAAA,UACA,IAAI,OAAO,YAAA;AAAA,QAAY;AAEzB,cAAM,aAAa,MAAM,UAAU,GAAG;AACtC,YAAI,sBAAsB,UAAU;AAClC,iBAAO;AAAA,QACT;AAEA,cAAM;AAAA,MACR,WAAW,CAAC,KAAK,cAAc,YAAY,IAAI,MAAM,GAAG;AAGtD,eAAO,KAAK,oBAAoB,KAAK,yBAAyB;AAAA,MAChE;AAGA,aAAO,MAAM,KAAK;AAAA,QAChB;AAAA,QACAA;AAAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAGA,UAAM,oBAAoB,eAAe,cAAA;AACzC,UAAM,aAAa,KAAK,UAAU,UAAU;AAE5C,QAAI,YAAiB;AACrB,eAAW,CAAC,MAAM,IAAI,KAAK,mBAAmB;AAG5C,UAAI,KAAK,WAAW,KAAK,QAAQ,MAAM,aAAa,MAAM,YAAY;AACpE,oBAAY;AACZ;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,WAAW;AACd,aAAO,KAAK;AAAA,QACV;AAAA,QACA,gBAAgB,UAAU;AAAA,MAAA;AAAA,IAE9B;AAGA,QAAI,KAAK,OAAO,gBAAgB;AAC9B,YAAM,YAAY,KAAK,OAAO;AAAA,QAC5B,UAAU;AAAA,QACV,IAAI,OAAO,YAAA;AAAA,MAAY;AAEzB,YAAM,aAAa,MAAM,UAAU,GAAG;AACtC,UAAI,sBAAsB,UAAU;AAClC,eAAO;AAAA,MACT;AAEA,YAAM;AAAA,IACR,WAAW,CAAC,KAAK,cAAc,UAAU,MAAM,IAAI,MAAM,GAAG;AAE1D,aAAO,KAAK,oBAAoB,KAAK,yBAAyB;AAAA,IAChE;AAGA,UAAM,aAAa,KAAK,cAAc,SAAS;AAE/C,WAAO,MAAM,KAAK;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,IAAA;AAAA,EAEd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,qBACZ,KACA,YACA,UACA,KACA,YACmB;AACnB,QAAI;AACF,YAAM,SAAS,KAAK,cAAc,IAAI,QAAQ,QAAQ;AACtD,UAAI,UAAU,CAAC,KAAK,mBAAmB,YAAY,MAAM,GAAG;AAC1D,eAAO,KAAK,oBAAoB,KAAK,oBAAoB;AAAA,MAC3D;AAGA,UAAI,aAAa,WAAW,IAAI,WAAW,OAAO;AAChD,eAAO,MAAM,KAAK,YAAY,YAAY,IAAI,YAAY;AAAA,MAC5D;AAGA,cAAQ,IAAI,QAAA;AAAA,QACV,KAAK;AACH,iBAAO,WACH,MAAM,KAAK,UAAU,YAAY,QAAQ,IACzC,MAAM,KAAK,WAAW,YAAY,IAAI,YAAY;AAAA,QAExD,KAAK;AACH,iBAAO,MAAM,KAAK,aAAa,YAAY,KAAK,UAAU;AAAA,QAE5D,KAAK;AAAA,QACL,KAAK;AACH,cAAI,CAAC,UAAU;AACb,mBAAO,KAAK;AAAA,cACV;AAAA,cACA;AAAA,YAAA;AAAA,UAEJ;AACA,iBAAO,MAAM,KAAK,aAAa,YAAY,UAAU,KAAK,UAAU;AAAA,QAEtE,KAAK;AACH,cAAI,CAAC,UAAU;AACb,mBAAO,KAAK;AAAA,cACV;AAAA,cACA;AAAA,YAAA;AAAA,UAEJ;AACA,iBAAO,MAAM,KAAK,aAAa,YAAY,QAAQ;AAAA,QAErD;AACE,iBAAO,KAAK,oBAAoB,KAAK,oBAAoB;AAAA,MAAA;AAAA,IAE/D,SAAS,OAAO;AACd,cAAQ,MAAM,cAAc,KAAK;AACjC,aAAO,KAAK,oBAAoB,KAAK,uBAAuB;AAAA,IAC9D;AAAA,EACF;AAAA,EAEQ,cACN,QACA,UACwD;AACxD,YAAQ,QAAA;AAAA,MACN,KAAK;AACH,eAAO,YAAY,aAAa,UAAU,QAAQ;AAAA,MACpD,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IAAA;AAAA,EAEb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,cACN,YACA,QACS;AACT,QAAI,CAAC,WAAY,QAAO;AACxB,UAAM,YAAY,eAAe,UAAU,UAAU,GAAG;AACxD,QAAI,CAAC,aAAa,OAAO,cAAc,SAAU,QAAO;AACxD,UAAM,eAAgB,UAA4C;AAClE,QAAI,iBAAiB,KAAM,QAAO;AAClC,QAAI,iBAAiB,OAAQ,QAAO,OAAO,kBAAkB;AAC7D,WAAO;AAAA,EACT;AAAA,EAEQ,mBACN,YACA,QACS;AACT,QAAI,CAAC,YAAY;AACf,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,eAAe,UAAU,UAAU;AAClD,UAAM,YAAY,OAAO;AAEzB,QAAI,cAAc,OAAO;AACvB,aAAO;AAAA,IACT;AAEA,QAAI,aAAa,OAAO,cAAc,UAAU;AAC9C,UAAI,UAAU,WAAW,CAAC,UAAU,QAAQ,SAAS,MAAM,GAAG;AAC5D,eAAO;AAAA,MACT;AAEA,UAAI,UAAU,SAAS,SAAS,MAAM,GAAG;AACvC,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,wBACN,YACoB;AACpB,UAAM,YACH,WAAmB,cACnB,WAAW,aAAqB;AAEnC,QAAI,CAAC,WAAW;AACd,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,eAAe,sBAAsB,SAAS;AACjE,WAAO,YAAY,iBAAiB,YAAY,QAAQ,UAAU;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,UACZ,YACA,IACmB;AACnB,UAAM,SAAS,MAAM,WAAW,IAAI,EAAE;AACtC,QAAI,CAAC,QAAQ;AACX,aAAO,KAAK,oBAAoB,KAAK,kBAAkB;AAAA,IACzD;AACA,WAAO,KAAK,mBAAmB,KAAK,aAAa,MAAM,CAAC;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,WACZ,YACA,QACmB;AACnB,UAAM,QAAQ,OAAO,SAAS,OAAO,IAAI,OAAO,KAAK,MAAM,EAAE;AAC7D,UAAM,SAAS,OAAO,SAAS,OAAO,IAAI,QAAQ,KAAK,KAAK,EAAE;AAC9D,UAAM,UAAU,OAAO,IAAI,SAAS,KAAK;AAIzC,UAAM,QAAa,CAAA;AACnB,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,WAAW;AAC3C,UAAI,CAAC,CAAC,SAAS,UAAU,SAAS,EAAE,SAAS,GAAG,GAAG;AAEjD,cAAM,QAAQ,IAAI,MAAM,gBAAgB;AACxC,YAAI,OAAO;AACT,gBAAM,QAAQ,MAAM,CAAC;AACrB,gBAAM,WAAW,MAAM,CAAC;AAExB,gBAAM,cAAsC;AAAA,YAC1C,IAAI;AAAA,YACJ,KAAK;AAAA,YACL,IAAI;AAAA,YACJ,KAAK;AAAA,YACL,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ,MAAM;AAAA,UAAA;AAER,gBAAM,cAAc,YAAY,QAAQ,KAAK;AAC7C,gBAAM,SAAS,GAAG,KAAK,IAAI,WAAW;AAEtC,gBAAM,MAAM,IAAI,aAAa,OAAO,MAAM,MAAM,GAAG,IAAI;AAAA,QACzD,OAAO;AACL,gBAAM,GAAG,IAAI;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAEA,UAAM,UAAU,MAAM,WAAW,KAAK;AAAA,MACpC,OAAO,OAAO,KAAK,KAAK,EAAE,SAAS,IAAI,QAAQ;AAAA,MAC/C;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAED,WAAO,KAAK;AAAA,MACV,QAAQ,IAAI,CAAC,WAAgB,KAAK,aAAa,MAAM,CAAC;AAAA,IAAA;AAAA,EAE1D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,YACZ,YACA,QACmB;AAEnB,UAAM,QAAa,CAAA;AACnB,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,WAAW;AAE3C,YAAM,QAAQ,IAAI,MAAM,gBAAgB;AACxC,UAAI,OAAO;AACT,cAAM,QAAQ,MAAM,CAAC;AACrB,cAAM,WAAW,MAAM,CAAC;AAExB,cAAM,cAAsC;AAAA,UAC1C,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,IAAI;AAAA,UACJ,IAAI;AAAA,UACJ,MAAM;AAAA,QAAA;AAER,cAAM,cAAc,YAAY,QAAQ,KAAK;AAC7C,cAAM,SAAS,GAAG,KAAK,IAAI,WAAW;AAEtC,cAAM,MAAM,IAAI,aAAa,OAAO,MAAM,MAAM,GAAG,IAAI;AAAA,MACzD,OAAO;AACL,cAAM,GAAG,IAAI;AAAA,MACf;AAAA,IACF;AAEA,UAAM,QAAQ,MAAM,WAAW,MAAM;AAAA,MACnC,OAAO,OAAO,KAAK,KAAK,EAAE,SAAS,IAAI,QAAQ;AAAA,IAAA,CAChD;AAED,WAAO,KAAK,mBAAmB,EAAE,OAAO;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aACZ,YACA,KACA,YACmB;AACnB,UAAM,OAAO,KAAK,oBAAoB,YAAY,MAAM,IAAI,MAAM;AAClE,UAAM,SAAS,MAAM,WAAW,OAAO,EAAE,GAAG,MAAM,WAAW,MAAM;AACnE,UAAM,OAAO,KAAA;AACb,WAAO,KAAK,mBAAmB,KAAK,aAAa,MAAM,GAAG,GAAG;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aACZ,YACA,IACA,KACA,YACmB;AACnB,UAAM,OAAO,KAAK,oBAAoB,YAAY,MAAM,IAAI,MAAM;AAClE,UAAM,SAAS,MAAM,WAAW,IAAI,EAAE;AAEtC,QAAI,CAAC,QAAQ;AACX,aAAO,KAAK,oBAAoB,KAAK,kBAAkB;AAAA,IACzD;AAGA,WAAO,OAAO,QAAQ,IAAI;AAC1B,UAAM,OAAO,KAAA;AAEb,WAAO,KAAK,mBAAmB,KAAK,aAAa,MAAM,CAAC;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aACZ,YACA,IACmB;AACnB,UAAM,SAAS,MAAM,WAAW,IAAI,EAAE;AAEtC,QAAI,CAAC,QAAQ;AACX,aAAO,KAAK,oBAAoB,KAAK,kBAAkB;AAAA,IACzD;AAEA,UAAM,OAAO,OAAA;AACb,WAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,WAAqC;AACzD,QAAI,CAAC,KAAK,YAAY,IAAI,UAAU,IAAI,GAAG;AACzC,YAAMA,cAAa,IAAI,UAAU,sBAAsB;AAAA,QACrD,IAAI,KAAK,QAAQ;AAAA,QACjB,IAAI,KAAK,QAAQ;AAAA,MAAA,CAClB;AACD,WAAK,YAAY,IAAI,UAAU,MAAMA,WAAU;AAAA,IACjD;AACA,UAAM,aAAa,KAAK,YAAY,IAAI,UAAU,IAAI;AACtD,QAAI,CAAC,WAAY,OAAM,IAAI,MAAM,cAAc,UAAU,IAAI,YAAY;AACzE,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,oBACN,YACA,MACqB;AACrB,QAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,aAAO,CAAA;AAAA,IACT;AAEA,UAAM,oCAAoB,IAAI;AAAA,MAC5B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAED,UAAM,+BAAe,IAAA;AACrB,QAAI,WAA4B;AAEhC,QAAI,YAAY;AACd,YAAM,YAAY,eAAe,UAAU,UAAU,GAAG;AACxD,UACE,aACA,OAAO,cAAc,YACrB,MAAM,QAAS,UAAqC,QAAQ,GAC5D;AACA,mBAAY,UAAqC;AAAA,MACnD;AAEA,iBAAW,CAAC,MAAM,GAAG,KAAK,eAAe,UAAU,UAAU,GAAG;AAC9D,YAAI,QAAQ,IAAI,aAAa,QAAQ,IAAI,OAAO,aAAa,OAAO;AAClE,mBAAS,IAAI,IAAI;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,SAA8B,CAAA;AACpC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,UAAI,IAAI,WAAW,GAAG,EAAG;AACzB,UAAI,cAAc,IAAI,GAAG,EAAG;AAC5B,UAAI,SAAS,IAAI,GAAG,EAAG;AACvB,UAAI,YAAY,CAAC,SAAS,SAAS,GAAG,EAAG;AACzC,aAAO,GAAG,IAAI;AAAA,IAChB;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,aAAa,QAAkB;AACrC,WAAO,OAAO,QAAQ,iBAAiB,aACnC,OAAO,iBACP;AAAA,EACN;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAmB,MAAW,SAAS,KAAe;AAC5D,WAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,MACxC;AAAA,MACA,SAAS;AAAA,QACP,gBAAgB;AAAA,MAAA;AAAA,IAClB,CACD;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAoB,QAAgB,SAA2B;AACrE,WAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,QAAA,CAAS,GAAG;AAAA,MACtD;AAAA,MACA,SAAS;AAAA,QACP,gBAAgB;AAAA,MAAA;AAAA,IAClB,CACD;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,qBAAqB,KAA6B;AACxD,QAAI,CAAC,KAAK,OAAO,WAAY,QAAO;AACpC,UAAM,UAAU,KAAK,OAAO;AAC5B,QAAI,CAAC,WAAW,QAAQ,WAAW,EAAG,QAAO;AAC7C,UAAM,SAAS,IAAI,QAAQ,IAAI,QAAQ;AACvC,WAAO,UAAU,QAAQ,SAAS,MAAM,IAAI,SAAS;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAmB,KAAwB;AACjD,UAAM,UAAkC;AAAA,MACtC,gCAAgC;AAAA,MAChC,gCAAgC;AAAA,MAChC,0BAA0B;AAAA,IAAA;AAE5B,UAAM,SAAS,KAAK,qBAAqB,GAAG;AAC5C,QAAI,QAAQ;AACV,cAAQ,6BAA6B,IAAI;AACzC,cAAQ,OAAO;AAAA,IACjB;AACA,WAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,SAAS;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,UAAoB,KAAwB;AACjE,UAAM,SAAS,KAAK,qBAAqB,GAAG;AAC5C,QAAI,CAAC,OAAQ,QAAO;AAEpB,UAAM,UAAU,IAAI,QAAQ,SAAS,OAAO;AAC5C,YAAQ,IAAI,+BAA+B,MAAM;AACjD,YAAQ,IAAI,QAAQ,QAAQ;AAC5B,YAAQ;AAAA,MACN;AAAA,MACA;AAAA,IAAA;AAEF,YAAQ,IAAI,gCAAgC,4BAA4B;AAExE,WAAO,IAAI,SAAS,SAAS,MAAM;AAAA,MACjC,QAAQ,SAAS;AAAA,MACjB,YAAY,SAAS;AAAA,MACrB;AAAA,IAAA,CACD;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,UAAU,MAAsB;AACtC,QAAI,KAAK,SAAS,GAAG,GAAG;AACtB,aAAO,GAAG,KAAK,MAAM,GAAG,EAAE,CAAC;AAAA,IAC7B;AACA,QAAI,KAAK,SAAS,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG;AACpE,aAAO,GAAG,IAAI;AAAA,IAChB;AACA,WAAO,GAAG,IAAI;AAAA,EAChB;AACF;AAeO,SAAS,iBACd,SACA,UAAsB,CAAA,GACtB,SAA2B,CAAA,GACG;AAE9B,UAAQ,QAAQ,CAAC,QAAQ;AACvB,QAAI,CAAC,eAAe,SAAS,IAAI,IAAI,GAAG;AACtC,cAAQ,KAAK,UAAU,IAAI,IAAI,sCAAsC;AAAA,IACvE;AAAA,EACF,CAAC;AAED,QAAM,YAAY,IAAI,aAAa,QAAQ,OAAO;AAClD,QAAM,EAAE,QAAQ,QAAQ,UAAU,aAAA;AAElC,UAAQ,IAAI,sCAAsC,GAAG,EAAE;AAEvD,SAAO,EAAE,QAAQ,IAAA;AACnB;AAKO,SAAS,gBACd,SACA,UAAsB,CAAA,GACtB,SAA2B,CAAA,GACG;AAC9B,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,EAAE,QAAQ,IAAA,IAAQ,iBAAiB,SAAS,SAAS,MAAM;AAGjE,UAAM,WAAW,MAAqB;AACpC,aAAO,IAAI,QAAQ,CAAC,oBAAoB;AACtC,gBAAQ,IAAI,uCAAuC;AACnD,eAAO,KAAA;AACP,gBAAQ,IAAI,6BAA6B;AACzC,wBAAA;AAAA,MACF,CAAC;AAAA,IACH;AAGA,YAAQ,GAAG,WAAW,QAAQ;AAC9B,YAAQ,GAAG,UAAU,QAAQ;AAE7B,YAAQ,QAAQ;AAAA,EAClB,CAAC;AACH;"}
|
|
1
|
+
{"version":3,"file":"rest.js","sources":["../../src/generators/rest.ts"],"sourcesContent":["/**\n * High-performance REST API generator for smrt objects using Node.js HTTP server\n *\n * Designed for minimal bundle size and maximum performance\n */\n\nimport http from 'node:http';\nimport type { SmrtCollection } from '../collection';\nimport type { SmrtObject } from '../object';\nimport { ObjectRegistry } from '../registry';\nimport type { RegisteredClass, SmrtObjectConstructor } from '../registry/types';\n\nexport interface APIConfig {\n basePath?: string;\n enableCors?: boolean;\n /**\n * Explicit CORS origin allowlist (#1540). Required when `enableCors` is true —\n * the generator never emits `Access-Control-Allow-Origin: *`. A request's\n * `Origin` is echoed only when it appears here.\n */\n allowedOrigins?: string[];\n customRoutes?: Record<string, (req: Request) => Promise<Response>>;\n authMiddleware?: (\n objectName: string,\n action: string,\n ) => (req: Request) => Promise<Request | Response>;\n port?: number;\n hostname?: string;\n}\n\nexport interface APIContext {\n db?: unknown;\n ai?: unknown;\n user?: {\n id: string;\n username?: string;\n roles?: string[];\n };\n}\n\n/**\n * High-performance API generator using native Bun\n */\nexport class APIGenerator {\n private config: APIConfig;\n private collections = new Map<string, SmrtCollection<SmrtObject>>();\n private context: APIContext;\n\n constructor(config: APIConfig = {}, context: APIContext = {}) {\n this.config = {\n basePath: '/api/v1',\n // Security defaults (#1540): bind to loopback and keep CORS off unless an\n // explicit origin allowlist is supplied. No `Access-Control-Allow-Origin: *`.\n enableCors: false,\n port: 3000,\n hostname: '127.0.0.1',\n ...config,\n };\n this.context = context;\n }\n\n /**\n * Register a pre-configured collection instance for API exposure\n *\n * @param name - URL path segment for the collection (e.g., 'products' for /api/products)\n * @param collection - Pre-initialized SmrtCollection instance\n */\n registerCollection(\n name: string,\n collection: SmrtCollection<SmrtObject>,\n ): void {\n this.collections.set(name, collection);\n }\n\n /**\n * Create Node.js HTTP server with all routes\n */\n createServer(): { server: http.Server; url: string } {\n const server = http.createServer(async (req, res) => {\n try {\n const request = await this.nodeRequestToWebRequest(req);\n const response = await this.handleRequest(request);\n await this.webResponseToNodeResponse(response, res);\n } catch (_error) {\n res.statusCode = 500;\n res.end('Internal Server Error');\n }\n });\n\n server.listen(this.config.port, this.config.hostname);\n\n return {\n server,\n url: `http://${this.config.hostname}:${this.config.port}`,\n };\n }\n\n /**\n * Convert stream to string\n */\n private async streamToString(stream: http.IncomingMessage): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of stream) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n return Buffer.concat(chunks).toString('utf-8');\n }\n\n /**\n * Convert Node.js IncomingMessage to Web Request\n */\n private async nodeRequestToWebRequest(\n req: http.IncomingMessage,\n ): Promise<Request> {\n const url = `http://${this.config.hostname}:${this.config.port}${req.url}`;\n const method = req.method || 'GET';\n const headers = new Headers();\n\n for (const [key, value] of Object.entries(req.headers)) {\n if (value) {\n headers.set(key, Array.isArray(value) ? value[0] : value);\n }\n }\n\n let body: string | undefined;\n if (method !== 'GET' && method !== 'HEAD') {\n body = await this.streamToString(req);\n }\n\n return new Request(url, {\n method,\n headers,\n body,\n });\n }\n\n /**\n * Convert Web Response to Node.js ServerResponse\n */\n private async webResponseToNodeResponse(\n webResponse: Response,\n res: http.ServerResponse,\n ): Promise<void> {\n res.statusCode = webResponse.status;\n\n // Set headers\n webResponse.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n\n // Send body\n if (webResponse.body) {\n const reader = webResponse.body.getReader();\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n res.write(value);\n }\n }\n\n res.end();\n }\n\n /**\n * Generate fetch handler function (for serverless environments)\n */\n generateHandler(): (req: Request) => Promise<Response> {\n return (req) => this.handleRequest(req);\n }\n\n /**\n * Main request handler using native Bun APIs\n */\n private async handleRequest(req: Request): Promise<Response> {\n const url = new URL(req.url);\n\n // Handle CORS preflight\n if (req.method === 'OPTIONS' && this.config.enableCors) {\n return this.createCorsResponse(req);\n }\n\n // Handle custom routes first\n if (this.config.customRoutes) {\n for (const [path, handler] of Object.entries(this.config.customRoutes)) {\n if (url.pathname === `${this.config.basePath}${path}`) {\n const response = await handler(req);\n return this.addCorsHeaders(response, req);\n }\n }\n }\n\n // Handle object routes\n if (url.pathname.startsWith(this.config.basePath || '')) {\n const response = await this.handleObjectRoute(req, url);\n return this.addCorsHeaders(response, req);\n }\n\n // Not found\n return this.createErrorResponse(404, 'Not found');\n }\n\n /**\n * Handle CRUD routes for SMRT objects\n */\n private async handleObjectRoute(req: Request, url: URL): Promise<Response> {\n const pathParts = url.pathname\n .replace(this.config.basePath || '', '')\n .split('/')\n .filter(Boolean);\n\n if (pathParts.length === 0) {\n return this.createErrorResponse(400, 'Object type required');\n }\n\n const objectType = pathParts[0];\n const objectId = pathParts[1];\n\n // Check for explicitly registered collection first\n if (this.collections.has(objectType)) {\n const collection = this.collections.get(objectType);\n if (!collection) throw new Error(`Collection ${objectType} not found`);\n\n const objectName = this.getCollectionObjectName(collection) || objectType;\n\n // Apply auth middleware if configured\n if (this.config.authMiddleware) {\n const authCheck = this.config.authMiddleware(\n objectName,\n req.method.toLowerCase(),\n );\n const authResult = await authCheck(req);\n if (authResult instanceof Response) {\n return authResult; // Auth failed\n }\n // Auth passed, use the potentially modified request\n req = authResult;\n } else if (!this.isRoutePublic(objectName, req.method)) {\n // Fail-closed (#1540): no auth middleware wired and the object isn't\n // marked `@smrt({ api: { public } })` → refuse rather than serve open.\n return this.createErrorResponse(401, 'Authentication required');\n }\n\n // Use registered collection directly\n return await this.executeCrudOperation(\n req,\n collection,\n objectId,\n url,\n objectName,\n );\n }\n\n // Fall back to auto-discovery via ObjectRegistry\n const registeredClasses = ObjectRegistry.getAllClasses();\n const pluralName = this.pluralize(objectType);\n\n let classInfo: RegisteredClass | null = null;\n for (const [_key, info] of registeredClasses) {\n // Issue #951: Use simple name (info.name) for URL matching, not the map key\n // which may be a qualified name like '@happyvertical/smrt-events:Event'\n if (this.pluralize((info.name || _key).toLowerCase()) === pluralName) {\n classInfo = info;\n break;\n }\n }\n\n if (!classInfo) {\n return this.createErrorResponse(\n 404,\n `Object type '${objectType}' not found`,\n );\n }\n\n // Apply auth middleware if configured\n if (this.config.authMiddleware) {\n const authCheck = this.config.authMiddleware(\n classInfo.name,\n req.method.toLowerCase(),\n );\n const authResult = await authCheck(req);\n if (authResult instanceof Response) {\n return authResult; // Auth failed\n }\n // Auth passed, use the potentially modified request\n req = authResult;\n } else if (!this.isRoutePublic(classInfo.name, req.method)) {\n // Fail-closed (#1540): see registered-collection branch above.\n return this.createErrorResponse(401, 'Authentication required');\n }\n\n // Get or create collection\n const collection = this.getCollection(classInfo);\n\n return await this.executeCrudOperation(\n req,\n collection,\n objectId,\n url,\n classInfo.name,\n );\n }\n\n /**\n * Execute CRUD operation on a collection\n */\n private async executeCrudOperation(\n req: Request,\n collection: SmrtCollection<SmrtObject>,\n objectId: string | undefined,\n url: URL,\n objectName?: string,\n ): Promise<Response> {\n try {\n const action = this.getCrudAction(req.method, objectId);\n if (action && !this.isApiActionEnabled(objectName, action)) {\n return this.createErrorResponse(405, 'Method not allowed');\n }\n\n // Handle special /count endpoint\n if (objectId === 'count' && req.method === 'GET') {\n return await this.handleCount(collection, url.searchParams);\n }\n\n // Route to appropriate CRUD operation\n switch (req.method) {\n case 'GET':\n return objectId\n ? await this.handleGet(collection, objectId)\n : await this.handleList(collection, url.searchParams);\n\n case 'POST':\n return await this.handleCreate(collection, req, objectName);\n\n case 'PUT':\n case 'PATCH':\n if (!objectId) {\n return this.createErrorResponse(\n 400,\n 'Object ID required for update',\n );\n }\n return await this.handleUpdate(collection, objectId, req, objectName);\n\n case 'DELETE':\n if (!objectId) {\n return this.createErrorResponse(\n 400,\n 'Object ID required for delete',\n );\n }\n return await this.handleDelete(collection, objectId);\n\n default:\n return this.createErrorResponse(405, 'Method not allowed');\n }\n } catch (error) {\n console.error('API Error:', error);\n return this.createErrorResponse(500, 'Internal server error');\n }\n }\n\n private getCrudAction(\n method: string,\n objectId: string | undefined,\n ): 'list' | 'get' | 'create' | 'update' | 'delete' | null {\n switch (method) {\n case 'GET':\n return objectId && objectId !== 'count' ? 'get' : 'list';\n case 'POST':\n return 'create';\n case 'PUT':\n case 'PATCH':\n return 'update';\n case 'DELETE':\n return 'delete';\n default:\n return null;\n }\n }\n\n /**\n * Fail-closed authorization posture (#1540). Returns true only when the\n * object opts out of auth via `@smrt({ api: { public } })`:\n * - `public: true` → all methods are public.\n * - `public: 'read'` → only safe (GET) methods are public.\n * Everything else requires an `authMiddleware` to be configured.\n */\n private isRoutePublic(\n objectName: string | undefined,\n method: string,\n ): boolean {\n if (!objectName) return false;\n const apiConfig = ObjectRegistry.getConfig(objectName)?.api;\n if (!apiConfig || typeof apiConfig !== 'object') return false;\n const publicAccess = (apiConfig as { public?: boolean | 'read' }).public;\n if (publicAccess === true) return true;\n if (publicAccess === 'read') return method.toUpperCase() === 'GET';\n return false;\n }\n\n private isApiActionEnabled(\n objectName: string | undefined,\n action: 'list' | 'get' | 'create' | 'update' | 'delete',\n ): boolean {\n if (!objectName) {\n return true;\n }\n\n const config = ObjectRegistry.getConfig(objectName);\n const apiConfig = config.api;\n\n if (apiConfig === false) {\n return false;\n }\n\n if (apiConfig && typeof apiConfig === 'object') {\n if (apiConfig.include && !apiConfig.include.includes(action)) {\n return false;\n }\n\n if (apiConfig.exclude?.includes(action)) {\n return false;\n }\n }\n\n return true;\n }\n\n private getCollectionObjectName(\n collection: SmrtCollection<SmrtObject>,\n ): string | undefined {\n // `_itemClass` lives on the instance (protected getter) or the collection\n // constructor (static). Read it through a structural shape rather than the\n // class's private surface; behavior is unchanged.\n const itemClass: SmrtObjectConstructor | undefined =\n (collection as unknown as { _itemClass?: SmrtObjectConstructor })\n ._itemClass ||\n (\n collection.constructor as unknown as {\n _itemClass?: SmrtObjectConstructor;\n }\n )?._itemClass;\n\n if (!itemClass) {\n return undefined;\n }\n\n const registered = ObjectRegistry.getClassByConstructor(itemClass);\n return registered?.qualifiedName || registered?.name || itemClass.name;\n }\n\n /**\n * Handle GET /objects/:id\n */\n private async handleGet(\n collection: SmrtCollection<SmrtObject>,\n id: string,\n ): Promise<Response> {\n const object = await collection.get(id);\n if (!object) {\n return this.createErrorResponse(404, 'Object not found');\n }\n return this.createJsonResponse(this.toPublicData(object));\n }\n\n /**\n * Handle GET /objects (list with query params)\n */\n private async handleList(\n collection: SmrtCollection<SmrtObject>,\n params: URLSearchParams,\n ): Promise<Response> {\n const limit = Number.parseInt(params.get('limit') || '50', 10);\n const offset = Number.parseInt(params.get('offset') || '0', 10);\n const orderBy = params.get('orderBy') || 'created_at DESC';\n\n // Build where clause from query params\n // Convert REST-style operators (price[gt]) to SQL-style (price >)\n const where: Record<string, string | string[]> = {};\n for (const [key, value] of params.entries()) {\n if (!['limit', 'offset', 'orderBy'].includes(key)) {\n // Parse REST operator format: field[operator]\n const match = key.match(/^(.+)\\[(.+)\\]$/);\n if (match) {\n const field = match[1];\n const operator = match[2];\n // Map REST operators to SQL operators\n const operatorMap: Record<string, string> = {\n gt: '>',\n gte: '>=',\n lt: '<',\n lte: '<=',\n ne: '!=',\n in: 'in',\n like: 'like',\n };\n const sqlOperator = operatorMap[operator] || operator;\n const sqlKey = `${field} ${sqlOperator}`;\n // Handle 'in' operator - convert comma-separated string to array\n where[sqlKey] = operator === 'in' ? value.split(',') : value;\n } else {\n where[key] = value;\n }\n }\n }\n\n const objects = await collection.list({\n where: Object.keys(where).length > 0 ? where : undefined,\n limit,\n offset,\n orderBy,\n });\n\n return this.createJsonResponse(\n objects.map((object: SmrtObject) => this.toPublicData(object)),\n );\n }\n\n /**\n * Handle GET /objects/count\n */\n private async handleCount(\n collection: SmrtCollection<SmrtObject>,\n params: URLSearchParams,\n ): Promise<Response> {\n // Build where clause from query params (same logic as handleList)\n const where: Record<string, string | string[]> = {};\n for (const [key, value] of params.entries()) {\n // Parse REST operator format: field[operator]\n const match = key.match(/^(.+)\\[(.+)\\]$/);\n if (match) {\n const field = match[1];\n const operator = match[2];\n // Map REST operators to SQL operators\n const operatorMap: Record<string, string> = {\n gt: '>',\n gte: '>=',\n lt: '<',\n lte: '<=',\n ne: '!=',\n in: 'in',\n like: 'like',\n };\n const sqlOperator = operatorMap[operator] || operator;\n const sqlKey = `${field} ${sqlOperator}`;\n // Handle 'in' operator - convert comma-separated string to array\n where[sqlKey] = operator === 'in' ? value.split(',') : value;\n } else {\n where[key] = value;\n }\n }\n\n const count = await collection.count({\n where: Object.keys(where).length > 0 ? where : undefined,\n });\n\n return this.createJsonResponse({ count });\n }\n\n /**\n * Handle POST /objects\n */\n private async handleCreate(\n collection: SmrtCollection<SmrtObject>,\n req: Request,\n objectName?: string,\n ): Promise<Response> {\n const data = this.applyWritablePolicy(objectName, await req.json());\n const object = await collection.create({ ...data, _skipLoad: true });\n await object.save();\n return this.createJsonResponse(this.toPublicData(object), 201);\n }\n\n /**\n * Handle PUT/PATCH /objects/:id\n */\n private async handleUpdate(\n collection: SmrtCollection<SmrtObject>,\n id: string,\n req: Request,\n objectName?: string,\n ): Promise<Response> {\n const data = this.applyWritablePolicy(objectName, await req.json());\n const object = await collection.get(id);\n\n if (!object) {\n return this.createErrorResponse(404, 'Object not found');\n }\n\n // Update object properties\n Object.assign(object, data);\n await object.save();\n\n return this.createJsonResponse(this.toPublicData(object));\n }\n\n /**\n * Handle DELETE /objects/:id\n */\n private async handleDelete(\n collection: SmrtCollection<SmrtObject>,\n id: string,\n ): Promise<Response> {\n const object = await collection.get(id);\n\n if (!object) {\n return this.createErrorResponse(404, 'Object not found');\n }\n\n await object.delete();\n return new Response(null, { status: 204 });\n }\n\n /**\n * Get or create collection instance\n */\n private getCollection(\n classInfo: RegisteredClass,\n ): SmrtCollection<SmrtObject> {\n if (!this.collections.has(classInfo.name)) {\n // `collectionConstructor` is typed optional on RegisteredClass, but the\n // auto-discovery path only reaches here for classes that declare one.\n // Narrow to the non-optional constructor type before instantiating.\n const ctor = classInfo.collectionConstructor as NonNullable<\n RegisteredClass['collectionConstructor']\n >;\n const collection = new ctor({\n ai: this.context.ai,\n db: this.context.db,\n });\n this.collections.set(classInfo.name, collection);\n }\n const collection = this.collections.get(classInfo.name);\n if (!collection) throw new Error(`Collection ${classInfo.name} not found`);\n return collection;\n }\n\n /**\n * Mass-assignment guard (#1540): strip framework/server-managed and\n * `@field({ readonly: true })` fields from a create/update body, and — when an\n * `@smrt({ api: { writable: [...] } })` allowlist is set — intersect with it.\n */\n private applyWritablePolicy(\n objectName: string | undefined,\n data: unknown,\n ): Record<string, unknown> {\n if (!data || typeof data !== 'object') {\n return {};\n }\n\n const serverManaged = new Set([\n 'id',\n 'tenantId',\n 'tenant_id',\n 'createdAt',\n 'created_at',\n 'updatedAt',\n 'updated_at',\n ]);\n\n const readonly = new Set<string>();\n let writable: string[] | null = null;\n\n if (objectName) {\n const apiConfig = ObjectRegistry.getConfig(objectName)?.api;\n if (\n apiConfig &&\n typeof apiConfig === 'object' &&\n Array.isArray((apiConfig as { writable?: unknown }).writable)\n ) {\n writable = (apiConfig as { writable: string[] }).writable;\n }\n\n for (const [name, def] of ObjectRegistry.getFields(objectName)) {\n if (def && (def.readonly === true || def._meta?.readonly === true)) {\n readonly.add(name);\n }\n }\n }\n\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (key.startsWith('_')) continue;\n if (serverManaged.has(key)) continue;\n if (readonly.has(key)) continue;\n if (writable && !writable.includes(key)) continue;\n result[key] = value;\n }\n return result;\n }\n\n /**\n * Serialize a SmrtObject for a network response, excluding sensitive fields\n * (#1540). Falls back to the value unchanged for non-SmrtObject payloads.\n */\n private toPublicData(object: unknown): unknown {\n const serializable = object as { toPublicJSON?: () => unknown } | null;\n return typeof serializable?.toPublicJSON === 'function'\n ? serializable.toPublicJSON()\n : object;\n }\n\n /**\n * Create JSON response with proper headers\n */\n private createJsonResponse(data: unknown, status = 200): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n }\n\n /**\n * Create error response\n */\n private createErrorResponse(status: number, message: string): Response {\n return new Response(JSON.stringify({ error: message }), {\n status,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n }\n\n /**\n * Resolve the allowed `Access-Control-Allow-Origin` for a request (#1540).\n * Returns the request's `Origin` only when it is in the configured allowlist;\n * never `*`. Returns null when CORS should not be applied.\n */\n private resolveAllowedOrigin(req: Request): string | null {\n if (!this.config.enableCors) return null;\n const allowed = this.config.allowedOrigins;\n if (!allowed || allowed.length === 0) return null;\n const origin = req.headers.get('origin');\n return origin && allowed.includes(origin) ? origin : null;\n }\n\n /**\n * Create CORS preflight response\n */\n private createCorsResponse(req: Request): Response {\n const headers: Record<string, string> = {\n 'Access-Control-Allow-Methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type,Authorization',\n 'Access-Control-Max-Age': '86400',\n };\n const origin = this.resolveAllowedOrigin(req);\n if (origin) {\n headers['Access-Control-Allow-Origin'] = origin;\n headers.Vary = 'Origin';\n }\n return new Response(null, { status: 200, headers });\n }\n\n /**\n * Add CORS headers to response\n */\n private addCorsHeaders(response: Response, req: Request): Response {\n const origin = this.resolveAllowedOrigin(req);\n if (!origin) return response;\n\n const headers = new Headers(response.headers);\n headers.set('Access-Control-Allow-Origin', origin);\n headers.set('Vary', 'Origin');\n headers.set(\n 'Access-Control-Allow-Methods',\n 'GET,POST,PUT,PATCH,DELETE,OPTIONS',\n );\n headers.set('Access-Control-Allow-Headers', 'Content-Type,Authorization');\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n\n /**\n * Simple pluralization (basic implementation)\n */\n private pluralize(word: string): string {\n if (word.endsWith('y')) {\n return `${word.slice(0, -1)}ies`;\n }\n if (word.endsWith('s') || word.endsWith('sh') || word.endsWith('ch')) {\n return `${word}es`;\n }\n return `${word}s`;\n }\n}\n\n// REST Server Utilities\n\nexport interface RestServerConfig extends APIConfig {\n healthCheck?: {\n enabled?: boolean;\n path?: string;\n customChecks?: (() => Promise<boolean>)[];\n };\n}\n\n/**\n * Create REST server with health checks using Bun\n */\nexport function createRestServer(\n objects: (typeof SmrtObject)[],\n context: APIContext = {},\n config: RestServerConfig = {},\n): { server: http.Server; url: string } {\n // Register objects if not already registered\n objects.forEach((obj) => {\n if (!ObjectRegistry.hasClass(obj.name)) {\n console.warn(`Object ${obj.name} not registered with @smrt decorator`);\n }\n });\n\n const generator = new APIGenerator(config, context);\n const { server, url } = generator.createServer();\n\n console.log(`🚀 smrt REST API server running at ${url}`);\n\n return { server, url };\n}\n\n/**\n * Start server with graceful shutdown\n */\nexport function startRestServer(\n objects: (typeof SmrtObject)[],\n context: APIContext = {},\n config: RestServerConfig = {},\n): Promise<() => Promise<void>> {\n return new Promise((resolve) => {\n const { server, url } = createRestServer(objects, context, config);\n\n // Graceful shutdown function\n const shutdown = (): Promise<void> => {\n return new Promise((shutdownResolve) => {\n console.log('🛑 Shutting down server gracefully...');\n // Node's http.Server#close stops accepting new connections and invokes\n // the callback once in-flight ones drain. (The prior `.stop()` was a\n // Bun-era API absent on http.Server — it would have thrown.)\n server.close(() => {\n console.log('✅ Server shut down complete');\n shutdownResolve();\n });\n });\n };\n\n // Handle shutdown signals\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n\n resolve(shutdown);\n });\n}\n"],"names":["collection"],"mappings":"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"}
|
|
@@ -10,12 +10,22 @@ export interface OpenAPIConfig {
|
|
|
10
10
|
basePath?: string;
|
|
11
11
|
serverUrl?: string;
|
|
12
12
|
}
|
|
13
|
+
type OpenAPISpec = Record<string, unknown>;
|
|
14
|
+
/**
|
|
15
|
+
* Minimal Express-like surface consumed by {@link setupSwaggerUI}. Avoids a hard
|
|
16
|
+
* dependency on Express types for what is an optional integration helper.
|
|
17
|
+
*/
|
|
18
|
+
interface SwaggerApp {
|
|
19
|
+
use(path: string, ...handlers: unknown[]): unknown;
|
|
20
|
+
get(path: string, ...handlers: unknown[]): unknown;
|
|
21
|
+
}
|
|
13
22
|
/**
|
|
14
23
|
* Generate OpenAPI specification (tree-shakeable)
|
|
15
24
|
*/
|
|
16
|
-
export declare function generateOpenAPISpec(config?: OpenAPIConfig):
|
|
25
|
+
export declare function generateOpenAPISpec(config?: OpenAPIConfig): OpenAPISpec;
|
|
17
26
|
/**
|
|
18
27
|
* Setup Swagger UI (optional peer dependency)
|
|
19
28
|
*/
|
|
20
|
-
export declare function setupSwaggerUI(app:
|
|
29
|
+
export declare function setupSwaggerUI(app: SwaggerApp, spec: OpenAPISpec, path?: string): void;
|
|
30
|
+
export {};
|
|
21
31
|
//# sourceMappingURL=swagger.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swagger.d.ts","sourceRoot":"","sources":["../../src/generators/swagger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"swagger.d.ts","sourceRoot":"","sources":["../../src/generators/swagger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAUD,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE3C;;;GAGG;AACH,UAAU,UAAU;IAClB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IACnD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;CACpD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,GAAE,aAAkB,GAAG,WAAW,CAuD3E;AAsRD;;GAEG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,UAAU,EACf,IAAI,EAAE,WAAW,EACjB,IAAI,SAAU,QAwBf"}
|
|
@@ -286,9 +286,12 @@ function setupSwaggerUI(app, spec, path = "/docs") {
|
|
|
286
286
|
customCss: ".swagger-ui .topbar { display: none }"
|
|
287
287
|
})
|
|
288
288
|
);
|
|
289
|
-
app.get(
|
|
290
|
-
|
|
291
|
-
|
|
289
|
+
app.get(
|
|
290
|
+
`${path}/openapi.json`,
|
|
291
|
+
(_req, res) => {
|
|
292
|
+
res.json(spec);
|
|
293
|
+
}
|
|
294
|
+
);
|
|
292
295
|
console.log(`📚 Swagger UI available at ${path}`);
|
|
293
296
|
} catch (_error) {
|
|
294
297
|
console.warn("Swagger UI not available (install swagger-ui-express)");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swagger.js","sources":["../../src/generators/swagger.ts"],"sourcesContent":["/**\n * OpenAPI documentation generation for smrt APIs\n *\n * Lightweight implementation with optional Swagger UI\n */\n\nimport { ObjectRegistry } from '../registry';\n\nexport interface OpenAPIConfig {\n title?: string;\n version?: string;\n description?: string;\n basePath?: string;\n serverUrl?: string;\n}\n\n/**\n * Generate OpenAPI specification (tree-shakeable)\n */\nexport function generateOpenAPISpec(config: OpenAPIConfig = {}): any {\n const {\n title = 'smrt API',\n version = '1.0.0',\n description = 'Auto-generated API from smrt objects',\n basePath = '/api/v1',\n serverUrl = 'http://localhost:3000',\n } = config;\n\n const spec = {\n openapi: '3.0.3',\n info: { title, version, description },\n servers: [{ url: serverUrl }],\n security: [{ bearerAuth: [] }],\n components: {\n securitySchemes: {\n bearerAuth: {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT',\n },\n },\n schemas: generateSchemas(),\n responses: {\n ValidationError: {\n description: 'Validation error',\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n properties: {\n error: { type: 'string' },\n details: { type: 'string' },\n },\n },\n },\n },\n },\n NotFound: {\n description: 'Resource not found',\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n properties: { error: { type: 'string' } },\n },\n },\n },\n },\n },\n },\n paths: generatePaths(basePath),\n };\n\n return spec;\n}\n\n/**\n * Generate schemas for all registered objects\n */\nfunction generateSchemas(): Record<string, any> {\n const schemas: Record<string, any> = {};\n const registeredClasses = ObjectRegistry.getAllClasses();\n\n for (const [key, classInfo] of registeredClasses) {\n // Issue #951: Use simple name for schema keys, not the qualified map key\n const name = classInfo.name || key;\n // `api: false` disables REST generation (see rest.ts) and is already\n // skipped in generatePaths(); mirror that here so component schemas don't\n // leak the field shape of a surface with no working handlers. Skip ONLY\n // when api is explicitly false — default (undefined) and\n // `{ include: [...] }` configs still publish their schema (#1540).\n if (ObjectRegistry.getConfig(name).api === false) {\n continue;\n }\n schemas[name] = generateObjectSchema(name);\n schemas[`${name}List`] = {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: { $ref: `#/components/schemas/${name}` },\n },\n meta: {\n type: 'object',\n properties: {\n total: { type: 'integer' },\n limit: { type: 'integer' },\n offset: { type: 'integer' },\n count: { type: 'integer' },\n },\n },\n },\n };\n }\n\n return schemas;\n}\n\n/**\n * Generate schema for a specific object\n */\nfunction generateObjectSchema(objectName: string): any {\n const fields = ObjectRegistry.getFields(objectName);\n const properties: Record<string, any> = {\n id: { type: 'string', format: 'uuid' },\n slug: { type: 'string' },\n created_at: { type: 'string', format: 'date-time' },\n updated_at: { type: 'string', format: 'date-time' },\n };\n\n const required = ['id'];\n\n for (const [fieldName, field] of fields) {\n properties[fieldName] = fieldToOpenAPISchema(field);\n if (field._meta?.required) {\n required.push(fieldName);\n }\n }\n\n return { type: 'object', properties, required };\n}\n\n/**\n * Convert field to OpenAPI schema\n */\nfunction fieldToOpenAPISchema(field: any): any {\n const schema: any = {\n description: field._meta?.description || '',\n };\n\n switch (field.type) {\n case 'text':\n schema.type = 'string';\n if (field._meta?.maxLength) schema.maxLength = field._meta.maxLength;\n if (field._meta?.minLength) schema.minLength = field._meta.minLength;\n break;\n case 'integer':\n schema.type = 'integer';\n if (field._meta?.min !== undefined) schema.minimum = field._meta.min;\n if (field._meta?.max !== undefined) schema.maximum = field._meta.max;\n break;\n case 'decimal':\n schema.type = 'number';\n schema.format = 'float';\n if (field._meta?.min !== undefined) schema.minimum = field._meta.min;\n if (field._meta?.max !== undefined) schema.maximum = field._meta.max;\n break;\n case 'boolean':\n schema.type = 'boolean';\n break;\n case 'datetime':\n schema.type = 'string';\n schema.format = 'date-time';\n break;\n case 'json':\n schema.type = 'object';\n schema.additionalProperties = true;\n break;\n case 'foreignKey':\n schema.type = 'string';\n schema.format = 'uuid';\n break;\n default:\n schema.type = 'string';\n }\n\n if (field._meta?.default !== undefined) {\n schema.default = field._meta.default;\n }\n\n return schema;\n}\n\n/**\n * Generate API paths\n */\nfunction generatePaths(basePath: string): Record<string, any> {\n const paths: Record<string, any> = {};\n const registeredClasses = ObjectRegistry.getAllClasses();\n\n for (const [key, classInfo] of registeredClasses) {\n // Issue #951: Use simple name, not the qualified map key\n const name = classInfo.name || key;\n const pluralName = pluralize(name.toLowerCase());\n const objectPath = `${basePath}/${pluralName}`;\n\n const config = ObjectRegistry.getConfig(name);\n const apiConfig = config.api;\n // `api: false` disables REST generation (see rest.ts); don't advertise its\n // paths in the OpenAPI spec either, or the spec leaks the shape of a\n // surface that has no working handlers (#1540).\n if (apiConfig === false) {\n continue;\n }\n const excluded: string[] =\n typeof apiConfig === 'object' && apiConfig?.exclude\n ? apiConfig.exclude\n : [];\n const included: string[] | undefined =\n typeof apiConfig === 'object' ? apiConfig?.include : undefined;\n\n const shouldInclude = (\n endpoint: 'list' | 'get' | 'create' | 'update' | 'delete',\n ) => {\n if (included && !included.includes(endpoint)) return false;\n if (excluded.includes(endpoint)) return false;\n return true;\n };\n\n // Collection endpoints\n paths[objectPath] = {};\n\n if (shouldInclude('list')) {\n paths[objectPath].get = {\n summary: `List ${name} objects`,\n tags: [name],\n parameters: [\n {\n name: 'limit',\n in: 'query',\n schema: { type: 'integer', default: 50 },\n },\n {\n name: 'offset',\n in: 'query',\n schema: { type: 'integer', default: 0 },\n },\n ],\n responses: {\n '200': {\n description: 'Success',\n content: {\n 'application/json': {\n schema: { $ref: `#/components/schemas/${name}List` },\n },\n },\n },\n },\n };\n }\n\n if (shouldInclude('create')) {\n paths[objectPath].post = {\n summary: `Create ${name}`,\n tags: [name],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: { $ref: `#/components/schemas/${name}` },\n },\n },\n },\n responses: {\n '201': { description: 'Created' },\n '400': { $ref: '#/components/responses/ValidationError' },\n },\n };\n }\n\n // Item endpoints\n paths[`${objectPath}/{id}`] = {};\n\n if (shouldInclude('get')) {\n paths[`${objectPath}/{id}`].get = {\n summary: `Get ${name} by ID`,\n tags: [name],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string' },\n },\n ],\n responses: {\n '200': { description: 'Success' },\n '404': { $ref: '#/components/responses/NotFound' },\n },\n };\n }\n\n if (shouldInclude('update')) {\n paths[`${objectPath}/{id}`].put = {\n summary: `Update ${name}`,\n tags: [name],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string' },\n },\n ],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: { $ref: `#/components/schemas/${name}` },\n },\n },\n },\n responses: {\n '200': { description: 'Updated' },\n '404': { $ref: '#/components/responses/NotFound' },\n },\n };\n }\n\n if (shouldInclude('delete')) {\n paths[`${objectPath}/{id}`].delete = {\n summary: `Delete ${name}`,\n tags: [name],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string' },\n },\n ],\n responses: {\n '204': { description: 'Deleted' },\n '404': { $ref: '#/components/responses/NotFound' },\n },\n };\n }\n }\n\n return paths;\n}\n\n/**\n * Setup Swagger UI (optional peer dependency)\n */\nexport function setupSwaggerUI(app: any, spec: any, path = '/docs') {\n try {\n const swaggerUi = require('swagger-ui-express');\n\n app.use(path, swaggerUi.serve);\n app.get(\n path,\n swaggerUi.setup(spec, {\n customCss: '.swagger-ui .topbar { display: none }',\n }),\n );\n\n app.get(`${path}/openapi.json`, (_req: any, res: any) => {\n res.json(spec);\n });\n\n console.log(`📚 Swagger UI available at ${path}`);\n } catch (_error) {\n console.warn('Swagger UI not available (install swagger-ui-express)');\n }\n}\n\nfunction pluralize(word: string): string {\n if (word.endsWith('y')) return `${word.slice(0, -1)}ies`;\n if (\n word.endsWith('s') ||\n word.endsWith('x') ||\n word.endsWith('z') ||\n word.endsWith('ch') ||\n word.endsWith('sh')\n )\n return `${word}es`;\n return `${word}s`;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"swagger.js","sources":["../../src/generators/swagger.ts"],"sourcesContent":["/**\n * OpenAPI documentation generation for smrt APIs\n *\n * Lightweight implementation with optional Swagger UI\n */\n\nimport { ObjectRegistry } from '../registry';\nimport type { FieldDefinition } from '../scanner/types';\n\nexport interface OpenAPIConfig {\n title?: string;\n version?: string;\n description?: string;\n basePath?: string;\n serverUrl?: string;\n}\n\n/**\n * Loose structural types for the generated OpenAPI 3.0 document. The generator\n * builds these objects by progressively assigning heterogeneous spec fields, so\n * the value surface is intentionally open (`unknown`) rather than a fully-typed\n * OpenAPI model (no shared OpenAPI types are available in this package).\n */\ntype OpenAPISchemaObject = Record<string, unknown>;\ntype OpenAPIPathItem = Record<string, unknown>;\ntype OpenAPISpec = Record<string, unknown>;\n\n/**\n * Minimal Express-like surface consumed by {@link setupSwaggerUI}. Avoids a hard\n * dependency on Express types for what is an optional integration helper.\n */\ninterface SwaggerApp {\n use(path: string, ...handlers: unknown[]): unknown;\n get(path: string, ...handlers: unknown[]): unknown;\n}\n\n/**\n * Generate OpenAPI specification (tree-shakeable)\n */\nexport function generateOpenAPISpec(config: OpenAPIConfig = {}): OpenAPISpec {\n const {\n title = 'smrt API',\n version = '1.0.0',\n description = 'Auto-generated API from smrt objects',\n basePath = '/api/v1',\n serverUrl = 'http://localhost:3000',\n } = config;\n\n const spec = {\n openapi: '3.0.3',\n info: { title, version, description },\n servers: [{ url: serverUrl }],\n security: [{ bearerAuth: [] }],\n components: {\n securitySchemes: {\n bearerAuth: {\n type: 'http',\n scheme: 'bearer',\n bearerFormat: 'JWT',\n },\n },\n schemas: generateSchemas(),\n responses: {\n ValidationError: {\n description: 'Validation error',\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n properties: {\n error: { type: 'string' },\n details: { type: 'string' },\n },\n },\n },\n },\n },\n NotFound: {\n description: 'Resource not found',\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n properties: { error: { type: 'string' } },\n },\n },\n },\n },\n },\n },\n paths: generatePaths(basePath),\n };\n\n return spec;\n}\n\n/**\n * Generate schemas for all registered objects\n */\nfunction generateSchemas(): Record<string, OpenAPISchemaObject> {\n const schemas: Record<string, OpenAPISchemaObject> = {};\n const registeredClasses = ObjectRegistry.getAllClasses();\n\n for (const [key, classInfo] of registeredClasses) {\n // Issue #951: Use simple name for schema keys, not the qualified map key\n const name = classInfo.name || key;\n // `api: false` disables REST generation (see rest.ts) and is already\n // skipped in generatePaths(); mirror that here so component schemas don't\n // leak the field shape of a surface with no working handlers. Skip ONLY\n // when api is explicitly false — default (undefined) and\n // `{ include: [...] }` configs still publish their schema (#1540).\n if (ObjectRegistry.getConfig(name).api === false) {\n continue;\n }\n schemas[name] = generateObjectSchema(name);\n schemas[`${name}List`] = {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: { $ref: `#/components/schemas/${name}` },\n },\n meta: {\n type: 'object',\n properties: {\n total: { type: 'integer' },\n limit: { type: 'integer' },\n offset: { type: 'integer' },\n count: { type: 'integer' },\n },\n },\n },\n };\n }\n\n return schemas;\n}\n\n/**\n * Generate schema for a specific object\n */\nfunction generateObjectSchema(objectName: string): OpenAPISchemaObject {\n const fields = ObjectRegistry.getFields(objectName);\n const properties: Record<string, OpenAPISchemaObject> = {\n id: { type: 'string', format: 'uuid' },\n slug: { type: 'string' },\n created_at: { type: 'string', format: 'date-time' },\n updated_at: { type: 'string', format: 'date-time' },\n };\n\n const required = ['id'];\n\n for (const [fieldName, field] of fields) {\n properties[fieldName] = fieldToOpenAPISchema(field);\n if (field._meta?.required) {\n required.push(fieldName);\n }\n }\n\n return { type: 'object', properties, required };\n}\n\n/**\n * Convert field to OpenAPI schema\n */\nfunction fieldToOpenAPISchema(field: FieldDefinition): OpenAPISchemaObject {\n const schema: OpenAPISchemaObject = {\n description: field._meta?.description || '',\n };\n\n switch (field.type) {\n case 'text':\n schema.type = 'string';\n if (field._meta?.maxLength) schema.maxLength = field._meta.maxLength;\n if (field._meta?.minLength) schema.minLength = field._meta.minLength;\n break;\n case 'integer':\n schema.type = 'integer';\n if (field._meta?.min !== undefined) schema.minimum = field._meta.min;\n if (field._meta?.max !== undefined) schema.maximum = field._meta.max;\n break;\n case 'decimal':\n schema.type = 'number';\n schema.format = 'float';\n if (field._meta?.min !== undefined) schema.minimum = field._meta.min;\n if (field._meta?.max !== undefined) schema.maximum = field._meta.max;\n break;\n case 'boolean':\n schema.type = 'boolean';\n break;\n case 'datetime':\n schema.type = 'string';\n schema.format = 'date-time';\n break;\n case 'json':\n schema.type = 'object';\n schema.additionalProperties = true;\n break;\n case 'foreignKey':\n schema.type = 'string';\n schema.format = 'uuid';\n break;\n default:\n schema.type = 'string';\n }\n\n if (field._meta?.default !== undefined) {\n schema.default = field._meta.default;\n }\n\n return schema;\n}\n\n/**\n * Generate API paths\n */\nfunction generatePaths(basePath: string): Record<string, OpenAPIPathItem> {\n const paths: Record<string, OpenAPIPathItem> = {};\n const registeredClasses = ObjectRegistry.getAllClasses();\n\n for (const [key, classInfo] of registeredClasses) {\n // Issue #951: Use simple name, not the qualified map key\n const name = classInfo.name || key;\n const pluralName = pluralize(name.toLowerCase());\n const objectPath = `${basePath}/${pluralName}`;\n\n const config = ObjectRegistry.getConfig(name);\n const apiConfig = config.api;\n // `api: false` disables REST generation (see rest.ts); don't advertise its\n // paths in the OpenAPI spec either, or the spec leaks the shape of a\n // surface that has no working handlers (#1540).\n if (apiConfig === false) {\n continue;\n }\n const excluded: string[] =\n typeof apiConfig === 'object' && apiConfig?.exclude\n ? apiConfig.exclude\n : [];\n const included: string[] | undefined =\n typeof apiConfig === 'object' ? apiConfig?.include : undefined;\n\n const shouldInclude = (\n endpoint: 'list' | 'get' | 'create' | 'update' | 'delete',\n ) => {\n if (included && !included.includes(endpoint)) return false;\n if (excluded.includes(endpoint)) return false;\n return true;\n };\n\n // Collection endpoints\n paths[objectPath] = {};\n\n if (shouldInclude('list')) {\n paths[objectPath].get = {\n summary: `List ${name} objects`,\n tags: [name],\n parameters: [\n {\n name: 'limit',\n in: 'query',\n schema: { type: 'integer', default: 50 },\n },\n {\n name: 'offset',\n in: 'query',\n schema: { type: 'integer', default: 0 },\n },\n ],\n responses: {\n '200': {\n description: 'Success',\n content: {\n 'application/json': {\n schema: { $ref: `#/components/schemas/${name}List` },\n },\n },\n },\n },\n };\n }\n\n if (shouldInclude('create')) {\n paths[objectPath].post = {\n summary: `Create ${name}`,\n tags: [name],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: { $ref: `#/components/schemas/${name}` },\n },\n },\n },\n responses: {\n '201': { description: 'Created' },\n '400': { $ref: '#/components/responses/ValidationError' },\n },\n };\n }\n\n // Item endpoints\n paths[`${objectPath}/{id}`] = {};\n\n if (shouldInclude('get')) {\n paths[`${objectPath}/{id}`].get = {\n summary: `Get ${name} by ID`,\n tags: [name],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string' },\n },\n ],\n responses: {\n '200': { description: 'Success' },\n '404': { $ref: '#/components/responses/NotFound' },\n },\n };\n }\n\n if (shouldInclude('update')) {\n paths[`${objectPath}/{id}`].put = {\n summary: `Update ${name}`,\n tags: [name],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string' },\n },\n ],\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: { $ref: `#/components/schemas/${name}` },\n },\n },\n },\n responses: {\n '200': { description: 'Updated' },\n '404': { $ref: '#/components/responses/NotFound' },\n },\n };\n }\n\n if (shouldInclude('delete')) {\n paths[`${objectPath}/{id}`].delete = {\n summary: `Delete ${name}`,\n tags: [name],\n parameters: [\n {\n name: 'id',\n in: 'path',\n required: true,\n schema: { type: 'string' },\n },\n ],\n responses: {\n '204': { description: 'Deleted' },\n '404': { $ref: '#/components/responses/NotFound' },\n },\n };\n }\n }\n\n return paths;\n}\n\n/**\n * Setup Swagger UI (optional peer dependency)\n */\nexport function setupSwaggerUI(\n app: SwaggerApp,\n spec: OpenAPISpec,\n path = '/docs',\n) {\n try {\n const swaggerUi = require('swagger-ui-express');\n\n app.use(path, swaggerUi.serve);\n app.get(\n path,\n swaggerUi.setup(spec, {\n customCss: '.swagger-ui .topbar { display: none }',\n }),\n );\n\n app.get(\n `${path}/openapi.json`,\n (_req: unknown, res: { json(body: unknown): void }) => {\n res.json(spec);\n },\n );\n\n console.log(`📚 Swagger UI available at ${path}`);\n } catch (_error) {\n console.warn('Swagger UI not available (install swagger-ui-express)');\n }\n}\n\nfunction pluralize(word: string): string {\n if (word.endsWith('y')) return `${word.slice(0, -1)}ies`;\n if (\n word.endsWith('s') ||\n word.endsWith('x') ||\n word.endsWith('z') ||\n word.endsWith('ch') ||\n word.endsWith('sh')\n )\n return `${word}es`;\n return `${word}s`;\n}\n"],"names":[],"mappings":"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"}
|
package/dist/knowledge.d.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { DomainKnowledgeConfig, DomainKnowledgeManifest } from '@happyvertical/smrt-types';
|
|
2
2
|
import { SmartObjectManifest } from './scanner/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Minimal package.json shape consumed by the knowledge builder.
|
|
5
|
+
* `name`/`version` are typed concretely because they flow into the manifest's
|
|
6
|
+
* string-typed metadata fields; everything else is read via `unknown`-accepting
|
|
7
|
+
* helpers (`record()`, `exportKeys()`), so an index signature is sufficient.
|
|
8
|
+
*/
|
|
9
|
+
export interface PackageJsonLike {
|
|
10
|
+
name?: string;
|
|
11
|
+
version?: string;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
3
14
|
export interface BuildDomainKnowledgeOptions {
|
|
4
15
|
manifest: SmartObjectManifest;
|
|
5
16
|
rootDir: string;
|
|
6
|
-
packageJson?:
|
|
17
|
+
packageJson?: PackageJsonLike;
|
|
7
18
|
manifestPath?: string;
|
|
8
19
|
config?: DomainKnowledgeConfig;
|
|
9
20
|
}
|
package/dist/knowledge.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knowledge.d.ts","sourceRoot":"","sources":["../src/knowledge.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,qBAAqB,EACrB,uBAAuB,EAGxB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAEV,mBAAmB,EACpB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"knowledge.d.ts","sourceRoot":"","sources":["../src/knowledge.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,qBAAqB,EACrB,uBAAuB,EAGxB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAEV,mBAAmB,EACpB,MAAM,oBAAoB,CAAC;AAE5B;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AAkCD,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,2BAA2B,GACnC,uBAAuB,CAsDzB"}
|
package/dist/knowledge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knowledge.js","sources":["../src/knowledge.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport type {\n DomainKnowledgeConfig,\n DomainKnowledgeManifest,\n DomainKnowledgeObject,\n DomainKnowledgeSurface,\n} from '@happyvertical/smrt-types';\nimport type {\n SmartObjectDefinition,\n SmartObjectManifest,\n} from './scanner/types.js';\n\nexport interface BuildDomainKnowledgeOptions {\n manifest: SmartObjectManifest;\n rootDir: string;\n packageJson?: Record<string, any>;\n manifestPath?: string;\n config?: DomainKnowledgeConfig;\n}\n\nconst SDK_PACKAGE_NAMES = new Set([\n '@happyvertical/ai',\n '@happyvertical/cache',\n '@happyvertical/documents',\n '@happyvertical/email',\n '@happyvertical/encryption',\n '@happyvertical/files',\n '@happyvertical/geo',\n '@happyvertical/images',\n '@happyvertical/jobs',\n '@happyvertical/json',\n '@happyvertical/logger',\n '@happyvertical/messages',\n '@happyvertical/ocr',\n '@happyvertical/pdf',\n '@happyvertical/projects',\n '@happyvertical/repos',\n '@happyvertical/secrets',\n '@happyvertical/spider',\n '@happyvertical/sql',\n '@happyvertical/utils',\n]);\n\nconst RELATIONSHIP_FIELD_TYPES = new Set([\n 'foreignKey',\n 'crossPackageRef',\n 'oneToMany',\n 'manyToMany',\n]);\n\nconst STANDARD_OPERATIONS = ['list', 'get', 'create', 'update', 'delete'];\n\nexport function buildDomainKnowledgeManifest(\n options: BuildDomainKnowledgeOptions,\n): DomainKnowledgeManifest {\n const rootDir = options.rootDir;\n const packageJson = options.packageJson ?? readPackageJson(rootDir) ?? {};\n const packageName = options.manifest.packageName ?? packageJson.name;\n const packageVersion = options.manifest.packageVersion ?? packageJson.version;\n const agentDocPath = existingPath(rootDir, 'AGENTS.md');\n const agentDoc =\n options.config?.includeDocs === false || !agentDocPath\n ? undefined\n : readFileSync(agentDocPath, 'utf8');\n const allDependencies = {\n ...record(packageJson.dependencies),\n ...record(packageJson.devDependencies),\n ...record(packageJson.peerDependencies),\n };\n const manifestObjects = Object.values(options.manifest.objects).filter(\n (object) => object.decoratorConfig?.knowledge !== false,\n );\n const objects = manifestObjects.map((object) => buildKnowledgeObject(object));\n const surfaces = objects.flatMap((object) => object.surfaces);\n const manifestJson = stableJson(normalizeManifestForHash(options.manifest));\n\n return {\n schemaVersion: 1,\n generatedAt: new Date().toISOString(),\n packageName,\n packageVersion,\n sourceManifestPath: options.manifestPath\n ? relative(rootDir, options.manifestPath)\n : undefined,\n agentDocPath: agentDocPath ? relative(rootDir, agentDocPath) : undefined,\n sourceHashes: sourceHashes({\n manifest: { content: manifestJson },\n packageJson: fileHashSource(existingPath(rootDir, 'package.json')),\n agents: fileHashSource(agentDocPath),\n }),\n exports: exportKeys(packageJson.exports),\n dependencies: allDependencies,\n smrtDependencies: Object.keys(allDependencies)\n .filter((dep) => dep.startsWith('@happyvertical/smrt-'))\n .sort(),\n sdkDependencies: Object.keys(allDependencies)\n .filter((dep) => SDK_PACKAGE_NAMES.has(dep))\n .sort(),\n tags: options.config?.tags ?? [],\n summary: options.config?.summary,\n risks: options.config?.risks ?? [],\n objects,\n surfaces,\n prompts:\n options.config?.includePrompts === false ? [] : readPrompts(rootDir),\n relationshipsV2: summarizeRelationships(objects, manifestObjects),\n agentDoc,\n };\n}\n\nfunction buildKnowledgeObject(\n object: SmartObjectDefinition,\n): DomainKnowledgeObject {\n const knowledge =\n typeof object.decoratorConfig?.knowledge === 'object'\n ? object.decoratorConfig.knowledge\n : {};\n const fields = Object.entries(object.fields).map(([name, field]) => ({\n name,\n type: field.type,\n required: field.required,\n related: field.related,\n columnType: columnType(object, name),\n }));\n const relationships = fields\n .filter((field) => RELATIONSHIP_FIELD_TYPES.has(field.type))\n .map((field) => ({\n name: field.name,\n type: field.type,\n required: field.required,\n related: field.related,\n columnType: field.columnType,\n }));\n\n return {\n name: object.className,\n qualifiedName: object.qualifiedName,\n collection: object.collection,\n tableName: object.schema?.tableName,\n packageName: object.packageName,\n extends: object.extends,\n visibility: object.visibility,\n fields,\n relationships,\n methods: Object.keys(object.methods).sort(),\n surfaces: objectSurfaces(object),\n relationshipFeatures: relationshipFeatures(object),\n tags: knowledge.tags ?? [],\n summary: knowledge.summary,\n risks: knowledge.risks ?? [],\n };\n}\n\nfunction objectSurfaces(\n object: SmartObjectDefinition,\n): DomainKnowledgeSurface[] {\n return [\n ...configuredSurfaces('api', object),\n ...configuredSurfaces('cli', object),\n ...configuredSurfaces('mcp', object),\n ...aiSurfaces(object),\n ];\n}\n\nfunction configuredSurfaces(\n kind: 'api' | 'cli' | 'mcp',\n object: SmartObjectDefinition,\n): DomainKnowledgeSurface[] {\n const config = object.decoratorConfig?.[kind];\n if (!config) return [];\n const operations = configuredOperations(config);\n return operations.map((operation) => ({\n kind,\n name:\n kind === 'api'\n ? `${object.collection}.${operation}`\n : `${object.className.toLowerCase()}_${operation}`,\n operation,\n objectName: object.qualifiedName ?? object.className,\n path: kind === 'api' ? apiPath(object, operation) : undefined,\n method: kind === 'api' ? apiMethod(operation) : undefined,\n }));\n}\n\nfunction configuredOperations(config: unknown): string[] {\n if (config === true) return [...STANDARD_OPERATIONS];\n if (!config || typeof config !== 'object' || Array.isArray(config)) {\n return [];\n }\n const recordConfig = config as { include?: string[]; exclude?: string[] };\n const base = Array.isArray(recordConfig.include)\n ? recordConfig.include\n : STANDARD_OPERATIONS;\n const excluded = new Set(recordConfig.exclude ?? []);\n return [...new Set(base.filter((operation) => !excluded.has(operation)))];\n}\n\nfunction aiSurfaces(object: SmartObjectDefinition): DomainKnowledgeSurface[] {\n return (object.tools ?? []).map((tool) => ({\n kind: 'ai',\n name: tool.function.name,\n operation: tool.function.name,\n description: tool.function.description,\n objectName: object.qualifiedName ?? object.className,\n }));\n}\n\nfunction apiPath(object: SmartObjectDefinition, operation: string): string {\n const collection = object.decoratorConfig?.api;\n const configuredPath =\n typeof collection === 'object' && typeof collection.path === 'string'\n ? collection.path\n : object.collection.replaceAll('_', '-');\n if (operation === 'list' || operation === 'create') {\n return `/${configuredPath}`;\n }\n if (STANDARD_OPERATIONS.includes(operation)) {\n return `/${configuredPath}/[id]`;\n }\n return `/${configuredPath}/${operation}`;\n}\n\nfunction apiMethod(operation: string): string {\n switch (operation) {\n case 'list':\n case 'get':\n return 'GET';\n case 'create':\n return 'POST';\n case 'update':\n return 'PATCH';\n case 'delete':\n return 'DELETE';\n default:\n return 'POST';\n }\n}\n\nfunction relationshipFeatures(object: SmartObjectDefinition): string[] {\n const features = new Set<string>();\n for (const field of Object.values(object.fields)) {\n if (field.type === 'foreignKey') features.add('foreignKey');\n if (field.type === 'crossPackageRef') features.add('crossPackageRef');\n if (field.type === 'oneToMany') features.add('oneToMany');\n if (field.type === 'manyToMany') features.add('manyToMany');\n }\n if (object.extends === 'SmrtJunction') features.add('SmrtJunction');\n if (object.extends === 'SmrtHierarchical') features.add('SmrtHierarchical');\n if (\n object.extends === 'SmrtPolymorphicAssociation' ||\n object.fields.metaType ||\n object.fields.metaId\n ) {\n features.add('SmrtPolymorphicAssociation');\n }\n if (\n Object.keys(object.schema?.columns ?? {}).some(\n (name) => object.schema?.columns[name]?.type === 'UUID',\n )\n ) {\n features.add('uuidColumns');\n }\n return [...features].sort();\n}\n\nfunction summarizeRelationships(\n objects: DomainKnowledgeObject[],\n manifestObjects: SmartObjectDefinition[],\n) {\n const fields = objects.flatMap((object) => object.fields);\n return {\n foreignKeyFields: fields.filter((field) => field.type === 'foreignKey')\n .length,\n crossPackageRefFields: fields.filter(\n (field) => field.type === 'crossPackageRef',\n ).length,\n junctionCollections: objects.filter((object) =>\n object.relationshipFeatures.includes('SmrtJunction'),\n ).length,\n hierarchicalObjects: objects.filter((object) =>\n object.relationshipFeatures.includes('SmrtHierarchical'),\n ).length,\n polymorphicAssociations: objects.filter((object) =>\n object.relationshipFeatures.includes('SmrtPolymorphicAssociation'),\n ).length,\n uuidColumns: manifestObjects.reduce(\n (count, object) =>\n count +\n Object.values(object.schema?.columns ?? {}).filter(\n (column) => column.type === 'UUID',\n ).length,\n 0,\n ),\n };\n}\n\nfunction columnType(\n object: SmartObjectDefinition,\n fieldName: string,\n): string | undefined {\n const columnName = camelToSnake(fieldName);\n return object.schema?.columns[columnName]?.type;\n}\n\nfunction readPrompts(\n rootDir: string,\n): Array<{ filePath: string; key?: string }> {\n const srcDir = join(rootDir, 'src');\n if (!existsSync(srcDir)) return [];\n const prompts: Array<{ filePath: string; key?: string }> = [];\n for (const filePath of walkFiles(srcDir)) {\n if (!filePath.endsWith('.ts')) continue;\n const content = readFileSync(filePath, 'utf8');\n if (!content.includes('definePrompt')) continue;\n const keyMatch = content.match(/definePrompt\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/);\n prompts.push({\n filePath: relative(rootDir, filePath),\n key: keyMatch?.[1],\n });\n }\n return prompts;\n}\n\nfunction walkFiles(dir: string): string[] {\n const files: string[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (\n entry.name === 'node_modules' ||\n entry.name === 'dist' ||\n entry.name === '.svelte-kit'\n ) {\n continue;\n }\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...walkFiles(fullPath));\n } else if (entry.isFile()) {\n files.push(fullPath);\n }\n }\n return files;\n}\n\nfunction sourceHashes(sources: Record<string, HashSource | undefined>) {\n const hashes: Record<string, string> = {};\n for (const [name, source] of Object.entries(sources)) {\n if (!source) continue;\n const content =\n 'content' in source ? source.content : readFileSync(source.path, 'utf8');\n hashes[name] = createHash('sha256').update(content).digest('hex');\n }\n return hashes;\n}\n\ntype HashSource = { content: string } | { path: string };\n\nfunction fileHashSource(path: string | undefined): HashSource | undefined {\n return path ? { path } : undefined;\n}\n\nfunction existingPath(rootDir: string, path: string): string | undefined {\n const fullPath = join(rootDir, path);\n return existsSync(fullPath) ? fullPath : undefined;\n}\n\nfunction readPackageJson(rootDir: string): Record<string, any> | null {\n const path = join(rootDir, 'package.json');\n if (!existsSync(path)) return null;\n return JSON.parse(readFileSync(path, 'utf8'));\n}\n\nfunction record(value: unknown): Record<string, string> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, string>)\n : {};\n}\n\nfunction exportKeys(exportsField: unknown): string[] {\n if (typeof exportsField === 'string') return ['.'];\n if (\n typeof exportsField !== 'object' ||\n exportsField === null ||\n Array.isArray(exportsField)\n ) {\n return [];\n }\n return Object.keys(exportsField).sort();\n}\n\nfunction camelToSnake(value: string): string {\n return value\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[-\\s]+/g, '_')\n .toLowerCase();\n}\n\nfunction normalizeManifestForHash(manifest: SmartObjectManifest): unknown {\n const normalized = JSON.parse(JSON.stringify(manifest)) as Record<\n string,\n unknown\n >;\n delete normalized.timestamp;\n return normalized;\n}\n\nfunction stableJson(value: unknown): string {\n return JSON.stringify(sortJson(value), null, 2);\n}\n\nfunction sortJson(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortJson);\n if (value && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, entry]) => [key, sortJson(entry)]),\n );\n }\n return value;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"knowledge.js","sources":["../src/knowledge.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport type {\n DomainKnowledgeConfig,\n DomainKnowledgeManifest,\n DomainKnowledgeObject,\n DomainKnowledgeSurface,\n} from '@happyvertical/smrt-types';\nimport type {\n SmartObjectDefinition,\n SmartObjectManifest,\n} from './scanner/types.js';\n\n/**\n * Minimal package.json shape consumed by the knowledge builder.\n * `name`/`version` are typed concretely because they flow into the manifest's\n * string-typed metadata fields; everything else is read via `unknown`-accepting\n * helpers (`record()`, `exportKeys()`), so an index signature is sufficient.\n */\nexport interface PackageJsonLike {\n name?: string;\n version?: string;\n [key: string]: unknown;\n}\n\nexport interface BuildDomainKnowledgeOptions {\n manifest: SmartObjectManifest;\n rootDir: string;\n packageJson?: PackageJsonLike;\n manifestPath?: string;\n config?: DomainKnowledgeConfig;\n}\n\nconst SDK_PACKAGE_NAMES = new Set([\n '@happyvertical/ai',\n '@happyvertical/cache',\n '@happyvertical/documents',\n '@happyvertical/email',\n '@happyvertical/encryption',\n '@happyvertical/files',\n '@happyvertical/geo',\n '@happyvertical/images',\n '@happyvertical/jobs',\n '@happyvertical/json',\n '@happyvertical/logger',\n '@happyvertical/messages',\n '@happyvertical/ocr',\n '@happyvertical/pdf',\n '@happyvertical/projects',\n '@happyvertical/repos',\n '@happyvertical/secrets',\n '@happyvertical/spider',\n '@happyvertical/sql',\n '@happyvertical/utils',\n]);\n\nconst RELATIONSHIP_FIELD_TYPES = new Set([\n 'foreignKey',\n 'crossPackageRef',\n 'oneToMany',\n 'manyToMany',\n]);\n\nconst STANDARD_OPERATIONS = ['list', 'get', 'create', 'update', 'delete'];\n\nexport function buildDomainKnowledgeManifest(\n options: BuildDomainKnowledgeOptions,\n): DomainKnowledgeManifest {\n const rootDir = options.rootDir;\n const packageJson = options.packageJson ?? readPackageJson(rootDir) ?? {};\n const packageName = options.manifest.packageName ?? packageJson.name;\n const packageVersion = options.manifest.packageVersion ?? packageJson.version;\n const agentDocPath = existingPath(rootDir, 'AGENTS.md');\n const agentDoc =\n options.config?.includeDocs === false || !agentDocPath\n ? undefined\n : readFileSync(agentDocPath, 'utf8');\n const allDependencies = {\n ...record(packageJson.dependencies),\n ...record(packageJson.devDependencies),\n ...record(packageJson.peerDependencies),\n };\n const manifestObjects = Object.values(options.manifest.objects).filter(\n (object) => object.decoratorConfig?.knowledge !== false,\n );\n const objects = manifestObjects.map((object) => buildKnowledgeObject(object));\n const surfaces = objects.flatMap((object) => object.surfaces);\n const manifestJson = stableJson(normalizeManifestForHash(options.manifest));\n\n return {\n schemaVersion: 1,\n generatedAt: new Date().toISOString(),\n packageName,\n packageVersion,\n sourceManifestPath: options.manifestPath\n ? relative(rootDir, options.manifestPath)\n : undefined,\n agentDocPath: agentDocPath ? relative(rootDir, agentDocPath) : undefined,\n sourceHashes: sourceHashes({\n manifest: { content: manifestJson },\n packageJson: fileHashSource(existingPath(rootDir, 'package.json')),\n agents: fileHashSource(agentDocPath),\n }),\n exports: exportKeys(packageJson.exports),\n dependencies: allDependencies,\n smrtDependencies: Object.keys(allDependencies)\n .filter((dep) => dep.startsWith('@happyvertical/smrt-'))\n .sort(),\n sdkDependencies: Object.keys(allDependencies)\n .filter((dep) => SDK_PACKAGE_NAMES.has(dep))\n .sort(),\n tags: options.config?.tags ?? [],\n summary: options.config?.summary,\n risks: options.config?.risks ?? [],\n objects,\n surfaces,\n prompts:\n options.config?.includePrompts === false ? [] : readPrompts(rootDir),\n relationshipsV2: summarizeRelationships(objects, manifestObjects),\n agentDoc,\n };\n}\n\nfunction buildKnowledgeObject(\n object: SmartObjectDefinition,\n): DomainKnowledgeObject {\n const knowledge =\n typeof object.decoratorConfig?.knowledge === 'object'\n ? object.decoratorConfig.knowledge\n : {};\n const fields = Object.entries(object.fields).map(([name, field]) => ({\n name,\n type: field.type,\n required: field.required,\n related: field.related,\n columnType: columnType(object, name),\n }));\n const relationships = fields\n .filter((field) => RELATIONSHIP_FIELD_TYPES.has(field.type))\n .map((field) => ({\n name: field.name,\n type: field.type,\n required: field.required,\n related: field.related,\n columnType: field.columnType,\n }));\n\n return {\n name: object.className,\n qualifiedName: object.qualifiedName,\n collection: object.collection,\n tableName: object.schema?.tableName,\n packageName: object.packageName,\n extends: object.extends,\n visibility: object.visibility,\n fields,\n relationships,\n methods: Object.keys(object.methods).sort(),\n surfaces: objectSurfaces(object),\n relationshipFeatures: relationshipFeatures(object),\n tags: knowledge.tags ?? [],\n summary: knowledge.summary,\n risks: knowledge.risks ?? [],\n };\n}\n\nfunction objectSurfaces(\n object: SmartObjectDefinition,\n): DomainKnowledgeSurface[] {\n return [\n ...configuredSurfaces('api', object),\n ...configuredSurfaces('cli', object),\n ...configuredSurfaces('mcp', object),\n ...aiSurfaces(object),\n ];\n}\n\nfunction configuredSurfaces(\n kind: 'api' | 'cli' | 'mcp',\n object: SmartObjectDefinition,\n): DomainKnowledgeSurface[] {\n const config = object.decoratorConfig?.[kind];\n if (!config) return [];\n const operations = configuredOperations(config);\n return operations.map((operation) => ({\n kind,\n name:\n kind === 'api'\n ? `${object.collection}.${operation}`\n : `${object.className.toLowerCase()}_${operation}`,\n operation,\n objectName: object.qualifiedName ?? object.className,\n path: kind === 'api' ? apiPath(object, operation) : undefined,\n method: kind === 'api' ? apiMethod(operation) : undefined,\n }));\n}\n\nfunction configuredOperations(config: unknown): string[] {\n if (config === true) return [...STANDARD_OPERATIONS];\n if (!config || typeof config !== 'object' || Array.isArray(config)) {\n return [];\n }\n const recordConfig = config as { include?: string[]; exclude?: string[] };\n const base = Array.isArray(recordConfig.include)\n ? recordConfig.include\n : STANDARD_OPERATIONS;\n const excluded = new Set(recordConfig.exclude ?? []);\n return [...new Set(base.filter((operation) => !excluded.has(operation)))];\n}\n\nfunction aiSurfaces(object: SmartObjectDefinition): DomainKnowledgeSurface[] {\n return (object.tools ?? []).map((tool) => ({\n kind: 'ai',\n name: tool.function.name,\n operation: tool.function.name,\n description: tool.function.description,\n objectName: object.qualifiedName ?? object.className,\n }));\n}\n\nfunction apiPath(object: SmartObjectDefinition, operation: string): string {\n const collection = object.decoratorConfig?.api;\n const configuredPath =\n typeof collection === 'object' && typeof collection.path === 'string'\n ? collection.path\n : object.collection.replaceAll('_', '-');\n if (operation === 'list' || operation === 'create') {\n return `/${configuredPath}`;\n }\n if (STANDARD_OPERATIONS.includes(operation)) {\n return `/${configuredPath}/[id]`;\n }\n return `/${configuredPath}/${operation}`;\n}\n\nfunction apiMethod(operation: string): string {\n switch (operation) {\n case 'list':\n case 'get':\n return 'GET';\n case 'create':\n return 'POST';\n case 'update':\n return 'PATCH';\n case 'delete':\n return 'DELETE';\n default:\n return 'POST';\n }\n}\n\nfunction relationshipFeatures(object: SmartObjectDefinition): string[] {\n const features = new Set<string>();\n for (const field of Object.values(object.fields)) {\n if (field.type === 'foreignKey') features.add('foreignKey');\n if (field.type === 'crossPackageRef') features.add('crossPackageRef');\n if (field.type === 'oneToMany') features.add('oneToMany');\n if (field.type === 'manyToMany') features.add('manyToMany');\n }\n if (object.extends === 'SmrtJunction') features.add('SmrtJunction');\n if (object.extends === 'SmrtHierarchical') features.add('SmrtHierarchical');\n if (\n object.extends === 'SmrtPolymorphicAssociation' ||\n object.fields.metaType ||\n object.fields.metaId\n ) {\n features.add('SmrtPolymorphicAssociation');\n }\n if (\n Object.keys(object.schema?.columns ?? {}).some(\n (name) => object.schema?.columns[name]?.type === 'UUID',\n )\n ) {\n features.add('uuidColumns');\n }\n return [...features].sort();\n}\n\nfunction summarizeRelationships(\n objects: DomainKnowledgeObject[],\n manifestObjects: SmartObjectDefinition[],\n) {\n const fields = objects.flatMap((object) => object.fields);\n return {\n foreignKeyFields: fields.filter((field) => field.type === 'foreignKey')\n .length,\n crossPackageRefFields: fields.filter(\n (field) => field.type === 'crossPackageRef',\n ).length,\n junctionCollections: objects.filter((object) =>\n object.relationshipFeatures.includes('SmrtJunction'),\n ).length,\n hierarchicalObjects: objects.filter((object) =>\n object.relationshipFeatures.includes('SmrtHierarchical'),\n ).length,\n polymorphicAssociations: objects.filter((object) =>\n object.relationshipFeatures.includes('SmrtPolymorphicAssociation'),\n ).length,\n uuidColumns: manifestObjects.reduce(\n (count, object) =>\n count +\n Object.values(object.schema?.columns ?? {}).filter(\n (column) => column.type === 'UUID',\n ).length,\n 0,\n ),\n };\n}\n\nfunction columnType(\n object: SmartObjectDefinition,\n fieldName: string,\n): string | undefined {\n const columnName = camelToSnake(fieldName);\n return object.schema?.columns[columnName]?.type;\n}\n\nfunction readPrompts(\n rootDir: string,\n): Array<{ filePath: string; key?: string }> {\n const srcDir = join(rootDir, 'src');\n if (!existsSync(srcDir)) return [];\n const prompts: Array<{ filePath: string; key?: string }> = [];\n for (const filePath of walkFiles(srcDir)) {\n if (!filePath.endsWith('.ts')) continue;\n const content = readFileSync(filePath, 'utf8');\n if (!content.includes('definePrompt')) continue;\n const keyMatch = content.match(/definePrompt\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/);\n prompts.push({\n filePath: relative(rootDir, filePath),\n key: keyMatch?.[1],\n });\n }\n return prompts;\n}\n\nfunction walkFiles(dir: string): string[] {\n const files: string[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (\n entry.name === 'node_modules' ||\n entry.name === 'dist' ||\n entry.name === '.svelte-kit'\n ) {\n continue;\n }\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...walkFiles(fullPath));\n } else if (entry.isFile()) {\n files.push(fullPath);\n }\n }\n return files;\n}\n\nfunction sourceHashes(sources: Record<string, HashSource | undefined>) {\n const hashes: Record<string, string> = {};\n for (const [name, source] of Object.entries(sources)) {\n if (!source) continue;\n const content =\n 'content' in source ? source.content : readFileSync(source.path, 'utf8');\n hashes[name] = createHash('sha256').update(content).digest('hex');\n }\n return hashes;\n}\n\ntype HashSource = { content: string } | { path: string };\n\nfunction fileHashSource(path: string | undefined): HashSource | undefined {\n return path ? { path } : undefined;\n}\n\nfunction existingPath(rootDir: string, path: string): string | undefined {\n const fullPath = join(rootDir, path);\n return existsSync(fullPath) ? fullPath : undefined;\n}\n\nfunction readPackageJson(rootDir: string): PackageJsonLike | null {\n const path = join(rootDir, 'package.json');\n if (!existsSync(path)) return null;\n return JSON.parse(readFileSync(path, 'utf8'));\n}\n\nfunction record(value: unknown): Record<string, string> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, string>)\n : {};\n}\n\nfunction exportKeys(exportsField: unknown): string[] {\n if (typeof exportsField === 'string') return ['.'];\n if (\n typeof exportsField !== 'object' ||\n exportsField === null ||\n Array.isArray(exportsField)\n ) {\n return [];\n }\n return Object.keys(exportsField).sort();\n}\n\nfunction camelToSnake(value: string): string {\n return value\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[-\\s]+/g, '_')\n .toLowerCase();\n}\n\nfunction normalizeManifestForHash(manifest: SmartObjectManifest): unknown {\n const normalized = JSON.parse(JSON.stringify(manifest)) as Record<\n string,\n unknown\n >;\n delete normalized.timestamp;\n return normalized;\n}\n\nfunction stableJson(value: unknown): string {\n return JSON.stringify(sortJson(value), null, 2);\n}\n\nfunction sortJson(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortJson);\n if (value && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, entry]) => [key, sortJson(entry)]),\n );\n }\n return value;\n}\n"],"names":[],"mappings":";;;AAkCA,MAAM,wCAAwB,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,MAAM,+CAA+B,IAAI;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,MAAM,sBAAsB,CAAC,QAAQ,OAAO,UAAU,UAAU,QAAQ;AAEjE,SAAS,6BACd,SACyB;AACzB,QAAM,UAAU,QAAQ;AACxB,QAAM,cAAc,QAAQ,eAAe,gBAAgB,OAAO,KAAK,CAAA;AACvE,QAAM,cAAc,QAAQ,SAAS,eAAe,YAAY;AAChE,QAAM,iBAAiB,QAAQ,SAAS,kBAAkB,YAAY;AACtE,QAAM,eAAe,aAAa,SAAS,WAAW;AACtD,QAAM,WACJ,QAAQ,QAAQ,gBAAgB,SAAS,CAAC,eACtC,SACA,aAAa,cAAc,MAAM;AACvC,QAAM,kBAAkB;AAAA,IACtB,GAAG,OAAO,YAAY,YAAY;AAAA,IAClC,GAAG,OAAO,YAAY,eAAe;AAAA,IACrC,GAAG,OAAO,YAAY,gBAAgB;AAAA,EAAA;AAExC,QAAM,kBAAkB,OAAO,OAAO,QAAQ,SAAS,OAAO,EAAE;AAAA,IAC9D,CAAC,WAAW,OAAO,iBAAiB,cAAc;AAAA,EAAA;AAEpD,QAAM,UAAU,gBAAgB,IAAI,CAAC,WAAW,qBAAqB,MAAM,CAAC;AAC5E,QAAM,WAAW,QAAQ,QAAQ,CAAC,WAAW,OAAO,QAAQ;AAC5D,QAAM,eAAe,WAAW,yBAAyB,QAAQ,QAAQ,CAAC;AAE1E,SAAO;AAAA,IACL,eAAe;AAAA,IACf,cAAa,oBAAI,KAAA,GAAO,YAAA;AAAA,IACxB;AAAA,IACA;AAAA,IACA,oBAAoB,QAAQ,eACxB,SAAS,SAAS,QAAQ,YAAY,IACtC;AAAA,IACJ,cAAc,eAAe,SAAS,SAAS,YAAY,IAAI;AAAA,IAC/D,cAAc,aAAa;AAAA,MACzB,UAAU,EAAE,SAAS,aAAA;AAAA,MACrB,aAAa,eAAe,aAAa,SAAS,cAAc,CAAC;AAAA,MACjE,QAAQ,eAAe,YAAY;AAAA,IAAA,CACpC;AAAA,IACD,SAAS,WAAW,YAAY,OAAO;AAAA,IACvC,cAAc;AAAA,IACd,kBAAkB,OAAO,KAAK,eAAe,EAC1C,OAAO,CAAC,QAAQ,IAAI,WAAW,sBAAsB,CAAC,EACtD,KAAA;AAAA,IACH,iBAAiB,OAAO,KAAK,eAAe,EACzC,OAAO,CAAC,QAAQ,kBAAkB,IAAI,GAAG,CAAC,EAC1C,KAAA;AAAA,IACH,MAAM,QAAQ,QAAQ,QAAQ,CAAA;AAAA,IAC9B,SAAS,QAAQ,QAAQ;AAAA,IACzB,OAAO,QAAQ,QAAQ,SAAS,CAAA;AAAA,IAChC;AAAA,IACA;AAAA,IACA,SACE,QAAQ,QAAQ,mBAAmB,QAAQ,CAAA,IAAK,YAAY,OAAO;AAAA,IACrE,iBAAiB,uBAAuB,SAAS,eAAe;AAAA,IAChE;AAAA,EAAA;AAEJ;AAEA,SAAS,qBACP,QACuB;AACvB,QAAM,YACJ,OAAO,OAAO,iBAAiB,cAAc,WACzC,OAAO,gBAAgB,YACvB,CAAA;AACN,QAAM,SAAS,OAAO,QAAQ,OAAO,MAAM,EAAE,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO;AAAA,IACnE;AAAA,IACA,MAAM,MAAM;AAAA,IACZ,UAAU,MAAM;AAAA,IAChB,SAAS,MAAM;AAAA,IACf,YAAY,WAAW,QAAQ,IAAI;AAAA,EAAA,EACnC;AACF,QAAM,gBAAgB,OACnB,OAAO,CAAC,UAAU,yBAAyB,IAAI,MAAM,IAAI,CAAC,EAC1D,IAAI,CAAC,WAAW;AAAA,IACf,MAAM,MAAM;AAAA,IACZ,MAAM,MAAM;AAAA,IACZ,UAAU,MAAM;AAAA,IAChB,SAAS,MAAM;AAAA,IACf,YAAY,MAAM;AAAA,EAAA,EAClB;AAEJ,SAAO;AAAA,IACL,MAAM,OAAO;AAAA,IACb,eAAe,OAAO;AAAA,IACtB,YAAY,OAAO;AAAA,IACnB,WAAW,OAAO,QAAQ;AAAA,IAC1B,aAAa,OAAO;AAAA,IACpB,SAAS,OAAO;AAAA,IAChB,YAAY,OAAO;AAAA,IACnB;AAAA,IACA;AAAA,IACA,SAAS,OAAO,KAAK,OAAO,OAAO,EAAE,KAAA;AAAA,IACrC,UAAU,eAAe,MAAM;AAAA,IAC/B,sBAAsB,qBAAqB,MAAM;AAAA,IACjD,MAAM,UAAU,QAAQ,CAAA;AAAA,IACxB,SAAS,UAAU;AAAA,IACnB,OAAO,UAAU,SAAS,CAAA;AAAA,EAAC;AAE/B;AAEA,SAAS,eACP,QAC0B;AAC1B,SAAO;AAAA,IACL,GAAG,mBAAmB,OAAO,MAAM;AAAA,IACnC,GAAG,mBAAmB,OAAO,MAAM;AAAA,IACnC,GAAG,mBAAmB,OAAO,MAAM;AAAA,IACnC,GAAG,WAAW,MAAM;AAAA,EAAA;AAExB;AAEA,SAAS,mBACP,MACA,QAC0B;AAC1B,QAAM,SAAS,OAAO,kBAAkB,IAAI;AAC5C,MAAI,CAAC,OAAQ,QAAO,CAAA;AACpB,QAAM,aAAa,qBAAqB,MAAM;AAC9C,SAAO,WAAW,IAAI,CAAC,eAAe;AAAA,IACpC;AAAA,IACA,MACE,SAAS,QACL,GAAG,OAAO,UAAU,IAAI,SAAS,KACjC,GAAG,OAAO,UAAU,YAAA,CAAa,IAAI,SAAS;AAAA,IACpD;AAAA,IACA,YAAY,OAAO,iBAAiB,OAAO;AAAA,IAC3C,MAAM,SAAS,QAAQ,QAAQ,QAAQ,SAAS,IAAI;AAAA,IACpD,QAAQ,SAAS,QAAQ,UAAU,SAAS,IAAI;AAAA,EAAA,EAChD;AACJ;AAEA,SAAS,qBAAqB,QAA2B;AACvD,MAAI,WAAW,KAAM,QAAO,CAAC,GAAG,mBAAmB;AACnD,MAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GAAG;AAClE,WAAO,CAAA;AAAA,EACT;AACA,QAAM,eAAe;AACrB,QAAM,OAAO,MAAM,QAAQ,aAAa,OAAO,IAC3C,aAAa,UACb;AACJ,QAAM,WAAW,IAAI,IAAI,aAAa,WAAW,CAAA,CAAE;AACnD,SAAO,CAAC,GAAG,IAAI,IAAI,KAAK,OAAO,CAAC,cAAc,CAAC,SAAS,IAAI,SAAS,CAAC,CAAC,CAAC;AAC1E;AAEA,SAAS,WAAW,QAAyD;AAC3E,UAAQ,OAAO,SAAS,CAAA,GAAI,IAAI,CAAC,UAAU;AAAA,IACzC,MAAM;AAAA,IACN,MAAM,KAAK,SAAS;AAAA,IACpB,WAAW,KAAK,SAAS;AAAA,IACzB,aAAa,KAAK,SAAS;AAAA,IAC3B,YAAY,OAAO,iBAAiB,OAAO;AAAA,EAAA,EAC3C;AACJ;AAEA,SAAS,QAAQ,QAA+B,WAA2B;AACzE,QAAM,aAAa,OAAO,iBAAiB;AAC3C,QAAM,iBACJ,OAAO,eAAe,YAAY,OAAO,WAAW,SAAS,WACzD,WAAW,OACX,OAAO,WAAW,WAAW,KAAK,GAAG;AAC3C,MAAI,cAAc,UAAU,cAAc,UAAU;AAClD,WAAO,IAAI,cAAc;AAAA,EAC3B;AACA,MAAI,oBAAoB,SAAS,SAAS,GAAG;AAC3C,WAAO,IAAI,cAAc;AAAA,EAC3B;AACA,SAAO,IAAI,cAAc,IAAI,SAAS;AACxC;AAEA,SAAS,UAAU,WAA2B;AAC5C,UAAQ,WAAA;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EAAA;AAEb;AAEA,SAAS,qBAAqB,QAAyC;AACrE,QAAM,+BAAe,IAAA;AACrB,aAAW,SAAS,OAAO,OAAO,OAAO,MAAM,GAAG;AAChD,QAAI,MAAM,SAAS,aAAc,UAAS,IAAI,YAAY;AAC1D,QAAI,MAAM,SAAS,kBAAmB,UAAS,IAAI,iBAAiB;AACpE,QAAI,MAAM,SAAS,YAAa,UAAS,IAAI,WAAW;AACxD,QAAI,MAAM,SAAS,aAAc,UAAS,IAAI,YAAY;AAAA,EAC5D;AACA,MAAI,OAAO,YAAY,eAAgB,UAAS,IAAI,cAAc;AAClE,MAAI,OAAO,YAAY,mBAAoB,UAAS,IAAI,kBAAkB;AAC1E,MACE,OAAO,YAAY,gCACnB,OAAO,OAAO,YACd,OAAO,OAAO,QACd;AACA,aAAS,IAAI,4BAA4B;AAAA,EAC3C;AACA,MACE,OAAO,KAAK,OAAO,QAAQ,WAAW,CAAA,CAAE,EAAE;AAAA,IACxC,CAAC,SAAS,OAAO,QAAQ,QAAQ,IAAI,GAAG,SAAS;AAAA,EAAA,GAEnD;AACA,aAAS,IAAI,aAAa;AAAA,EAC5B;AACA,SAAO,CAAC,GAAG,QAAQ,EAAE,KAAA;AACvB;AAEA,SAAS,uBACP,SACA,iBACA;AACA,QAAM,SAAS,QAAQ,QAAQ,CAAC,WAAW,OAAO,MAAM;AACxD,SAAO;AAAA,IACL,kBAAkB,OAAO,OAAO,CAAC,UAAU,MAAM,SAAS,YAAY,EACnE;AAAA,IACH,uBAAuB,OAAO;AAAA,MAC5B,CAAC,UAAU,MAAM,SAAS;AAAA,IAAA,EAC1B;AAAA,IACF,qBAAqB,QAAQ;AAAA,MAAO,CAAC,WACnC,OAAO,qBAAqB,SAAS,cAAc;AAAA,IAAA,EACnD;AAAA,IACF,qBAAqB,QAAQ;AAAA,MAAO,CAAC,WACnC,OAAO,qBAAqB,SAAS,kBAAkB;AAAA,IAAA,EACvD;AAAA,IACF,yBAAyB,QAAQ;AAAA,MAAO,CAAC,WACvC,OAAO,qBAAqB,SAAS,4BAA4B;AAAA,IAAA,EACjE;AAAA,IACF,aAAa,gBAAgB;AAAA,MAC3B,CAAC,OAAO,WACN,QACA,OAAO,OAAO,OAAO,QAAQ,WAAW,CAAA,CAAE,EAAE;AAAA,QAC1C,CAAC,WAAW,OAAO,SAAS;AAAA,MAAA,EAC5B;AAAA,MACJ;AAAA,IAAA;AAAA,EACF;AAEJ;AAEA,SAAS,WACP,QACA,WACoB;AACpB,QAAM,aAAa,aAAa,SAAS;AACzC,SAAO,OAAO,QAAQ,QAAQ,UAAU,GAAG;AAC7C;AAEA,SAAS,YACP,SAC2C;AAC3C,QAAM,SAAS,KAAK,SAAS,KAAK;AAClC,MAAI,CAAC,WAAW,MAAM,UAAU,CAAA;AAChC,QAAM,UAAqD,CAAA;AAC3D,aAAW,YAAY,UAAU,MAAM,GAAG;AACxC,QAAI,CAAC,SAAS,SAAS,KAAK,EAAG;AAC/B,UAAM,UAAU,aAAa,UAAU,MAAM;AAC7C,QAAI,CAAC,QAAQ,SAAS,cAAc,EAAG;AACvC,UAAM,WAAW,QAAQ,MAAM,yCAAyC;AACxE,YAAQ,KAAK;AAAA,MACX,UAAU,SAAS,SAAS,QAAQ;AAAA,MACpC,KAAK,WAAW,CAAC;AAAA,IAAA,CAClB;AAAA,EACH;AACA,SAAO;AACT;AAEA,SAAS,UAAU,KAAuB;AACxC,QAAM,QAAkB,CAAA;AACxB,aAAW,SAAS,YAAY,KAAK,EAAE,eAAe,KAAA,CAAM,GAAG;AAC7D,QACE,MAAM,SAAS,kBACf,MAAM,SAAS,UACf,MAAM,SAAS,eACf;AACA;AAAA,IACF;AACA,UAAM,WAAW,KAAK,KAAK,MAAM,IAAI;AACrC,QAAI,MAAM,eAAe;AACvB,YAAM,KAAK,GAAG,UAAU,QAAQ,CAAC;AAAA,IACnC,WAAW,MAAM,UAAU;AACzB,YAAM,KAAK,QAAQ;AAAA,IACrB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,aAAa,SAAiD;AACrE,QAAM,SAAiC,CAAA;AACvC,aAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,OAAO,GAAG;AACpD,QAAI,CAAC,OAAQ;AACb,UAAM,UACJ,aAAa,SAAS,OAAO,UAAU,aAAa,OAAO,MAAM,MAAM;AACzE,WAAO,IAAI,IAAI,WAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAAA,EAClE;AACA,SAAO;AACT;AAIA,SAAS,eAAe,MAAkD;AACxE,SAAO,OAAO,EAAE,KAAA,IAAS;AAC3B;AAEA,SAAS,aAAa,SAAiB,MAAkC;AACvE,QAAM,WAAW,KAAK,SAAS,IAAI;AACnC,SAAO,WAAW,QAAQ,IAAI,WAAW;AAC3C;AAEA,SAAS,gBAAgB,SAAyC;AAChE,QAAM,OAAO,KAAK,SAAS,cAAc;AACzC,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,SAAO,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AAC9C;AAEA,SAAS,OAAO,OAAwC;AACtD,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IACrE,QACD,CAAA;AACN;AAEA,SAAS,WAAW,cAAiC;AACnD,MAAI,OAAO,iBAAiB,SAAU,QAAO,CAAC,GAAG;AACjD,MACE,OAAO,iBAAiB,YACxB,iBAAiB,QACjB,MAAM,QAAQ,YAAY,GAC1B;AACA,WAAO,CAAA;AAAA,EACT;AACA,SAAO,OAAO,KAAK,YAAY,EAAE,KAAA;AACnC;AAEA,SAAS,aAAa,OAAuB;AAC3C,SAAO,MACJ,QAAQ,sBAAsB,OAAO,EACrC,QAAQ,WAAW,GAAG,EACtB,YAAA;AACL;AAEA,SAAS,yBAAyB,UAAwC;AACxE,QAAM,aAAa,KAAK,MAAM,KAAK,UAAU,QAAQ,CAAC;AAItD,SAAO,WAAW;AAClB,SAAO;AACT;AAEA,SAAS,WAAW,OAAwB;AAC1C,SAAO,KAAK,UAAU,SAAS,KAAK,GAAG,MAAM,CAAC;AAChD;AAEA,SAAS,SAAS,OAAyB;AACzC,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,QAAQ;AACnD,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,WAAO,OAAO;AAAA,MACZ,OAAO,QAAQ,KAAgC,EAC5C,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,EACrC,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,SAAS,KAAK,CAAC,CAAC;AAAA,IAAA;AAAA,EAEnD;AACA,SAAO;AACT;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-config.d.ts","sourceRoot":"","sources":["../src/lazy-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAE9D;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAEhD;;;OAGG;IACH,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAEzE;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;CACtC;AAID;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,cAAc,GACvB,IAAI,CAQN;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE1E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,kBAAkB,CAU7B;AA2GD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvE,MAAM,EAAE,CAAC,EACT,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,CAAC,CAAC,CAwCZ;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,OAAO,GACZ,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"lazy-config.d.ts","sourceRoot":"","sources":["../src/lazy-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAE9D;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAEhD;;;OAGG;IACH,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAEzE;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;CACtC;AAID;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,cAAc,GACvB,IAAI,CAQN;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE1E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,kBAAkB,CAU7B;AA2GD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvE,MAAM,EAAE,CAAC,EACT,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,CAAC,CAAC,CAwCZ;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,OAAO,GACZ,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,SAAS,CAqB5C"}
|
package/dist/lazy-config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-config.js","sources":["../src/lazy-config.ts"],"sourcesContent":["/**\n * Lazy / execute-time agent_config resolvers.\n *\n * Persisted agent configs (e.g. `_smrt_agent_schedules.agent_config`) freeze\n * any value resolved at sync time, including env-derived ones. When operators\n * rotate an env var the persisted snapshot goes stale until the schedule is\n * rewritten.\n *\n * To avoid that, callers can store **sentinel references** in the persisted\n * config and register matching resolvers that run at execute time. The\n * runtime walks the agent_config tree, replaces every recognised sentinel\n * with the resolver's current return value, and only then hands the config\n * to the agent.\n *\n * Two registration shapes are supported:\n *\n * 1. **Global named resolvers** — register a callback by name once at\n * application startup. Use this for shared resolvers like\n * `resolveSharedAssetStorage`.\n *\n * ```ts\n * import { registerConfigResolver } from '@happyvertical/smrt-agents';\n *\n * registerConfigResolver('sharedAssetStorage', () =>\n * resolveSharedAssetStorage(),\n * );\n * ```\n *\n * Then in the persisted agent_config:\n * ```jsonc\n * { \"assetStorage\": { \"$env\": \"sharedAssetStorage\" } }\n * ```\n *\n * 2. **Per-agent class resolvers** — declare them as a static\n * `configResolvers` map on the agent class. The runtime applies them on\n * top of the persisted config keyed by the same name as the resolver.\n *\n * ```ts\n * class Praeco extends Agent {\n * static configResolvers = {\n * assetStorage: () => resolveSharedAssetStorage(),\n * };\n * }\n * ```\n *\n * Both shapes can be combined freely. Global resolvers always take precedence\n * for `$env` sentinels; class resolvers fill in / override fields by name.\n *\n * @module\n */\n\n/**\n * A lazy resolver — synchronous or async. Return values are merged into the\n * config tree at execute time. Throwing is allowed but is treated as a\n * resolution failure (see {@link ResolveLazyConfigOptions.onError}).\n */\nexport type ConfigResolver = () => unknown | Promise<unknown>;\n\n/**\n * Sentinel object recognised inside agent_config to defer a value to a named\n * resolver. The runtime accepts both `$env` (preferred) and `$resolver` for\n * symmetry with similar systems.\n */\nexport interface LazyConfigSentinel {\n $env?: string;\n $resolver?: string;\n}\n\n/**\n * Options for resolving the lazy parts of a config tree.\n */\nexport interface ResolveLazyConfigOptions {\n /**\n * Per-class resolvers — typically the agent class's static\n * `configResolvers`. Applied as a key-by-key overlay on top of the\n * sentinel-resolved config.\n */\n classResolvers?: Record<string, ConfigResolver>;\n\n /**\n * Optional override for the global resolver registry. When omitted the\n * shared module-scoped registry is used. Useful for tests.\n */\n resolvers?: Map<string, ConfigResolver> | Record<string, ConfigResolver>;\n\n /**\n * What to do when a sentinel references a resolver that isn't registered,\n * or when a resolver throws.\n *\n * - `'leave'` (default): the sentinel is left in place verbatim. The\n * downstream consumer is responsible for handling it.\n * - `'omit'`: the sentinel key is removed from the parent object/array.\n * - `'throw'`: re-throw the underlying error (or throw a new one for\n * missing resolvers).\n */\n onError?: 'leave' | 'omit' | 'throw';\n}\n\nconst globalResolvers = new Map<string, ConfigResolver>();\n\n/**\n * Register a named resolver that participates in lazy config resolution.\n *\n * Calling this with the same name twice replaces the previous resolver — the\n * registry is intentionally last-write-wins so application boot ordering\n * doesn't have to be fragile.\n */\nexport function registerConfigResolver(\n name: string,\n resolver: ConfigResolver,\n): void {\n if (typeof name !== 'string' || name.length === 0) {\n throw new Error('registerConfigResolver: name must be a non-empty string');\n }\n if (typeof resolver !== 'function') {\n throw new Error('registerConfigResolver: resolver must be a function');\n }\n globalResolvers.set(name, resolver);\n}\n\n/**\n * Remove a previously registered resolver. Returns `true` if a resolver was\n * removed.\n */\nexport function unregisterConfigResolver(name: string): boolean {\n return globalResolvers.delete(name);\n}\n\n/**\n * Get the resolver registered under the given name, if any.\n */\nexport function getConfigResolver(name: string): ConfigResolver | undefined {\n return globalResolvers.get(name);\n}\n\n/**\n * List the names of all currently-registered global resolvers.\n */\nexport function listConfigResolvers(): string[] {\n return Array.from(globalResolvers.keys()).sort();\n}\n\n/**\n * Reset the global resolver registry. Primarily for tests.\n */\nexport function resetConfigResolvers(): void {\n globalResolvers.clear();\n}\n\n/**\n * Type guard that returns `true` if `value` is a lazy config sentinel.\n *\n * Recognises objects of the form `{ $env: string }` or\n * `{ $resolver: string }` exactly — extra keys disqualify the value so\n * arbitrary user data isn't accidentally treated as a sentinel.\n */\nexport function isLazyConfigSentinel(\n value: unknown,\n): value is LazyConfigSentinel {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false;\n }\n const keys = Object.keys(value as Record<string, unknown>);\n if (keys.length !== 1) return false;\n const key = keys[0];\n if (key !== '$env' && key !== '$resolver') return false;\n const resolverName = (value as Record<string, unknown>)[key];\n return typeof resolverName === 'string' && resolverName.length > 0;\n}\n\n/**\n * Extract the resolver name from a sentinel.\n */\nfunction getSentinelName(sentinel: LazyConfigSentinel): string {\n return (sentinel.$env ?? sentinel.$resolver) as string;\n}\n\nfunction normalizeResolverMap(\n resolvers: Map<string, ConfigResolver> | Record<string, ConfigResolver>,\n): Map<string, ConfigResolver> {\n if (resolvers instanceof Map) return resolvers;\n return new Map(Object.entries(resolvers));\n}\n\nconst REMOVE = Symbol('LazyConfig.REMOVE');\n\n/**\n * Maximum traversal depth before {@link resolveValue} bails out. Real\n * `agent_config` payloads are shallow (2–3 levels); a high cap here exists\n * solely to prevent stack overflow from a malicious or buggy persisted\n * config without rejecting any legitimate input. When the cap is hit the\n * value is returned verbatim — same fail-soft as for a missing resolver.\n */\nconst MAX_DEPTH = 64;\n\ninterface ResolveContext {\n resolvers: Map<string, ConfigResolver>;\n onError: NonNullable<ResolveLazyConfigOptions['onError']>;\n /**\n * Map from input object/array to its resolved replacement. Serves two\n * purposes simultaneously:\n * 1. Cycle protection — without it, a self-referential config would\n * recurse forever.\n * 2. DAG fidelity — a sub-tree referenced from two places gets cloned\n * once and the same clone is reused for every reference, preserving\n * structure and ensuring sentinels under shared references are still\n * resolved.\n */\n resolved: WeakMap<object, unknown>;\n}\n\nasync function resolveValue(\n value: unknown,\n ctx: ResolveContext,\n depth: number,\n): Promise<unknown> {\n if (isLazyConfigSentinel(value)) {\n const name = getSentinelName(value);\n const resolver = ctx.resolvers.get(name);\n if (!resolver) {\n if (ctx.onError === 'throw') {\n throw new Error(\n `Lazy config sentinel references unknown resolver \"${name}\"`,\n );\n }\n if (ctx.onError === 'omit') return REMOVE;\n return value;\n }\n\n try {\n return await resolver();\n } catch (error) {\n if (ctx.onError === 'throw') throw error;\n if (ctx.onError === 'omit') return REMOVE;\n return value;\n }\n }\n\n if (depth > MAX_DEPTH) {\n // Defensive: refuse to recurse further. The original sub-tree is\n // returned as-is; sentinels beyond this point will stay unresolved.\n return value;\n }\n\n if (Array.isArray(value)) {\n const cached = ctx.resolved.get(value);\n if (cached !== undefined) return cached;\n const next: unknown[] = [];\n // Register the placeholder *before* descending so that a cycle pointing\n // back at this array sees the in-progress array (and stops recursing).\n ctx.resolved.set(value, next);\n for (const entry of value) {\n const resolved = await resolveValue(entry, ctx, depth + 1);\n if (resolved !== REMOVE) next.push(resolved);\n }\n return next;\n }\n\n if (value && typeof value === 'object') {\n const cached = ctx.resolved.get(value);\n if (cached !== undefined) return cached;\n const next: Record<string, unknown> = {};\n ctx.resolved.set(value, next);\n for (const [key, entry] of Object.entries(\n value as Record<string, unknown>,\n )) {\n const resolved = await resolveValue(entry, ctx, depth + 1);\n if (resolved !== REMOVE) next[key] = resolved;\n }\n return next;\n }\n\n return value;\n}\n\n/**\n * Resolve every lazy sentinel inside an `agent_config`-style record at\n * execute time. The input is not mutated; a new object is returned with\n * sentinel placeholders replaced by their resolver's current return value.\n *\n * Class-level resolvers from {@link ResolveLazyConfigOptions.classResolvers}\n * are applied as a final overlay so they always reflect the live value\n * regardless of what's persisted.\n *\n * @example Sentinel resolution\n * ```ts\n * registerConfigResolver('sharedAssetStorage', () => resolveStorage());\n *\n * const stored = { assetStorage: { $env: 'sharedAssetStorage' } };\n * const live = await resolveLazyConfig(stored);\n * // live.assetStorage === resolveStorage()\n * ```\n *\n * @example Class-level resolvers\n * ```ts\n * const live = await resolveLazyConfig(stored, {\n * classResolvers: { assetStorage: () => resolveStorage() },\n * });\n * ```\n */\nexport async function resolveLazyConfig<T extends Record<string, unknown>>(\n config: T,\n options: ResolveLazyConfigOptions = {},\n): Promise<T> {\n const onError = options.onError ?? 'leave';\n const resolvers = options.resolvers\n ? normalizeResolverMap(options.resolvers)\n : globalResolvers;\n\n const ctx: ResolveContext = {\n resolvers,\n onError,\n resolved: new WeakMap<object, unknown>(),\n };\n const sentinelResolved = (await resolveValue(config, ctx, 0)) as Record<\n string,\n unknown\n >;\n\n const result: Record<string, unknown> = { ...sentinelResolved };\n\n if (options.classResolvers) {\n for (const [key, resolver] of Object.entries(options.classResolvers)) {\n try {\n const value = await resolver();\n // Both `undefined` and `null` are treated as \"no overlay value\" —\n // the persisted record's existing value (if any) is preserved.\n // This matches the documented contract on `Agent.configResolvers`\n // and makes the common pattern `() => process.env.X ?? null` safe.\n if (value !== undefined && value !== null) {\n result[key] = value;\n }\n } catch (error) {\n if (onError === 'throw') throw error;\n if (onError === 'omit') {\n delete result[key];\n }\n // 'leave': preserve the existing (possibly persisted) value.\n }\n }\n }\n\n return result as T;\n}\n\n/**\n * Look up the static `configResolvers` map on a class (or any of its\n * ancestors). Returns `undefined` if none is declared.\n *\n * The lookup walks the prototype chain so a subclass can extend its parent's\n * resolvers without redeclaring them.\n */\nexport function getClassConfigResolvers(\n ctor: unknown,\n): Record<string, ConfigResolver> | undefined {\n if (typeof ctor !== 'function') return undefined;\n\n let current: any = ctor;\n let collected: Record<string, ConfigResolver> | undefined;\n\n while (current && current !== Function.prototype) {\n const resolvers = current.configResolvers;\n if (resolvers && typeof resolvers === 'object') {\n collected = {\n ...(resolvers as Record<string, ConfigResolver>),\n ...(collected ?? {}),\n };\n }\n current = Object.getPrototypeOf(current);\n }\n\n return collected;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lazy-config.js","sources":["../src/lazy-config.ts"],"sourcesContent":["/**\n * Lazy / execute-time agent_config resolvers.\n *\n * Persisted agent configs (e.g. `_smrt_agent_schedules.agent_config`) freeze\n * any value resolved at sync time, including env-derived ones. When operators\n * rotate an env var the persisted snapshot goes stale until the schedule is\n * rewritten.\n *\n * To avoid that, callers can store **sentinel references** in the persisted\n * config and register matching resolvers that run at execute time. The\n * runtime walks the agent_config tree, replaces every recognised sentinel\n * with the resolver's current return value, and only then hands the config\n * to the agent.\n *\n * Two registration shapes are supported:\n *\n * 1. **Global named resolvers** — register a callback by name once at\n * application startup. Use this for shared resolvers like\n * `resolveSharedAssetStorage`.\n *\n * ```ts\n * import { registerConfigResolver } from '@happyvertical/smrt-agents';\n *\n * registerConfigResolver('sharedAssetStorage', () =>\n * resolveSharedAssetStorage(),\n * );\n * ```\n *\n * Then in the persisted agent_config:\n * ```jsonc\n * { \"assetStorage\": { \"$env\": \"sharedAssetStorage\" } }\n * ```\n *\n * 2. **Per-agent class resolvers** — declare them as a static\n * `configResolvers` map on the agent class. The runtime applies them on\n * top of the persisted config keyed by the same name as the resolver.\n *\n * ```ts\n * class Praeco extends Agent {\n * static configResolvers = {\n * assetStorage: () => resolveSharedAssetStorage(),\n * };\n * }\n * ```\n *\n * Both shapes can be combined freely. Global resolvers always take precedence\n * for `$env` sentinels; class resolvers fill in / override fields by name.\n *\n * @module\n */\n\n/**\n * A lazy resolver — synchronous or async. Return values are merged into the\n * config tree at execute time. Throwing is allowed but is treated as a\n * resolution failure (see {@link ResolveLazyConfigOptions.onError}).\n */\nexport type ConfigResolver = () => unknown | Promise<unknown>;\n\n/**\n * Sentinel object recognised inside agent_config to defer a value to a named\n * resolver. The runtime accepts both `$env` (preferred) and `$resolver` for\n * symmetry with similar systems.\n */\nexport interface LazyConfigSentinel {\n $env?: string;\n $resolver?: string;\n}\n\n/**\n * Options for resolving the lazy parts of a config tree.\n */\nexport interface ResolveLazyConfigOptions {\n /**\n * Per-class resolvers — typically the agent class's static\n * `configResolvers`. Applied as a key-by-key overlay on top of the\n * sentinel-resolved config.\n */\n classResolvers?: Record<string, ConfigResolver>;\n\n /**\n * Optional override for the global resolver registry. When omitted the\n * shared module-scoped registry is used. Useful for tests.\n */\n resolvers?: Map<string, ConfigResolver> | Record<string, ConfigResolver>;\n\n /**\n * What to do when a sentinel references a resolver that isn't registered,\n * or when a resolver throws.\n *\n * - `'leave'` (default): the sentinel is left in place verbatim. The\n * downstream consumer is responsible for handling it.\n * - `'omit'`: the sentinel key is removed from the parent object/array.\n * - `'throw'`: re-throw the underlying error (or throw a new one for\n * missing resolvers).\n */\n onError?: 'leave' | 'omit' | 'throw';\n}\n\nconst globalResolvers = new Map<string, ConfigResolver>();\n\n/**\n * Register a named resolver that participates in lazy config resolution.\n *\n * Calling this with the same name twice replaces the previous resolver — the\n * registry is intentionally last-write-wins so application boot ordering\n * doesn't have to be fragile.\n */\nexport function registerConfigResolver(\n name: string,\n resolver: ConfigResolver,\n): void {\n if (typeof name !== 'string' || name.length === 0) {\n throw new Error('registerConfigResolver: name must be a non-empty string');\n }\n if (typeof resolver !== 'function') {\n throw new Error('registerConfigResolver: resolver must be a function');\n }\n globalResolvers.set(name, resolver);\n}\n\n/**\n * Remove a previously registered resolver. Returns `true` if a resolver was\n * removed.\n */\nexport function unregisterConfigResolver(name: string): boolean {\n return globalResolvers.delete(name);\n}\n\n/**\n * Get the resolver registered under the given name, if any.\n */\nexport function getConfigResolver(name: string): ConfigResolver | undefined {\n return globalResolvers.get(name);\n}\n\n/**\n * List the names of all currently-registered global resolvers.\n */\nexport function listConfigResolvers(): string[] {\n return Array.from(globalResolvers.keys()).sort();\n}\n\n/**\n * Reset the global resolver registry. Primarily for tests.\n */\nexport function resetConfigResolvers(): void {\n globalResolvers.clear();\n}\n\n/**\n * Type guard that returns `true` if `value` is a lazy config sentinel.\n *\n * Recognises objects of the form `{ $env: string }` or\n * `{ $resolver: string }` exactly — extra keys disqualify the value so\n * arbitrary user data isn't accidentally treated as a sentinel.\n */\nexport function isLazyConfigSentinel(\n value: unknown,\n): value is LazyConfigSentinel {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false;\n }\n const keys = Object.keys(value as Record<string, unknown>);\n if (keys.length !== 1) return false;\n const key = keys[0];\n if (key !== '$env' && key !== '$resolver') return false;\n const resolverName = (value as Record<string, unknown>)[key];\n return typeof resolverName === 'string' && resolverName.length > 0;\n}\n\n/**\n * Extract the resolver name from a sentinel.\n */\nfunction getSentinelName(sentinel: LazyConfigSentinel): string {\n return (sentinel.$env ?? sentinel.$resolver) as string;\n}\n\nfunction normalizeResolverMap(\n resolvers: Map<string, ConfigResolver> | Record<string, ConfigResolver>,\n): Map<string, ConfigResolver> {\n if (resolvers instanceof Map) return resolvers;\n return new Map(Object.entries(resolvers));\n}\n\nconst REMOVE = Symbol('LazyConfig.REMOVE');\n\n/**\n * Maximum traversal depth before {@link resolveValue} bails out. Real\n * `agent_config` payloads are shallow (2–3 levels); a high cap here exists\n * solely to prevent stack overflow from a malicious or buggy persisted\n * config without rejecting any legitimate input. When the cap is hit the\n * value is returned verbatim — same fail-soft as for a missing resolver.\n */\nconst MAX_DEPTH = 64;\n\ninterface ResolveContext {\n resolvers: Map<string, ConfigResolver>;\n onError: NonNullable<ResolveLazyConfigOptions['onError']>;\n /**\n * Map from input object/array to its resolved replacement. Serves two\n * purposes simultaneously:\n * 1. Cycle protection — without it, a self-referential config would\n * recurse forever.\n * 2. DAG fidelity — a sub-tree referenced from two places gets cloned\n * once and the same clone is reused for every reference, preserving\n * structure and ensuring sentinels under shared references are still\n * resolved.\n */\n resolved: WeakMap<object, unknown>;\n}\n\nasync function resolveValue(\n value: unknown,\n ctx: ResolveContext,\n depth: number,\n): Promise<unknown> {\n if (isLazyConfigSentinel(value)) {\n const name = getSentinelName(value);\n const resolver = ctx.resolvers.get(name);\n if (!resolver) {\n if (ctx.onError === 'throw') {\n throw new Error(\n `Lazy config sentinel references unknown resolver \"${name}\"`,\n );\n }\n if (ctx.onError === 'omit') return REMOVE;\n return value;\n }\n\n try {\n return await resolver();\n } catch (error) {\n if (ctx.onError === 'throw') throw error;\n if (ctx.onError === 'omit') return REMOVE;\n return value;\n }\n }\n\n if (depth > MAX_DEPTH) {\n // Defensive: refuse to recurse further. The original sub-tree is\n // returned as-is; sentinels beyond this point will stay unresolved.\n return value;\n }\n\n if (Array.isArray(value)) {\n const cached = ctx.resolved.get(value);\n if (cached !== undefined) return cached;\n const next: unknown[] = [];\n // Register the placeholder *before* descending so that a cycle pointing\n // back at this array sees the in-progress array (and stops recursing).\n ctx.resolved.set(value, next);\n for (const entry of value) {\n const resolved = await resolveValue(entry, ctx, depth + 1);\n if (resolved !== REMOVE) next.push(resolved);\n }\n return next;\n }\n\n if (value && typeof value === 'object') {\n const cached = ctx.resolved.get(value);\n if (cached !== undefined) return cached;\n const next: Record<string, unknown> = {};\n ctx.resolved.set(value, next);\n for (const [key, entry] of Object.entries(\n value as Record<string, unknown>,\n )) {\n const resolved = await resolveValue(entry, ctx, depth + 1);\n if (resolved !== REMOVE) next[key] = resolved;\n }\n return next;\n }\n\n return value;\n}\n\n/**\n * Resolve every lazy sentinel inside an `agent_config`-style record at\n * execute time. The input is not mutated; a new object is returned with\n * sentinel placeholders replaced by their resolver's current return value.\n *\n * Class-level resolvers from {@link ResolveLazyConfigOptions.classResolvers}\n * are applied as a final overlay so they always reflect the live value\n * regardless of what's persisted.\n *\n * @example Sentinel resolution\n * ```ts\n * registerConfigResolver('sharedAssetStorage', () => resolveStorage());\n *\n * const stored = { assetStorage: { $env: 'sharedAssetStorage' } };\n * const live = await resolveLazyConfig(stored);\n * // live.assetStorage === resolveStorage()\n * ```\n *\n * @example Class-level resolvers\n * ```ts\n * const live = await resolveLazyConfig(stored, {\n * classResolvers: { assetStorage: () => resolveStorage() },\n * });\n * ```\n */\nexport async function resolveLazyConfig<T extends Record<string, unknown>>(\n config: T,\n options: ResolveLazyConfigOptions = {},\n): Promise<T> {\n const onError = options.onError ?? 'leave';\n const resolvers = options.resolvers\n ? normalizeResolverMap(options.resolvers)\n : globalResolvers;\n\n const ctx: ResolveContext = {\n resolvers,\n onError,\n resolved: new WeakMap<object, unknown>(),\n };\n const sentinelResolved = (await resolveValue(config, ctx, 0)) as Record<\n string,\n unknown\n >;\n\n const result: Record<string, unknown> = { ...sentinelResolved };\n\n if (options.classResolvers) {\n for (const [key, resolver] of Object.entries(options.classResolvers)) {\n try {\n const value = await resolver();\n // Both `undefined` and `null` are treated as \"no overlay value\" —\n // the persisted record's existing value (if any) is preserved.\n // This matches the documented contract on `Agent.configResolvers`\n // and makes the common pattern `() => process.env.X ?? null` safe.\n if (value !== undefined && value !== null) {\n result[key] = value;\n }\n } catch (error) {\n if (onError === 'throw') throw error;\n if (onError === 'omit') {\n delete result[key];\n }\n // 'leave': preserve the existing (possibly persisted) value.\n }\n }\n }\n\n return result as T;\n}\n\n/**\n * Look up the static `configResolvers` map on a class (or any of its\n * ancestors). Returns `undefined` if none is declared.\n *\n * The lookup walks the prototype chain so a subclass can extend its parent's\n * resolvers without redeclaring them.\n */\nexport function getClassConfigResolvers(\n ctor: unknown,\n): Record<string, ConfigResolver> | undefined {\n if (typeof ctor !== 'function') return undefined;\n\n let current: unknown = ctor;\n let collected: Record<string, ConfigResolver> | undefined;\n\n while (current && current !== Function.prototype) {\n // Each link in the prototype chain is an object/function; read its optional\n // static `configResolvers` map through a typed shape rather than `any`.\n const resolvers = (current as { configResolvers?: unknown })\n .configResolvers;\n if (resolvers && typeof resolvers === 'object') {\n collected = {\n ...(resolvers as Record<string, ConfigResolver>),\n ...(collected ?? {}),\n };\n }\n current = Object.getPrototypeOf(current);\n }\n\n return collected;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/manifest/generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAkB/D;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IAErC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IAGxB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAG/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;CACrC;
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/manifest/generator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAkB/D;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IAErC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IAGxB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAG/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;CACrC;AAuCD;;;;;GAKG;AACH,qBAAa,eAAe;IAC1B;;OAEG;IACG,QAAQ,CACZ,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,mBAAmB,CAAC;IAkC/B,OAAO,CAAC,kBAAkB;IAa1B,OAAO,CAAC,iBAAiB;IAmBzB;;OAEG;IACH,OAAO,CAAC,aAAa;IAUrB;;OAEG;YACW,gBAAgB;IAoD9B;;;;;OAKG;YACW,oBAAoB;IAyDlC;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAiCtB;;OAEG;YACW,WAAW;IAyCzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAuCxB;;OAEG;YACW,yBAAyB;IAkFvC;;OAEG;YACW,uBAAuB;IAwCrC;;OAEG;IACH,OAAO,CAAC,eAAe;IAavB;;OAEG;IACH,OAAO,CAAC,mBAAmB;CAuB5B"}
|