@happyvertical/smrt-core 0.38.2 → 0.38.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/AGENTS.md +11 -2
  2. package/dist/__tests__/fixtures/advisor-test-classes.d.ts +9 -0
  3. package/dist/__tests__/fixtures/advisor-test-classes.d.ts.map +1 -1
  4. package/dist/change-feed.d.ts +19 -10
  5. package/dist/change-feed.d.ts.map +1 -1
  6. package/dist/change-feed.js +43 -13
  7. package/dist/change-feed.js.map +1 -1
  8. package/dist/change-signals.d.ts +83 -0
  9. package/dist/change-signals.d.ts.map +1 -0
  10. package/dist/change-signals.js +272 -0
  11. package/dist/change-signals.js.map +1 -0
  12. package/dist/collection-cache.d.ts +21 -0
  13. package/dist/collection-cache.d.ts.map +1 -1
  14. package/dist/collection-cache.js +0 -0
  15. package/dist/collection-cache.js.map +1 -1
  16. package/dist/config.d.ts +1 -1
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js.map +1 -1
  19. package/dist/decorators/index.d.ts +8 -0
  20. package/dist/decorators/index.d.ts.map +1 -1
  21. package/dist/decorators/index.js.map +1 -1
  22. package/dist/errors.js +2 -1
  23. package/dist/errors.js.map +1 -1
  24. package/dist/generators/changes-route.d.ts +15 -27
  25. package/dist/generators/changes-route.d.ts.map +1 -1
  26. package/dist/generators/changes-route.js +17 -7
  27. package/dist/generators/changes-route.js.map +1 -1
  28. package/dist/generators/cli.d.ts +3 -0
  29. package/dist/generators/cli.d.ts.map +1 -1
  30. package/dist/generators/cli.js +7 -4
  31. package/dist/generators/cli.js.map +1 -1
  32. package/dist/generators/conditional-get.d.ts +17 -6
  33. package/dist/generators/conditional-get.d.ts.map +1 -1
  34. package/dist/generators/conditional-get.js +19 -13
  35. package/dist/generators/conditional-get.js.map +1 -1
  36. package/dist/generators/events-route.d.ts +72 -0
  37. package/dist/generators/events-route.d.ts.map +1 -0
  38. package/dist/generators/events-route.js +234 -0
  39. package/dist/generators/events-route.js.map +1 -0
  40. package/dist/generators/index.d.ts +1 -0
  41. package/dist/generators/index.d.ts.map +1 -1
  42. package/dist/generators/index.js +2 -1
  43. package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
  44. package/dist/generators/mcp-runtime-template.js +11 -5
  45. package/dist/generators/mcp-runtime-template.js.map +1 -1
  46. package/dist/generators/mcp.d.ts +3 -0
  47. package/dist/generators/mcp.d.ts.map +1 -1
  48. package/dist/generators/mcp.js +18 -9
  49. package/dist/generators/mcp.js.map +1 -1
  50. package/dist/generators/rest.d.ts +7 -1
  51. package/dist/generators/rest.d.ts.map +1 -1
  52. package/dist/generators/rest.js +77 -13
  53. package/dist/generators/rest.js.map +1 -1
  54. package/dist/generators.js +2 -1
  55. package/dist/index.d.ts +1 -0
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +3 -1
  58. package/dist/manifest/generator.js.map +1 -1
  59. package/dist/manifest/manifest-loader.js.map +1 -1
  60. package/dist/manifest/static-manifest.d.ts.map +1 -1
  61. package/dist/manifest/static-manifest.js +7 -3
  62. package/dist/manifest/static-manifest.js.map +1 -1
  63. package/dist/manifest/store.js +1 -1
  64. package/dist/manifest/store.js.map +1 -1
  65. package/dist/manifest/test-manifest-stub.d.ts.map +1 -1
  66. package/dist/manifest/test-manifest-stub.js +1171 -90
  67. package/dist/manifest/test-manifest-stub.js.map +1 -1
  68. package/dist/manifest.json +9 -3
  69. package/dist/mcp-advisor/tools/preview-api-endpoints.d.ts +1 -0
  70. package/dist/mcp-advisor/tools/preview-api-endpoints.d.ts.map +1 -1
  71. package/dist/mcp-advisor/types.d.ts +4 -0
  72. package/dist/mcp-advisor/types.d.ts.map +1 -1
  73. package/dist/migrations/tracker.js +5 -4
  74. package/dist/migrations/tracker.js.map +1 -1
  75. package/dist/migrations/types.d.ts +1 -1
  76. package/dist/migrations/types.d.ts.map +1 -1
  77. package/dist/object.d.ts +17 -8
  78. package/dist/object.d.ts.map +1 -1
  79. package/dist/object.js +42 -26
  80. package/dist/object.js.map +1 -1
  81. package/dist/registry/class-registration.d.ts.map +1 -1
  82. package/dist/registry/class-registration.js +12 -0
  83. package/dist/registry/class-registration.js.map +1 -1
  84. package/dist/registry/shared-state.d.ts +2 -1
  85. package/dist/registry/shared-state.d.ts.map +1 -1
  86. package/dist/registry/shared-state.js.map +1 -1
  87. package/dist/registry/types.d.ts.map +1 -1
  88. package/dist/registry.d.ts.map +1 -1
  89. package/dist/registry.js +1 -1
  90. package/dist/registry.js.map +1 -1
  91. package/dist/runtime/client.d.ts.map +1 -1
  92. package/dist/runtime/client.js.map +1 -1
  93. package/dist/scanner/manifest-generator.js.map +1 -1
  94. package/dist/scanner/types.d.ts +7 -0
  95. package/dist/scanner/types.d.ts.map +1 -1
  96. package/dist/schema/ddl/base-strategy.js +1 -1
  97. package/dist/schema/ddl/base-strategy.js.map +1 -1
  98. package/dist/scripts/migrate-datetime-to-timestamp.ts +1 -1
  99. package/dist/signals/sanitizer.d.ts.map +1 -1
  100. package/dist/signals/sanitizer.js.map +1 -1
  101. package/dist/smrt-knowledge.json +8 -8
  102. package/dist/testing/index.d.ts +1 -0
  103. package/dist/testing/index.d.ts.map +1 -1
  104. package/dist/testing/index.js +2 -1
  105. package/dist/testing/notifications-stub.d.ts +24 -0
  106. package/dist/testing/notifications-stub.d.ts.map +1 -0
  107. package/dist/testing/notifications-stub.js +62 -0
  108. package/dist/testing/notifications-stub.js.map +1 -0
  109. package/dist/testing.js +2 -1
  110. package/dist/tools/tool-generator.js +1 -1
  111. package/dist/tools/tool-generator.js.map +1 -1
  112. package/dist/vite-plugin/changes-route.d.ts +14 -0
  113. package/dist/vite-plugin/changes-route.d.ts.map +1 -1
  114. package/dist/vite-plugin/changes-route.js +2 -2
  115. package/dist/vite-plugin/changes-route.js.map +1 -1
  116. package/dist/vite-plugin/events-route.d.ts +9 -0
  117. package/dist/vite-plugin/events-route.d.ts.map +1 -0
  118. package/dist/vite-plugin/events-route.js +155 -0
  119. package/dist/vite-plugin/events-route.js.map +1 -0
  120. package/dist/vite-plugin/index.d.ts +8 -0
  121. package/dist/vite-plugin/index.d.ts.map +1 -1
  122. package/dist/vite-plugin/index.js +8 -2
  123. package/dist/vite-plugin/index.js.map +1 -1
  124. package/dist/vite-plugin/sveltekit-generator.d.ts +8 -0
  125. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  126. package/dist/vite-plugin/sveltekit-generator.js +239 -38
  127. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  128. package/dist/vite-plugin/web-collections.js +1 -1
  129. package/dist/vite-plugin/web-collections.js.map +1 -1
  130. package/package.json +12 -12
@@ -1 +1 @@
1
- {"version":3,"file":"rest.js","names":[],"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 { getTableVersion } from '../change-feed';\nimport type { SmrtCollection } from '../collection';\nimport {\n isTenantScopedClassResolved,\n resolveDispatchTenantScope,\n} from '../dispatch';\nimport type { SmrtObject } from '../object';\nimport { ObjectRegistry } from '../registry';\nimport type { RegisteredClass, SmrtObjectConstructor } from '../registry/types';\nimport {\n processSyncApplyBatch,\n SYNC_APPLY_ROUTE_SEGMENTS,\n type SyncApplyOp,\n type SyncApplyTarget,\n} from '../sync/apply';\nimport { handleChangesRoute } from './changes-route';\nimport {\n canonicalReadRepresentation,\n computeTableVersionEtag,\n ifNoneMatchHasConcreteMatch,\n ifNoneMatchSatisfied,\n resolveReadCacheControl,\n resolveTenantEtagDiscriminator,\n versionConditionalResponse,\n warnIfSharedCacheNeutralized,\n warnIfTenantScopedPublicRead,\n} from './conditional-get';\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 * Resolve the database for generator-level routes that are not object-scoped\n * (currently the `_changes` feed, #1758).\n *\n * Prefers the explicit `APIContext.db`, but falls back to the first\n * registered collection's live handle so the feed works under the documented\n * `registerCollection()` wiring, where callers configure collections with a\n * database but never populate `APIContext.db`. Without the fallback the feed\n * would 503 even though the database is reachable.\n */\n private resolveContextDb(): unknown {\n return (\n this.context.db ?? this.collections.values().next().value?.db ?? undefined\n );\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 // Change-feed route (#1758): auth-guarded, tenant-scoped cursor read\n // over the _smrt_changes log. Logic lives in ./changes-route.ts.\n if (url.pathname === `${this.config.basePath}/_changes`) {\n const response = await handleChangesRoute(req, {\n authMiddleware: this.config.authMiddleware,\n db: this.resolveContextDb(),\n });\n return this.addCorsHeaders(response, req);\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 // Reserved batch write contract route (#1759): POST {basePath}/sync/apply.\n if (\n pathParts.length === SYNC_APPLY_ROUTE_SEGMENTS.length &&\n SYNC_APPLY_ROUTE_SEGMENTS.every((segment, i) => pathParts[i] === segment)\n ) {\n if (req.method !== 'POST') {\n return this.createErrorResponse(405, 'Method not allowed');\n }\n return await this.handleSyncApply(req);\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, matching the URL segment\n // against the canonical manifest `collection` value (#1794).\n const classInfo = this.findClassByCollectionSegment(objectType);\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 = await 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, objectName);\n }\n\n // Route to appropriate CRUD operation\n switch (req.method) {\n case 'GET':\n return objectId\n ? await this.handleGet(collection, objectId, req, objectName)\n : await this.handleList(\n collection,\n url.searchParams,\n req,\n objectName,\n );\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 * Fail-closed tenant read scope (#1782).\n *\n * A `@TenantScoped` model served over a public/anonymous read has no ambient\n * tenant context, so the tenancy interceptor (optional mode) passes the query\n * through UNFILTERED and returns every tenant's rows. When tenancy is enabled\n * but no tenant is active, this returns a `{ tenantId: null }` filter so reads\n * fail closed to NULL-tenant (global) rows only — mirroring the dispatch\n * resolver's \"enforced, no active tenant → global rows only\" convention\n * (`resolveDispatchTenantScope` is the core-level trust anchor tenancy fills\n * in; core cannot import tenancy directly). Returns undefined when a tenant IS\n * active (the interceptor filters by it) or tenancy is disabled (no isolation\n * to enforce).\n *\n * Tenant scoping is recognized across BOTH registration forms (#1782): the\n * `@smrt({ tenantScoped })` config / manifest-merged form via\n * `ObjectRegistry.isTenantScoped`, and the standalone `@TenantScoped()`\n * decorator via the tenancy-filled `isTenantScopedClassResolved` hook — so a\n * `@TenantScoped()`-only public model can't slip past the guard regardless of\n * manifest timing.\n */\n private resolveTenantReadScope(\n objectName?: string,\n ): { tenantId: null } | undefined {\n if (\n !objectName ||\n !(\n ObjectRegistry.isTenantScoped(objectName) ||\n !!ObjectRegistry.getConfig(objectName)?.tenantScoped ||\n isTenantScopedClassResolved(objectName)\n )\n ) {\n return undefined;\n }\n const scope = resolveDispatchTenantScope();\n return scope.enforced && scope.tenantId === null\n ? { tenantId: null }\n : undefined;\n }\n\n /**\n * Merge the fail-closed tenant read scope (#1782) into a query's WHERE clause.\n * When the scope is active (global-only), any client-supplied tenant filter is\n * dropped first so a `?tenantId=...` / `?tenant_id[ne]=...` query param can\n * never widen the scope, then NULL-tenant is forced. Returns the original\n * `where` untouched when the scope is inactive.\n */\n private applyTenantReadScope(\n objectName: string | undefined,\n where: Record<string, string | string[]> | undefined,\n ): Record<string, unknown> | undefined {\n const scope = this.resolveTenantReadScope(objectName);\n if (!scope) {\n return where;\n }\n const cleaned: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(where ?? {})) {\n // A key may carry an operator suffix, e.g. \"tenant_id !=\" — match on the\n // field portion so no operator variant slips a client tenant filter past.\n const field = key.split(/\\s+/)[0];\n if (field === 'tenantId' || field === 'tenant_id') {\n continue;\n }\n cleaned[key] = value;\n }\n cleaned.tenantId = null;\n return cleaned;\n }\n\n /**\n * Handle GET /objects/:id\n */\n private async handleGet(\n collection: SmrtCollection<SmrtObject>,\n id: string,\n req: Request,\n objectName?: string,\n ): Promise<Response> {\n // ETag v2 (#1765): compute the table-version ETag first. A CONCRETE\n // If-None-Match short-circuits into a 304 BEFORE the row is fetched — a\n // delete advances the table version (tombstone), so a stale cached\n // representation of a since-deleted row can never concretely match. A\n // wildcard `*` is deferred until AFTER the fetch confirms the row exists,\n // so a missing row still returns 404 (not a false 304).\n const cacheControl = this.resolveReadCachePolicy(objectName);\n const etag = await this.computeReadEtag(collection, req, objectName);\n const ifNoneMatch = req.headers.get('if-none-match');\n const notModified = (): Response =>\n new Response(null, {\n status: 304,\n headers: { 'Cache-Control': cacheControl, ETag: etag },\n });\n if (ifNoneMatchHasConcreteMatch(ifNoneMatch, etag)) {\n return notModified();\n }\n\n // #1782: scope a public/anonymous read of a tenant-scoped model to global\n // (NULL-tenant) rows when no tenant context is active.\n const scope = this.resolveTenantReadScope(objectName);\n const object = scope\n ? await collection.get({ id, ...scope })\n : await collection.get(id);\n if (!object) {\n return this.createErrorResponse(404, 'Object not found');\n }\n // Row exists → a wildcard `*` may now be honored as a 304.\n if (ifNoneMatchSatisfied(ifNoneMatch, etag)) {\n return notModified();\n }\n return new Response(JSON.stringify(this.toPublicData(object)), {\n status: 200,\n headers: {\n 'Cache-Control': cacheControl,\n 'Content-Type': 'application/json',\n ETag: etag,\n },\n });\n }\n\n /**\n * Handle GET /objects (list with query params)\n */\n private async handleList(\n collection: SmrtCollection<SmrtObject>,\n params: URLSearchParams,\n req: Request,\n objectName?: string,\n ): Promise<Response> {\n // ETag v2 (#1765): resolve the policy + table-version ETag first, then\n // hand the list query to versionConditionalResponse as a thunk. On a\n // matching If-None-Match it returns a 304 and the thunk — the collection\n // query — never runs, so an unchanged table revalidates with no table scan.\n const cacheControl = this.resolveReadCachePolicy(objectName);\n const etag = await this.computeReadEtag(collection, req, objectName);\n\n return versionConditionalResponse(req, etag, cacheControl, async () => {\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 // #1782: fail closed to global (NULL-tenant) rows for a public/anonymous\n // read of a tenant-scoped model with no active tenant context.\n const scopedWhere = this.applyTenantReadScope(objectName, where);\n\n const objects = await collection.list({\n where:\n scopedWhere && Object.keys(scopedWhere).length > 0\n ? scopedWhere\n : undefined,\n limit,\n offset,\n orderBy,\n });\n\n return 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 objectName?: string,\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 // #1782: a count leaks cross-tenant cardinality just as a list leaks rows,\n // so apply the same fail-closed global scope for anonymous tenant reads.\n const scopedWhere = this.applyTenantReadScope(objectName, where);\n\n const count = await collection.count({\n where:\n scopedWhere && Object.keys(scopedWhere).length > 0\n ? scopedWhere\n : 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 * Handle POST /sync/apply — the idempotent batch write contract (#1759).\n * All processing lives in `sync/apply.ts`; this method only adapts the\n * generator's existing collection resolution, auth, action gating, and\n * writable policy into a {@link SyncApplyTarget} per item.\n */\n private async handleSyncApply(req: Request): Promise<Response> {\n // Buffer the body once. Request bodies are single-read streams, and the\n // per-item auth middleware below must receive a readable body exactly as\n // it does on CRUD routes (where auth runs before parsing) — so each\n // middleware invocation gets a fresh Request rebuilt from this buffer.\n let rawBody: string;\n let body: unknown;\n try {\n rawBody = await req.text();\n body = JSON.parse(rawBody);\n } catch {\n return this.createErrorResponse(400, 'Invalid JSON body');\n }\n\n const outcome = await processSyncApplyBatch(body, {\n resolveTarget: (objectSegment) =>\n this.resolveSyncApplyTarget(objectSegment, req, rawBody),\n });\n return this.createJsonResponse(outcome.body, outcome.status);\n }\n\n /**\n * Resolve a sync item's `object` segment exactly like `handleObjectRoute`\n * resolves a CRUD URL segment (registered collections first, then registry\n * auto-discovery), and wrap the generator's per-object machinery.\n */\n private async resolveSyncApplyTarget(\n objectSegment: string,\n req: Request,\n rawBody: string,\n ): Promise<SyncApplyTarget | null> {\n let collection: SmrtCollection<SmrtObject> | null = null;\n let objectName: string | null = null;\n\n const registered = this.collections.get(objectSegment);\n if (registered) {\n collection = registered;\n objectName = this.getCollectionObjectName(registered) || objectSegment;\n } else {\n // Same canonical-`collection` match as the CRUD path so a client and the\n // batch contract resolve identical segments (#1794).\n const info = this.findClassByCollectionSegment(objectSegment);\n if (info) {\n collection = await this.getCollection(info);\n objectName = info.name;\n }\n }\n\n if (!collection || !objectName) {\n return null;\n }\n\n const resolvedName = objectName;\n const resolvedCollection = collection;\n return {\n objectName: resolvedName,\n collection: resolvedCollection,\n isOpAllowed: (op: SyncApplyOp) =>\n this.isApiActionEnabled(resolvedName, op),\n authorize: async (op: SyncApplyOp) => {\n if (this.config.authMiddleware) {\n const verb =\n op === 'create' ? 'post' : op === 'update' ? 'put' : 'delete';\n const authCheck = this.config.authMiddleware(resolvedName, verb);\n // Fresh Request per invocation: the batch handler already consumed\n // the original body, and middleware may read it (body-based auth,\n // request signing) exactly as it can on CRUD routes.\n const authResult = await authCheck(\n new Request(req.url, {\n method: req.method,\n headers: req.headers,\n body: rawBody,\n }),\n );\n if (authResult instanceof Response) {\n return authResult.status === 401 ? 'auth_required' : 'forbidden';\n }\n return 'ok';\n }\n // Fail-closed (#1540): every sync op is mutating, so only\n // `@smrt({ api: { public: true } })` objects apply without auth.\n return this.isRoutePublic(resolvedName, 'POST')\n ? 'ok'\n : 'auth_required';\n },\n prepare: (payload) => this.applyWritablePolicy(resolvedName, payload),\n };\n }\n\n /**\n * Get or create collection instance\n */\n private async getCollection(\n classInfo: RegisteredClass,\n ): Promise<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 // Await initialize() so the auto-discovered collection is actually usable\n // (its DB is wired). Without this, list()/get() throw \"Database accessed\n // before initialization\" — a latent bug that was masked while the\n // generated client's plural URLs never matched the singular-only\n // auto-discovery segment (#1794). The explicit-registerCollection() path\n // receives an already-initialized collection from the caller, so this\n // only affects the auto-discovery factory.\n await collection.initialize();\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 * Resolve the Cache-Control policy for a generated read and fire the\n * one-time policy warnings (#1757): private + revalidatable by default;\n * shared `s-maxage` only for public models that opt in; always private for\n * tenant-scoped models, whose bodies vary with tenant context that URL-keyed\n * shared caches cannot see.\n */\n private resolveReadCachePolicy(objectName: string | undefined): string {\n const config = objectName\n ? ObjectRegistry.getConfig(objectName)\n : undefined;\n const apiConfig = config?.api;\n // Canonical accessor covers both `@smrt({ tenantScoped })` and the\n // manifest-merged `@TenantScoped()` decorator form; the raw config flag is\n // a belt-and-braces fallback for registrations not yet normalized.\n const tenantScoped = objectName\n ? ObjectRegistry.isTenantScoped(objectName) || !!config?.tenantScoped\n : false;\n if (objectName) {\n warnIfSharedCacheNeutralized(objectName, apiConfig, tenantScoped);\n warnIfTenantScopedPublicRead(objectName, apiConfig, tenantScoped);\n }\n return resolveReadCacheControl(apiConfig, { tenantScoped });\n }\n\n /**\n * Compute the ETag v2 (#1765) for a generated read: the table's change-feed\n * version ({@link getTableVersion}) keyed by the request representation, so a\n * matching `If-None-Match` can short-circuit into a 304 BEFORE the collection\n * query runs. The representation folds in the active tenant for tenant-scoped\n * models so one tenant's cached ETag never satisfies another's read.\n */\n private async computeReadEtag(\n collection: SmrtCollection<SmrtObject>,\n req: Request,\n objectName: string | undefined,\n ): Promise<string> {\n const version = await getTableVersion(collection.db, collection.tableName);\n const representation = canonicalReadRepresentation(\n req,\n this.readTenantDiscriminator(objectName),\n );\n return computeTableVersionEtag(version, representation);\n }\n\n /**\n * A per-tenant ETag discriminator for tenant-scoped models: the active tenant\n * id (or `global`) so tenant A's cached ETag never satisfies tenant B's read\n * of the same table and params. `undefined` for non-tenant-scoped models,\n * whose bodies do not vary by tenant.\n */\n private readTenantDiscriminator(objectName?: string): string | undefined {\n if (!objectName) return undefined;\n const tenantScoped =\n ObjectRegistry.isTenantScoped(objectName) ||\n !!ObjectRegistry.getConfig(objectName)?.tenantScoped ||\n isTenantScopedClassResolved(objectName);\n if (!tenantScoped) return undefined;\n // Shared with the generated SvelteKit route so both transports key the ETag\n // on the active tenant identically (cross-tenant false-304 guard).\n return resolveTenantEtagDiscriminator();\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 * Resolve a URL/collection segment to a registered class via auto-discovery.\n *\n * The segment IS the canonical `collection` value from the manifest\n * (`RegisteredClass.collection`, e.g. `products` for `Product`) — the same\n * value the generated client, the SvelteKit route generator, and the registry\n * all derive. Match it VERBATIM; do not re-pluralize. The previous\n * implementation pluralized both the URL segment and the class name and\n * compared them, turning an already-plural client URL (`/products`) into\n * `productses`, so the generated client 404'd against auto-discovered routes\n * (#1794). Shared by the CRUD path and the `sync/apply` batch path so they can\n * never drift.\n *\n * @param segment - The URL/collection path segment (e.g. `products`).\n * @returns The matching registered class, or null if none matches.\n */\n private findClassByCollectionSegment(\n segment: string,\n ): RegisteredClass | null {\n for (const [key, info] of ObjectRegistry.getAllClasses()) {\n // Primary match: the canonical manifest `collection` segment.\n if (info.collection === segment) {\n return info;\n }\n // Back-compat fallback for classes registered without a `collection`\n // (inline/test stubs): derive the plural segment from the simple name and\n // compare to the raw URL segment. Issue #951: use the simple name\n // (info.name), not the qualified map key.\n if (\n !info.collection &&\n this.pluralize((info.name || key).toLowerCase()) === segment\n ) {\n return info;\n }\n }\n return null;\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"],"mappings":"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"}
1
+ {"version":3,"file":"rest.js","names":[],"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 { getTableVersion } from '../change-feed';\nimport type { SmrtCollection } from '../collection';\nimport {\n isTenantScopedClassResolved,\n resolveDispatchTenantScope,\n} from '../dispatch';\nimport type { PublicJsonOptions, SmrtObject } from '../object';\nimport { ObjectRegistry } from '../registry';\nimport type { RegisteredClass, SmrtObjectConstructor } from '../registry/types';\nimport {\n processSyncApplyBatch,\n SYNC_APPLY_ROUTE_SEGMENTS,\n type SyncApplyOp,\n type SyncApplyTarget,\n} from '../sync/apply';\nimport { handleChangesRoute } from './changes-route';\nimport {\n canonicalReadRepresentation,\n computeTableVersionEtag,\n conditionalJsonResponse,\n ifNoneMatchHasConcreteMatch,\n ifNoneMatchSatisfied,\n resolveReadCacheControl,\n resolveTenantEtagDiscriminator,\n versionConditionalResponse,\n warnIfSharedCacheNeutralized,\n warnIfTenantScopedPublicRead,\n} from './conditional-get';\nimport { handleEventsRoute } from './events-route';\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 /** Resolved permission slugs held by the caller. */\n permissions?: Iterable<string>;\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 * Resolve the database for generator-level routes that are not object-scoped\n * (currently the `_changes` feed, #1758).\n *\n * Prefers the explicit `APIContext.db`, but falls back to the first\n * registered collection's live handle so the feed works under the documented\n * `registerCollection()` wiring, where callers configure collections with a\n * database but never populate `APIContext.db`. Without the fallback the feed\n * would 503 even though the database is reachable.\n */\n private resolveContextDb(): unknown {\n return (\n this.context.db ?? this.collections.values().next().value?.db ?? undefined\n );\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\n // Client disconnect handling — essential for the long-lived `_events` SSE\n // stream (#1763): a never-ending stream is only torn down when its reader\n // is cancelled, and nothing cancels it unless we detect the socket close.\n // On `res`/socket 'close' before the response finished, cancel the reader\n // so the source stream's `cancel()` fires (unsubscribe + clear heartbeat).\n let clientGone = false;\n const onClose = () => {\n if (res.writableFinished) return; // normal completion, not a disconnect\n clientGone = true;\n void reader.cancel().catch(() => {});\n };\n res.on('close', onClose);\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done || clientGone) break;\n if (!res.write(value)) {\n // Respect back-pressure so a slow/absent consumer can't unbounded-\n // buffer in the Node socket. Resolve on 'drain' OR on 'close'/\n // 'error' — a disconnect while back-pressured never fires 'drain',\n // so waiting only for it would hang this frame forever (leaking it\n // and pinning the reader for the long-lived `_events` stream). Race\n // the three and remove the losers so one-shot listeners don't pile\n // up across many drains.\n await new Promise<void>((resolve) => {\n const settle = () => {\n res.off('drain', settle);\n res.off('close', settle);\n res.off('error', settle);\n resolve();\n };\n res.once('drain', settle);\n res.once('close', settle);\n res.once('error', settle);\n });\n if (clientGone) break;\n }\n }\n } catch {\n // Reader cancelled (client gone) or write failed — stop streaming.\n } finally {\n res.off('close', onClose);\n }\n }\n\n if (!res.writableEnded) {\n res.end();\n }\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 // Change-feed route (#1758): auth-guarded, tenant-scoped cursor read\n // over the _smrt_changes log. Logic lives in ./changes-route.ts.\n if (url.pathname === `${this.config.basePath}/_changes`) {\n const response = await handleChangesRoute(req, {\n authMiddleware: this.config.authMiddleware,\n db: this.resolveContextDb(),\n });\n return this.addCorsHeaders(response, req);\n }\n\n // Live change-signal SSE route (#1763): auth-guarded, tenant-scoped\n // Server-Sent-Events stream of coarse signals (no row payloads). Logic\n // lives in ./events-route.ts. Deliberately NOT wrapped in addCorsHeaders —\n // this slice is same-origin only (EventSource cannot set request headers\n // and credentialed cross-origin SSE needs Allow-Credentials the CORS\n // helper does not emit); cross-origin SSE is a follow-up.\n if (url.pathname === `${this.config.basePath}/_events`) {\n return handleEventsRoute(req, {\n authMiddleware: this.config.authMiddleware,\n db: this.resolveContextDb(),\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 // Reserved batch write contract route (#1759): POST {basePath}/sync/apply.\n if (\n pathParts.length === SYNC_APPLY_ROUTE_SEGMENTS.length &&\n SYNC_APPLY_ROUTE_SEGMENTS.every((segment, i) => pathParts[i] === segment)\n ) {\n if (req.method !== 'POST') {\n return this.createErrorResponse(405, 'Method not allowed');\n }\n return await this.handleSyncApply(req);\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, matching the URL segment\n // against the canonical manifest `collection` value (#1794).\n const classInfo = this.findClassByCollectionSegment(objectType);\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 = await 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, objectName);\n }\n\n // Route to appropriate CRUD operation\n switch (req.method) {\n case 'GET':\n return objectId\n ? await this.handleGet(collection, objectId, req, objectName)\n : await this.handleList(\n collection,\n url.searchParams,\n req,\n objectName,\n );\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 * Fail-closed tenant read scope (#1782).\n *\n * A `@TenantScoped` model served over a public/anonymous read has no ambient\n * tenant context, so the tenancy interceptor (optional mode) passes the query\n * through UNFILTERED and returns every tenant's rows. When tenancy is enabled\n * but no tenant is active, this returns a `{ tenantId: null }` filter so reads\n * fail closed to NULL-tenant (global) rows only — mirroring the dispatch\n * resolver's \"enforced, no active tenant → global rows only\" convention\n * (`resolveDispatchTenantScope` is the core-level trust anchor tenancy fills\n * in; core cannot import tenancy directly). Returns undefined when a tenant IS\n * active (the interceptor filters by it) or tenancy is disabled (no isolation\n * to enforce).\n *\n * Tenant scoping is recognized across BOTH registration forms (#1782): the\n * `@smrt({ tenantScoped })` config / manifest-merged form via\n * `ObjectRegistry.isTenantScoped`, and the standalone `@TenantScoped()`\n * decorator via the tenancy-filled `isTenantScopedClassResolved` hook — so a\n * `@TenantScoped()`-only public model can't slip past the guard regardless of\n * manifest timing.\n */\n private resolveTenantReadScope(\n objectName?: string,\n ): { tenantId: null } | undefined {\n if (\n !objectName ||\n !(\n ObjectRegistry.isTenantScoped(objectName) ||\n !!ObjectRegistry.getConfig(objectName)?.tenantScoped ||\n isTenantScopedClassResolved(objectName)\n )\n ) {\n return undefined;\n }\n const scope = resolveDispatchTenantScope();\n return scope.enforced && scope.tenantId === null\n ? { tenantId: null }\n : undefined;\n }\n\n /**\n * Merge the fail-closed tenant read scope (#1782) into a query's WHERE clause.\n * When the scope is active (global-only), any client-supplied tenant filter is\n * dropped first so a `?tenantId=...` / `?tenant_id[ne]=...` query param can\n * never widen the scope, then NULL-tenant is forced. Returns the original\n * `where` untouched when the scope is inactive.\n */\n private applyTenantReadScope(\n objectName: string | undefined,\n where: Record<string, string | string[]> | undefined,\n ): Record<string, unknown> | undefined {\n const scope = this.resolveTenantReadScope(objectName);\n if (!scope) {\n return where;\n }\n const cleaned: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(where ?? {})) {\n // A key may carry an operator suffix, e.g. \"tenant_id !=\" — match on the\n // field portion so no operator variant slips a client tenant filter past.\n const field = key.split(/\\s+/)[0];\n if (field === 'tenantId' || field === 'tenant_id') {\n continue;\n }\n cleaned[key] = value;\n }\n cleaned.tenantId = null;\n return cleaned;\n }\n\n /**\n * Handle GET /objects/:id\n */\n private async handleGet(\n collection: SmrtCollection<SmrtObject>,\n id: string,\n req: Request,\n objectName?: string,\n ): Promise<Response> {\n const cacheControl = this.resolveReadCachePolicy(objectName);\n\n if (this.hasReadPermissionFields(objectName)) {\n // Field-level read permissions make the response body vary by caller\n // permissions, so the validator must cover the redacted body returned to\n // this caller rather than only the table version.\n const scope = this.resolveTenantReadScope(objectName);\n const object = scope\n ? await collection.get({ id, ...scope })\n : await collection.get(id);\n if (!object) {\n return this.createErrorResponse(404, 'Object not found');\n }\n return conditionalJsonResponse(\n req,\n this.toPublicData(object),\n cacheControl,\n );\n }\n\n // ETag v2 (#1765): compute the table-version ETag first. A CONCRETE\n // If-None-Match short-circuits into a 304 BEFORE the row is fetched — a\n // delete advances the table version (tombstone), so a stale cached\n // representation of a since-deleted row can never concretely match. A\n // wildcard `*` is deferred until AFTER the fetch confirms the row exists,\n // so a missing row still returns 404 (not a false 304).\n const etag = await this.computeReadEtag(collection, req, objectName);\n const ifNoneMatch = req.headers.get('if-none-match');\n const notModified = (): Response =>\n new Response(null, {\n status: 304,\n headers: { 'Cache-Control': cacheControl, ETag: etag },\n });\n if (ifNoneMatchHasConcreteMatch(ifNoneMatch, etag)) {\n return notModified();\n }\n\n // #1782: scope a public/anonymous read of a tenant-scoped model to global\n // (NULL-tenant) rows when no tenant context is active.\n const scope = this.resolveTenantReadScope(objectName);\n const object = scope\n ? await collection.get({ id, ...scope })\n : await collection.get(id);\n if (!object) {\n return this.createErrorResponse(404, 'Object not found');\n }\n // Row exists → a wildcard `*` may now be honored as a 304.\n if (ifNoneMatchSatisfied(ifNoneMatch, etag)) {\n return notModified();\n }\n return new Response(JSON.stringify(this.toPublicData(object)), {\n status: 200,\n headers: {\n 'Cache-Control': cacheControl,\n 'Content-Type': 'application/json',\n ETag: etag,\n },\n });\n }\n\n /**\n * Handle GET /objects (list with query params)\n */\n private async handleList(\n collection: SmrtCollection<SmrtObject>,\n params: URLSearchParams,\n req: Request,\n objectName?: string,\n ): Promise<Response> {\n const cacheControl = this.resolveReadCachePolicy(objectName);\n\n const buildPayload = async () => {\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 // #1782: fail closed to global (NULL-tenant) rows for a public/anonymous\n // read of a tenant-scoped model with no active tenant context.\n const scopedWhere = this.applyTenantReadScope(objectName, where);\n\n const objects = await collection.list({\n where:\n scopedWhere && Object.keys(scopedWhere).length > 0\n ? scopedWhere\n : undefined,\n limit,\n offset,\n orderBy,\n });\n\n return objects.map((object: SmrtObject) => this.toPublicData(object));\n };\n\n if (this.hasReadPermissionFields(objectName)) {\n // Field-level read permissions make the response body vary by caller\n // permissions, so use the v1 body-hash path over the redacted payload.\n return conditionalJsonResponse(req, await buildPayload(), cacheControl);\n }\n\n // ETag v2 (#1765): resolve the policy + table-version ETag first, then\n // hand the list query to versionConditionalResponse as a thunk. On a\n // matching If-None-Match it returns a 304 and the thunk — the collection\n // query — never runs, so an unchanged table revalidates with no table scan.\n const etag = await this.computeReadEtag(collection, req, objectName);\n return versionConditionalResponse(req, etag, cacheControl, buildPayload);\n }\n\n /**\n * Handle GET /objects/count\n */\n private async handleCount(\n collection: SmrtCollection<SmrtObject>,\n params: URLSearchParams,\n objectName?: string,\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 // #1782: a count leaks cross-tenant cardinality just as a list leaks rows,\n // so apply the same fail-closed global scope for anonymous tenant reads.\n const scopedWhere = this.applyTenantReadScope(objectName, where);\n\n const count = await collection.count({\n where:\n scopedWhere && Object.keys(scopedWhere).length > 0\n ? scopedWhere\n : 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 * Handle POST /sync/apply — the idempotent batch write contract (#1759).\n * All processing lives in `sync/apply.ts`; this method only adapts the\n * generator's existing collection resolution, auth, action gating, and\n * writable policy into a {@link SyncApplyTarget} per item.\n */\n private async handleSyncApply(req: Request): Promise<Response> {\n // Buffer the body once. Request bodies are single-read streams, and the\n // per-item auth middleware below must receive a readable body exactly as\n // it does on CRUD routes (where auth runs before parsing) — so each\n // middleware invocation gets a fresh Request rebuilt from this buffer.\n let rawBody: string;\n let body: unknown;\n try {\n rawBody = await req.text();\n body = JSON.parse(rawBody);\n } catch {\n return this.createErrorResponse(400, 'Invalid JSON body');\n }\n\n const outcome = await processSyncApplyBatch(body, {\n resolveTarget: (objectSegment) =>\n this.resolveSyncApplyTarget(objectSegment, req, rawBody),\n });\n return this.createJsonResponse(outcome.body, outcome.status);\n }\n\n /**\n * Resolve a sync item's `object` segment exactly like `handleObjectRoute`\n * resolves a CRUD URL segment (registered collections first, then registry\n * auto-discovery), and wrap the generator's per-object machinery.\n */\n private async resolveSyncApplyTarget(\n objectSegment: string,\n req: Request,\n rawBody: string,\n ): Promise<SyncApplyTarget | null> {\n let collection: SmrtCollection<SmrtObject> | null = null;\n let objectName: string | null = null;\n\n const registered = this.collections.get(objectSegment);\n if (registered) {\n collection = registered;\n objectName = this.getCollectionObjectName(registered) || objectSegment;\n } else {\n // Same canonical-`collection` match as the CRUD path so a client and the\n // batch contract resolve identical segments (#1794).\n const info = this.findClassByCollectionSegment(objectSegment);\n if (info) {\n collection = await this.getCollection(info);\n objectName = info.name;\n }\n }\n\n if (!collection || !objectName) {\n return null;\n }\n\n const resolvedName = objectName;\n const resolvedCollection = collection;\n return {\n objectName: resolvedName,\n collection: resolvedCollection,\n isOpAllowed: (op: SyncApplyOp) =>\n this.isApiActionEnabled(resolvedName, op),\n authorize: async (op: SyncApplyOp) => {\n if (this.config.authMiddleware) {\n const verb =\n op === 'create' ? 'post' : op === 'update' ? 'put' : 'delete';\n const authCheck = this.config.authMiddleware(resolvedName, verb);\n // Fresh Request per invocation: the batch handler already consumed\n // the original body, and middleware may read it (body-based auth,\n // request signing) exactly as it can on CRUD routes.\n const authResult = await authCheck(\n new Request(req.url, {\n method: req.method,\n headers: req.headers,\n body: rawBody,\n }),\n );\n if (authResult instanceof Response) {\n return authResult.status === 401 ? 'auth_required' : 'forbidden';\n }\n return 'ok';\n }\n // Fail-closed (#1540): every sync op is mutating, so only\n // `@smrt({ api: { public: true } })` objects apply without auth.\n return this.isRoutePublic(resolvedName, 'POST')\n ? 'ok'\n : 'auth_required';\n },\n prepare: (payload) => this.applyWritablePolicy(resolvedName, payload),\n };\n }\n\n /**\n * Get or create collection instance\n */\n private async getCollection(\n classInfo: RegisteredClass,\n ): Promise<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 // Await initialize() so the auto-discovered collection is actually usable\n // (its DB is wired). Without this, list()/get() throw \"Database accessed\n // before initialization\" — a latent bug that was masked while the\n // generated client's plural URLs never matched the singular-only\n // auto-discovery segment (#1794). The explicit-registerCollection() path\n // receives an already-initialized collection from the caller, so this\n // only affects the auto-discovery factory.\n await collection.initialize();\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 {\n toPublicJSON?: (options?: PublicJsonOptions) => unknown;\n } | null;\n return typeof serializable?.toPublicJSON === 'function'\n ? serializable.toPublicJSON(this.getPublicJsonOptions())\n : object;\n }\n\n private getPublicJsonOptions(): PublicJsonOptions {\n return { permissions: this.context.permissions };\n }\n\n /**\n * Resolve the Cache-Control policy for a generated read and fire the\n * one-time policy warnings (#1757): private + revalidatable by default;\n * shared `s-maxage` only for public models that opt in; always private for\n * tenant-scoped models, whose bodies vary with tenant context that URL-keyed\n * shared caches cannot see. Field read-permission models are also private\n * because the body varies by caller permissions.\n */\n private resolveReadCachePolicy(objectName: string | undefined): string {\n const config = objectName\n ? ObjectRegistry.getConfig(objectName)\n : undefined;\n const apiConfig = config?.api;\n // Canonical accessor covers both `@smrt({ tenantScoped })` and the\n // manifest-merged `@TenantScoped()` decorator form; the raw config flag is\n // a belt-and-braces fallback for registrations not yet normalized.\n const tenantScoped = objectName\n ? ObjectRegistry.isTenantScoped(objectName) || !!config?.tenantScoped\n : false;\n const permissionScoped = this.hasReadPermissionFields(objectName);\n if (objectName) {\n warnIfSharedCacheNeutralized(\n objectName,\n apiConfig,\n tenantScoped,\n permissionScoped,\n );\n warnIfTenantScopedPublicRead(objectName, apiConfig, tenantScoped);\n }\n return resolveReadCacheControl(apiConfig, {\n tenantScoped,\n permissionScoped,\n });\n }\n\n private hasReadPermissionFields(objectName: string | undefined): boolean {\n if (!objectName) return false;\n for (const fields of this.getFieldMapsForPublicPolicy(objectName)) {\n for (const [, def] of fields) {\n if (\n typeof def?.readPermission === 'string' ||\n typeof def?._meta?.readPermission === 'string'\n ) {\n return true;\n }\n }\n }\n return false;\n }\n\n private getFieldMapsForPublicPolicy(\n objectName: string,\n ): Array<ReturnType<typeof ObjectRegistry.getFields>> {\n const registered = ObjectRegistry.getClass(objectName);\n const className =\n registered?.qualifiedName ?? registered?.name ?? objectName;\n const fieldMaps: Array<ReturnType<typeof ObjectRegistry.getFields>> = [\n registered?.inheritedFields || ObjectRegistry.getFields(className),\n ];\n const stiBase = ObjectRegistry.getSTIBase(className);\n if (stiBase) {\n fieldMaps.push(\n ObjectRegistry.getClass(stiBase)?.inheritedFields ||\n ObjectRegistry.getFields(stiBase),\n );\n for (const descendant of ObjectRegistry.getDescendants(stiBase)) {\n fieldMaps.push(\n ObjectRegistry.getClass(descendant)?.inheritedFields ||\n ObjectRegistry.getFields(descendant),\n );\n }\n }\n return fieldMaps;\n }\n\n /**\n * Compute the ETag v2 (#1765) for a generated read: the table's change-feed\n * version ({@link getTableVersion}) keyed by the request representation, so a\n * matching `If-None-Match` can short-circuit into a 304 BEFORE the collection\n * query runs. The representation folds in the active tenant for tenant-scoped\n * models so one tenant's cached ETag never satisfies another's read.\n */\n private async computeReadEtag(\n collection: SmrtCollection<SmrtObject>,\n req: Request,\n objectName: string | undefined,\n ): Promise<string> {\n const version = await getTableVersion(collection.db, collection.tableName);\n const representation = canonicalReadRepresentation(\n req,\n this.readTenantDiscriminator(objectName),\n );\n return computeTableVersionEtag(version, representation);\n }\n\n /**\n * A per-tenant ETag discriminator for tenant-scoped models: the active tenant\n * id (or `global`) so tenant A's cached ETag never satisfies tenant B's read\n * of the same table and params. `undefined` for non-tenant-scoped models,\n * whose bodies do not vary by tenant.\n */\n private readTenantDiscriminator(objectName?: string): string | undefined {\n if (!objectName) return undefined;\n const tenantScoped =\n ObjectRegistry.isTenantScoped(objectName) ||\n !!ObjectRegistry.getConfig(objectName)?.tenantScoped ||\n isTenantScopedClassResolved(objectName);\n if (!tenantScoped) return undefined;\n // Shared with the generated SvelteKit route so both transports key the ETag\n // on the active tenant identically (cross-tenant false-304 guard).\n return resolveTenantEtagDiscriminator();\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 * Resolve a URL/collection segment to a registered class via auto-discovery.\n *\n * The segment IS the canonical `collection` value from the manifest\n * (`RegisteredClass.collection`, e.g. `products` for `Product`) — the same\n * value the generated client, the SvelteKit route generator, and the registry\n * all derive. Match it VERBATIM; do not re-pluralize. The previous\n * implementation pluralized both the URL segment and the class name and\n * compared them, turning an already-plural client URL (`/products`) into\n * `productses`, so the generated client 404'd against auto-discovered routes\n * (#1794). Shared by the CRUD path and the `sync/apply` batch path so they can\n * never drift.\n *\n * @param segment - The URL/collection path segment (e.g. `products`).\n * @returns The matching registered class, or null if none matches.\n */\n private findClassByCollectionSegment(\n segment: string,\n ): RegisteredClass | null {\n for (const [key, info] of ObjectRegistry.getAllClasses()) {\n // Primary match: the canonical manifest `collection` segment.\n if (info.collection === segment) {\n return info;\n }\n // Back-compat fallback for classes registered without a `collection`\n // (inline/test stubs): derive the plural segment from the simple name and\n // compare to the raw URL segment. Issue #951: use the simple name\n // (info.name), not the qualified map key.\n if (\n !info.collection &&\n this.pluralize((info.name || key).toLowerCase()) === segment\n ) {\n return info;\n }\n }\n return null;\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"],"mappings":"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"}
@@ -1,8 +1,9 @@
1
1
  import { runWithTenantGate, setTenantEntryPointRunner } from "./generators/tenant-gate.js";
2
2
  import { CLIGenerator, getCLIHandler, setupCLI } from "./generators/cli.js";
3
3
  import { PRIVATE_READ_CACHE_CONTROL, canonicalReadRepresentation, computeBodyEtag, computeTableVersionEtag, conditionalJsonResponse, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, resolveReadCacheControl, resolveTenantEtagDiscriminator, versionConditionalResponse, warnIfSharedCacheNeutralized } from "./generators/conditional-get.js";
4
+ import { DEFAULT_EVENTS_HEARTBEAT_MS, buildChangeEventStream, signalVisibleToTenant } from "./generators/events-route.js";
4
5
  import { MCPGenerator } from "./generators/mcp.js";
5
6
  import { APIGenerator, createRestServer, startRestServer } from "./generators/rest.js";
6
7
  import { generateOpenAPISpec, setupSwaggerUI } from "./generators/swagger.js";
7
8
  import "./generators/index.js";
8
- export { APIGenerator, CLIGenerator, MCPGenerator, PRIVATE_READ_CACHE_CONTROL, canonicalReadRepresentation, computeBodyEtag, computeTableVersionEtag, conditionalJsonResponse, createRestServer, generateOpenAPISpec, getCLIHandler, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, resolveReadCacheControl, resolveTenantEtagDiscriminator, runWithTenantGate, setTenantEntryPointRunner, setupCLI, setupSwaggerUI, startRestServer, versionConditionalResponse, warnIfSharedCacheNeutralized };
9
+ export { APIGenerator, CLIGenerator, DEFAULT_EVENTS_HEARTBEAT_MS, MCPGenerator, PRIVATE_READ_CACHE_CONTROL, buildChangeEventStream, canonicalReadRepresentation, computeBodyEtag, computeTableVersionEtag, conditionalJsonResponse, createRestServer, generateOpenAPISpec, getCLIHandler, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, resolveReadCacheControl, resolveTenantEtagDiscriminator, runWithTenantGate, setTenantEntryPointRunner, setupCLI, setupSwaggerUI, signalVisibleToTenant, startRestServer, versionConditionalResponse, warnIfSharedCacheNeutralized };
package/dist/index.d.ts CHANGED
@@ -17,6 +17,7 @@
17
17
  */
18
18
  export * from './adapters/index';
19
19
  export { type AppendChangeInput, appendChange, bumpChangeFeed, CHANGE_FEED_INTERCEPTOR_NAME, CHANGE_FEED_TABLE, type ChangeFeedEntry, type ChangeFeedPage, type ChangeFeedRetention, type ChangeOperation, DEFAULT_CHANGES_LIMIT, ensureChangeFeedTable, type GetChangesOptions, getChangesSince, getTableVersion, getTenantScopedChangesSince, MAX_CHANGES_LIMIT, pruneChangeFeed, registerChangeFeedWriter, resetChangeFeedWarnings, unregisterChangeFeedWriter, } from './change-feed';
20
+ export { CHANGE_SIGNAL_CHANNEL, type ChangeSignal, type ChangeSignalListener, resetChangeSignals, stopChangeSignalListeners, subscribeToChangeSignals, } from './change-signals';
20
21
  export { applyOneToManyChildAccessors, childAccessorName, } from './child-accessors';
21
22
  export * from './class';
22
23
  export * from './collection';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { MetricsAdapter } from "./adapters/metrics.js";
4
4
  import { PubSubAdapter } from "./adapters/pubsub.js";
5
5
  import "./adapters/index.js";
6
6
  import { CACHE_INVALIDATION_CHANNEL, broadcastCacheInvalidation, ensureCacheInvalidationListener, getCacheGeneration, invalidateCollectionCache, resetCollectionCache, resolveDbCacheKey, stopCacheInvalidationListeners } from "./collection-cache.js";
7
+ import { CHANGE_SIGNAL_CHANNEL, resetChangeSignals, stopChangeSignalListeners, subscribeToChangeSignals } from "./change-signals.js";
7
8
  import { isTenantScopedClassResolved, resolveDispatchTenantId, resolveDispatchTenantScope, setDispatchTenantResolver, setTenantScopedClassResolver } from "./dispatch/tenant-resolver.js";
8
9
  import { GlobalInterceptors, createInterceptorContext } from "./interceptors.js";
9
10
  import { detectEngine, generateDDLForEngine } from "./schema/ddl/index.js";
@@ -44,6 +45,7 @@ import "./embeddings/index.js";
44
45
  import { runWithTenantGate, setTenantEntryPointRunner } from "./generators/tenant-gate.js";
45
46
  import { CLIGenerator, getCLIHandler, setupCLI } from "./generators/cli.js";
46
47
  import { PRIVATE_READ_CACHE_CONTROL, canonicalReadRepresentation, computeBodyEtag, computeTableVersionEtag, conditionalJsonResponse, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, resolveReadCacheControl, resolveTenantEtagDiscriminator, versionConditionalResponse, warnIfSharedCacheNeutralized } from "./generators/conditional-get.js";
48
+ import { DEFAULT_EVENTS_HEARTBEAT_MS, buildChangeEventStream, signalVisibleToTenant } from "./generators/events-route.js";
47
49
  import { MCPGenerator } from "./generators/mcp.js";
48
50
  import { MAX_SYNC_APPLY_BATCH_SIZE, SYNC_APPLY_ROUTE_SEGMENTS, SYNC_APPLY_UUID_PATTERN, applySyncWritablePolicy, isStaleWrite, parseSyncApplyBatch, payloadMatchesRow, processSyncApplyBatch, validateSyncApplyItem } from "./sync/apply.js";
49
51
  import { APIGenerator, createRestServer, startRestServer } from "./generators/rest.js";
@@ -66,4 +68,4 @@ import "./system/index.js";
66
68
  import { getTestDatabase } from "./testing/database.js";
67
69
  import "./tools/index.js";
68
70
  import { smrtPlugin } from "./vite-plugin/index.js";
69
- export { AIError, APIGenerator, AiUsageCollector, AiUsagePersistenceHandler, CACHE_INVALIDATION_CHANNEL, CHANGE_FEED_INTERCEPTOR_NAME, CHANGE_FEED_TABLE, CLIGenerator, ConfigurationError, ContentHasher, CosineSimilarity, DEFAULT_AI_COST_RATES, DEFAULT_CHANGES_LIMIT, DEFAULT_EMBEDDING_CONFIG, DatabaseError, Dispatch, DispatchBus, DispatchCollection, DispatchSubscription, DispatchSubscriptionCollection, EmbeddingProvider, EmbeddingStorage, ErrorUtils, FilesystemError, GlobalInterceptors, MAX_CHANGES_LIMIT, MAX_SYNC_APPLY_BATCH_SIZE, MCPGenerator, ManifestBuilder, ManifestGenerator, ManifestManager, MetricsAdapter, NetworkError, ObjectRegistry, PRIVATE_READ_CACHE_CONTROL, PubSubAdapter, RuntimeError, SMRT_COLLECTION_BASE_NAMES, SYNC_APPLY_ROUTE_SEGMENTS, SYNC_APPLY_UUID_PATTERN, SchemaComparer, SignalBus, SignalSanitizer, SmrtClass, SmrtCollection, SmrtError, SmrtHierarchical, SmrtJunction, SmrtMCPServer, SmrtObject, SmrtPolymorphicAssociation, TenantIsolationError, ValidationError, ValidationReport, ValidationUtils, appendChange, applyOneToManyChildAccessors, applyPendingDecoratorRegistrations, applySyncWritablePolicy, broadcastCacheInvalidation, buildDomainKnowledgeManifest, bumpChangeFeed, canonicalReadRepresentation, childAccessorName, clone, computeBodyEtag, computeTableVersionEtag, conditionalJsonResponse, config, convertTypeToJsonSchema, createDispatchBus, createInterceptorContext, createMCPServer, createQualifiedName, createRestServer, createSmrtClient, createSmrtServer, crossPackageRef, detectEngine, discoverManifestEntry, ensureCacheInvalidationListener, ensureChangeFeedTable, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, estimateAiUsageCost, executeToolCall, executeToolCalls, field, findManifestEntryByQualifiedName, foreignKey, formatToolResults, generateDDLForEngine, generateOpenAPISpec, generateSchemaDiff, generateToolFromMethod, generateToolManifest, getAdapterInfo, getCLIHandler, getCacheGeneration, getChangesSince, getClassConfigResolvers, getClassName, getConfigResolver, getManifest, getPackageFromQualifiedName, getSQLFromDiff, getTableVersion, getTenantScopedChangesSince, getTestDatabase, hasActionableChanges, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, invalidateCollectionCache, isDatabaseInterface, isFromPackage, isLazyConfigSentinel, isQualifiedName, isSmrtCollectionExtendsName, isStaleWrite, isTenantScopedClassResolved, isType, isValid, listConfigResolvers, loadExternalManifest, loadExternalManifestSync, loadLocalTestManifestSync, loadManifestFromPathSync, manifest, manyToMany, meta, oneToMany, parse, parseQualifiedName, parseSyncApplyBatch, payloadMatchesRow, processSyncApplyBatch, pruneChangeFeed, qualifiedNamesEqual, registerChangeFeedWriter, registerCompatibleFieldDecorator, registerConfigResolver, resetChangeFeedWarnings, resetCollectionCache, resetConfigResolvers, resetVerifiedTables, resolveDatabase, resolveDbCacheKey, resolveDispatchTenantId, resolveDispatchTenantScope, resolveLazyConfig, resolveReadCacheControl, resolveTenantEtagDiscriminator, runWithTenantGate, safeParse, safeStringify, setDispatchTenantResolver, setTenantEntryPointRunner, setTenantScopedClassResolver, setupCLI, setupSwaggerUI, shouldIncludeMethod, smrt, smrtPlugin, smrt as smrtRegistry, startRestServer, staticManifest, stopCacheInvalidationListeners, stringify, tableExists, unregisterChangeFeedWriter, unregisterConfigResolver, validateSyncApplyItem, validateToolCall, versionConditionalResponse, warnIfSharedCacheNeutralized };
71
+ export { AIError, APIGenerator, AiUsageCollector, AiUsagePersistenceHandler, CACHE_INVALIDATION_CHANNEL, CHANGE_FEED_INTERCEPTOR_NAME, CHANGE_FEED_TABLE, CHANGE_SIGNAL_CHANNEL, CLIGenerator, ConfigurationError, ContentHasher, CosineSimilarity, DEFAULT_AI_COST_RATES, DEFAULT_CHANGES_LIMIT, DEFAULT_EMBEDDING_CONFIG, DEFAULT_EVENTS_HEARTBEAT_MS, DatabaseError, Dispatch, DispatchBus, DispatchCollection, DispatchSubscription, DispatchSubscriptionCollection, EmbeddingProvider, EmbeddingStorage, ErrorUtils, FilesystemError, GlobalInterceptors, MAX_CHANGES_LIMIT, MAX_SYNC_APPLY_BATCH_SIZE, MCPGenerator, ManifestBuilder, ManifestGenerator, ManifestManager, MetricsAdapter, NetworkError, ObjectRegistry, PRIVATE_READ_CACHE_CONTROL, PubSubAdapter, RuntimeError, SMRT_COLLECTION_BASE_NAMES, SYNC_APPLY_ROUTE_SEGMENTS, SYNC_APPLY_UUID_PATTERN, SchemaComparer, SignalBus, SignalSanitizer, SmrtClass, SmrtCollection, SmrtError, SmrtHierarchical, SmrtJunction, SmrtMCPServer, SmrtObject, SmrtPolymorphicAssociation, TenantIsolationError, ValidationError, ValidationReport, ValidationUtils, appendChange, applyOneToManyChildAccessors, applyPendingDecoratorRegistrations, applySyncWritablePolicy, broadcastCacheInvalidation, buildChangeEventStream, buildDomainKnowledgeManifest, bumpChangeFeed, canonicalReadRepresentation, childAccessorName, clone, computeBodyEtag, computeTableVersionEtag, conditionalJsonResponse, config, convertTypeToJsonSchema, createDispatchBus, createInterceptorContext, createMCPServer, createQualifiedName, createRestServer, createSmrtClient, createSmrtServer, crossPackageRef, detectEngine, discoverManifestEntry, ensureCacheInvalidationListener, ensureChangeFeedTable, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, estimateAiUsageCost, executeToolCall, executeToolCalls, field, findManifestEntryByQualifiedName, foreignKey, formatToolResults, generateDDLForEngine, generateOpenAPISpec, generateSchemaDiff, generateToolFromMethod, generateToolManifest, getAdapterInfo, getCLIHandler, getCacheGeneration, getChangesSince, getClassConfigResolvers, getClassName, getConfigResolver, getManifest, getPackageFromQualifiedName, getSQLFromDiff, getTableVersion, getTenantScopedChangesSince, getTestDatabase, hasActionableChanges, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, invalidateCollectionCache, isDatabaseInterface, isFromPackage, isLazyConfigSentinel, isQualifiedName, isSmrtCollectionExtendsName, isStaleWrite, isTenantScopedClassResolved, isType, isValid, listConfigResolvers, loadExternalManifest, loadExternalManifestSync, loadLocalTestManifestSync, loadManifestFromPathSync, manifest, manyToMany, meta, oneToMany, parse, parseQualifiedName, parseSyncApplyBatch, payloadMatchesRow, processSyncApplyBatch, pruneChangeFeed, qualifiedNamesEqual, registerChangeFeedWriter, registerCompatibleFieldDecorator, registerConfigResolver, resetChangeFeedWarnings, resetChangeSignals, resetCollectionCache, resetConfigResolvers, resetVerifiedTables, resolveDatabase, resolveDbCacheKey, resolveDispatchTenantId, resolveDispatchTenantScope, resolveLazyConfig, resolveReadCacheControl, resolveTenantEtagDiscriminator, runWithTenantGate, safeParse, safeStringify, setDispatchTenantResolver, setTenantEntryPointRunner, setTenantScopedClassResolver, setupCLI, setupSwaggerUI, shouldIncludeMethod, signalVisibleToTenant, smrt, smrtPlugin, smrt as smrtRegistry, startRestServer, staticManifest, stopCacheInvalidationListeners, stopChangeSignalListeners, stringify, subscribeToChangeSignals, tableExists, unregisterChangeFeedWriter, unregisterConfigResolver, validateSyncApplyItem, validateToolCall, versionConditionalResponse, warnIfSharedCacheNeutralized };
@@ -1 +1 @@
1
- {"version":3,"file":"generator.js","names":[],"sources":["../../src/manifest/generator.ts"],"sourcesContent":["/**\n * Manifest generation orchestrator\n * Consolidates manifest generation logic for both build and test manifests\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, isAbsolute, relative, resolve } from 'node:path';\nimport fg from 'fast-glob';\nimport { ManifestGenerator } from '../scanner/manifest-generator.js';\nimport type { SmartObjectManifest } from '../scanner/types.js';\nimport { importWorkspaceModule } from '../utils/import-workspace-module.js';\nimport type { ScannerModule } from '../utils/scanner-module.js';\nimport {\n discoverSmrtPackages,\n resolveManifestPath,\n} from './discover-smrt-packages.js';\nimport { ManifestManager } from './manager.js';\n\n// Build-time manifest-generation diagnostics route through the shared logger\n// (S14 / dim-9, #1579) so consumers can control verbosity instead of receiving\n// unconditional stdout writes.\nconst logger = createLogger({ level: 'info' });\n\nasync function importScanner() {\n return importWorkspaceModule<ScannerModule>({\n packageName: '@happyvertical/smrt-scanner',\n distEntry: 'packages/scanner/dist/index.js',\n sourceEntry: 'packages/scanner/src/index.ts',\n purpose: 'manifest generation',\n });\n}\n\n/**\n * Options for ManifestBuilder.generate()\n *\n * Controls file discovery, scanner configuration, output generation, and metadata injection\n */\nexport interface ManifestBuilderOptions {\n // File Discovery\n include?: string[];\n exclude?: string[];\n followImports?: boolean;\n\n // Scanner Configuration\n baseClasses?: string[];\n loadViteConfig?: boolean;\n discoverExternalPackages?: boolean;\n includeExternalBaseClasses?: boolean;\n includePrivateMethods?: boolean;\n includeStaticMethods?: boolean;\n\n // Output Configuration\n outputDir?: string;\n outputName?: string;\n generateTypeStub?: boolean;\n stubName?: string;\n\n // Metadata\n injectPackageInfo?: boolean;\n moduleType?: string;\n\n // Tree Shaking (External Object Filtering)\n /**\n * @deprecated Tree shaking is no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n treeShake?: boolean;\n\n /**\n * @deprecated Whitelists are no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n externalObjectsWhitelist?: string[];\n}\n\ninterface ScannerConfig {\n baseClasses: string[];\n includePrivateMethods: boolean;\n includeStaticMethods: boolean;\n followImports: boolean;\n smrtDependencies?: string[];\n}\n\ninterface PackageInfo {\n name?: string;\n version?: string;\n}\n\n/**\n * Subset of the smrt-auto-service Vite plugin options we probe when reading\n * baseClasses out of a project's vite.config.ts.\n */\ninterface VitePluginOptions {\n baseClasses?: string[];\n followImports?: boolean;\n}\n\n/**\n * Structural shape of a loaded Vite plugin entry as probed by\n * loadViteConfigBaseClasses(). The plugin may expose its options directly, on\n * `_options`, or via an `api` object/function — all optional and discovered at\n * runtime, so each is modeled as optional here.\n */\ninterface VitePluginProbe {\n name?: string;\n options?: VitePluginOptions;\n _options?: VitePluginOptions;\n api?:\n | { options?: VitePluginOptions }\n | (() => { options?: VitePluginOptions });\n}\n\n/**\n * Orchestrates manifest generation with configurable options\n *\n * Consolidates logic from generate-manifest.js and generate-test-manifest.js\n * into a single, testable, and maintainable service.\n */\nexport class ManifestBuilder {\n /**\n * Generate manifest with specified options\n */\n async generate(\n options: ManifestBuilderOptions = {},\n ): Promise<SmartObjectManifest> {\n // 1. Discover files to scan\n const files = this.discoverFiles(options);\n\n if (files.length === 0) {\n logger.info('[smrt] No source files found');\n return this.createEmptyManifest(options);\n }\n\n logger.info(`[smrt] Scanning ${files.length} source file(s)...`);\n\n // 2. Configure scanner (baseClasses, external packages, vite config)\n const scannerConfig = await this.configureScanner(options);\n\n // 3. Scan files with OXC scanner\n const scannedManifest = await this.scanAndBuildManifest(\n scannerConfig,\n options,\n );\n\n // 4. Enrich manifest with metadata (smrtDependencies, packageInfo)\n const manifest = this.enrichManifest(\n scannedManifest,\n options,\n scannerConfig,\n );\n\n // 5. Write output files\n this.normalizeFilePaths(manifest);\n await this.writeOutput(manifest, options);\n\n return manifest;\n }\n\n private normalizeFilePaths(manifest: SmartObjectManifest): void {\n const workspaceRoot = this.findWorkspaceRoot(process.cwd());\n\n for (const obj of Object.values(manifest.objects || {})) {\n if (obj.filePath && isAbsolute(obj.filePath)) {\n obj.filePath = relative(workspaceRoot, obj.filePath).replace(\n /\\\\/g,\n '/',\n );\n }\n }\n }\n\n private findWorkspaceRoot(startDir: string): string {\n let current = resolve(startDir);\n\n while (true) {\n if (\n existsSync(resolve(current, 'pnpm-workspace.yaml')) ||\n existsSync(resolve(current, '.git'))\n ) {\n return current;\n }\n\n const parent = dirname(current);\n if (parent === current) {\n return resolve(startDir);\n }\n current = parent;\n }\n }\n\n /**\n * Discover source files based on include/exclude patterns\n */\n private discoverFiles(options: ManifestBuilderOptions): string[] {\n const include = options.include || ['src/**/*.ts'];\n const exclude = options.exclude || ['src/**/*.d.ts', 'node_modules/**'];\n\n return fg.sync(include, {\n absolute: true,\n ignore: exclude,\n });\n }\n\n /**\n * Configure AST scanner with baseClasses, external packages, and vite config\n */\n private async configureScanner(\n options: ManifestBuilderOptions,\n ): Promise<ScannerConfig> {\n let baseClasses = options.baseClasses || [\n 'SmrtObject',\n 'SmrtClass',\n 'SmrtCollection',\n ];\n\n // Load from vite.config.ts if requested\n if (options.loadViteConfig) {\n const viteBaseClasses = await this.loadViteConfigBaseClasses();\n if (viteBaseClasses && viteBaseClasses.length > 0) {\n baseClasses = viteBaseClasses;\n logger.info(\n `[smrt] Using baseClasses from vite.config.ts: ${baseClasses.join(', ')}`,\n );\n }\n }\n\n // Discover external SMRT packages\n let smrtDependencies: string[] = [];\n if (options.discoverExternalPackages) {\n logger.info('[smrt] Discovering external SMRT packages...');\n smrtDependencies = discoverSmrtPackages();\n logger.info(\n `[smrt] Found ${smrtDependencies.length} SMRT package(s): ${smrtDependencies.join(', ')}`,\n );\n\n // Optionally add external base classes\n if (options.includeExternalBaseClasses) {\n const externalClasses =\n await this.loadExternalBaseClasses(smrtDependencies);\n if (externalClasses.length > 0) {\n baseClasses = [...baseClasses, ...externalClasses];\n logger.info(\n `[smrt] Added ${externalClasses.length} external base class(es) to scanner`,\n );\n logger.info(`[smrt] Total baseClasses: ${baseClasses.length}`);\n }\n }\n }\n\n return {\n baseClasses,\n includePrivateMethods: options.includePrivateMethods ?? false,\n includeStaticMethods: options.includeStaticMethods ?? true,\n followImports: options.followImports ?? false,\n smrtDependencies,\n };\n }\n\n /**\n * Scan files using OxcScanner and build manifest via ManifestAdapter.\n *\n * This replaces the old ASTScanner-based scanFiles() method.\n * Uses the same OXC pipeline as the Vite plugin's scanWithOxc().\n */\n private async scanAndBuildManifest(\n config: ScannerConfig,\n options: ManifestBuilderOptions,\n ): Promise<SmartObjectManifest> {\n const { OxcScanner, ManifestAdapter } = await importScanner();\n\n const scanner = new OxcScanner({\n cwd: process.cwd(),\n include: options.include || ['src/**/*.ts'],\n exclude: options.exclude || ['src/**/*.d.ts', 'node_modules/**'],\n baseClasses: config.baseClasses,\n includePrivateMethods: config.includePrivateMethods,\n includeStaticMethods: config.includeStaticMethods,\n followImports: config.followImports,\n });\n\n const { results, resolved } = await scanner.scanAndResolve();\n\n // Read package.json for metadata\n let packageName: string | undefined;\n let packageVersion: string | undefined;\n let packageJson: { name?: string; version?: string } | undefined;\n try {\n const pkgPath = resolve(process.cwd(), 'package.json');\n const pkgContent = readFileSync(pkgPath, 'utf-8');\n packageJson = JSON.parse(pkgContent);\n packageName = packageJson?.name || undefined;\n packageVersion = packageJson?.version || undefined;\n } catch {\n // package.json not found - continue without packageName\n }\n\n // Convert to manifest format\n const adapter = new ManifestAdapter();\n const manifest = adapter.toManifest(resolved, {\n packageName,\n packageVersion,\n typeAliases: results.typeAliases,\n });\n\n // Set smrtDependencies BEFORE mergeInheritedFields so external packages can be loaded\n if (config.smrtDependencies && config.smrtDependencies.length > 0) {\n manifest.smrtDependencies = config.smrtDependencies;\n }\n\n // Run manifest generation passes (same as Vite plugin path)\n const manifestGen = new ManifestGenerator();\n manifestGen.injectTenantScopedFields(manifest);\n manifestGen.mergeInheritedFields(manifest);\n manifestGen.generateValidationRules(manifest);\n manifestGen.generateSchemas(manifest);\n manifestGen.assertTenantScopedSchemaContract(manifest);\n manifestGen.generateAgentManifests(manifest, packageName, packageJson);\n\n return manifest;\n }\n\n /**\n * Enrich manifest with metadata (moduleType, smrtDependencies, packageInfo).\n *\n * Note: As of Issue #1013, this method no longer merges external package\n * objects into the manifest. Dependencies are referenced via\n * `smrtDependencies` and resolved at runtime by manifest-loader.ts.\n */\n private enrichManifest(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n scannerConfig: ScannerConfig,\n ): SmartObjectManifest {\n // Add module type\n manifest.moduleType = options.moduleType || 'smrt';\n\n // Record dependency references (not their objects) so runtime\n // manifest-loader.ts can discover and load them on demand.\n if (\n scannerConfig.smrtDependencies &&\n scannerConfig.smrtDependencies.length > 0\n ) {\n manifest.smrtDependencies = scannerConfig.smrtDependencies;\n }\n\n // Inject package info\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n logger.info(`[smrt] Injected package name: ${packageInfo.name}`);\n } else {\n logger.warn(\n '[smrt] Warning: Could not read package.json, packageName will be undefined',\n );\n }\n }\n\n return manifest;\n }\n\n /**\n * Write manifest JSON and optional TypeScript stub\n */\n private async writeOutput(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n ): Promise<void> {\n const manager = new ManifestManager(process.cwd());\n const isTest =\n options.outputName?.includes('test') ||\n options.stubName?.includes('test');\n const mode = isTest ? 'dev' : 'build';\n\n // 1. Always write to the unified location via ManifestManager\n manager.write(manifest, mode);\n\n // 2. Legacy/Explicit Output (if requested or for stubs)\n const outputDir = options.outputDir || 'src/manifest';\n const outputName = options.outputName || 'manifest.json';\n\n // Ensure output directory exists for legacy/stubs\n mkdirSync(outputDir, { recursive: true });\n\n // Write JSON manifest to legacy location if different from unified path\n const manifestPath = resolve(outputDir, outputName);\n const unifiedPath = manager.getOutputPath(mode);\n\n if (resolve(manifestPath) !== resolve(unifiedPath)) {\n writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));\n }\n\n // Write TypeScript stub if requested\n if (options.generateTypeStub) {\n const stubName = options.stubName || 'manifest.ts';\n const tsContent = this.generateTypeStub(manifest, stubName);\n const stubPath = resolve(outputDir, stubName);\n writeFileSync(stubPath, tsContent);\n }\n\n const objectCount = Object.keys(manifest.objects).length;\n logger.info(`[smrt] ✅ Generated manifest with ${objectCount} object(s)`);\n logger.info(`[smrt] Unified: ${unifiedPath}`);\n }\n\n /**\n * Generate TypeScript stub file with manifest constant\n */\n private generateTypeStub(\n manifest: SmartObjectManifest,\n filename: string,\n ): string {\n const isTestManifest = filename.includes('test');\n\n const comment = isTestManifest\n ? `/**\n * Test manifest stub\n * This file is replaced by generate-test-manifest.js during pretest\n * If tests run without pretest, this empty manifest is used\n *\n * DO NOT EDIT MANUALLY - This file is automatically generated\n */`\n : `/**\n * Auto-generated static manifest\n * Generated at build time from SMRT object scanning\n * DO NOT EDIT - This file is automatically generated\n */`;\n\n const exportName = isTestManifest ? 'testManifest' : 'staticManifest';\n\n // Use relative import for core package (to avoid self-reference during build),\n // package import for all other packages\n const isCorePackage = manifest.packageName === '@happyvertical/smrt-core';\n const importPath = isCorePackage\n ? '../scanner/types.js'\n : '@happyvertical/smrt-core/scanner/types';\n\n return `${comment}\n\nimport type { SmartObjectManifest } from '${importPath}';\n\nexport const ${exportName}: SmartObjectManifest = ${JSON.stringify(manifest, null, 2)} as const;\n\nexport default ${exportName};\n`;\n }\n\n /**\n * Load baseClasses from vite.config.ts\n */\n private async loadViteConfigBaseClasses(): Promise<string[] | null> {\n try {\n const viteConfigPath = resolve(process.cwd(), 'vite.config.ts');\n if (!existsSync(viteConfigPath)) {\n logger.info('[smrt] vite.config.ts not found');\n return null;\n }\n\n logger.info('[smrt] Found vite.config.ts, attempting to load...');\n\n // Use vite to load config which handles TypeScript\n const { loadConfigFromFile } = await import('vite');\n const loaded = await loadConfigFromFile(\n { command: 'build', mode: 'test' },\n viteConfigPath,\n );\n\n if (!loaded?.config?.plugins) {\n logger.info('[smrt] No plugins found in vite config');\n return null;\n }\n\n // Vite's loaded plugins are a recursive PluginOption[] (plugins, arrays,\n // falsy values, promises). We only probe a few optional fields, so model\n // them structurally and narrow defensively instead of using `any`.\n const plugins = loaded.config.plugins as readonly VitePluginProbe[];\n\n logger.info('[smrt] Vite config loaded', {\n plugins: plugins.map((p) => p?.name),\n });\n\n // Find smrtPlugin in plugins array\n const smrtPlugin = plugins.find((p) => p?.name === 'smrt-auto-service');\n\n if (!smrtPlugin) {\n logger.info(\n '[smrt] smrt-auto-service plugin not found in plugins array',\n );\n return null;\n }\n\n logger.info('[smrt] Found smrt-auto-service plugin');\n\n // Try different ways to access options\n const api = smrtPlugin.api;\n let opts: VitePluginOptions | undefined =\n (typeof api === 'object' && api !== null ? api.options : undefined) ||\n smrtPlugin.options ||\n smrtPlugin._options;\n\n if (!opts && typeof api === 'function') {\n try {\n opts = api().options;\n } catch (e) {\n logger.info('[smrt] Could not call plugin.api()');\n }\n }\n\n if (opts?.baseClasses) {\n logger.info('[smrt] Plugin options', {\n baseClasses: opts.baseClasses,\n followImports: opts.followImports,\n });\n return opts.baseClasses;\n }\n\n logger.info('[smrt] Could not access plugin options');\n return null;\n } catch (error) {\n logger.warn('[smrt] Error loading vite.config.ts', {\n error: error instanceof Error ? error.message : String(error),\n });\n logger.info('[smrt] Using default baseClasses');\n return null;\n }\n }\n\n /**\n * Load base classes from external SMRT package manifests\n */\n private async loadExternalBaseClasses(packages: string[]): Promise<string[]> {\n const externalBaseClasses: string[] = [];\n\n for (const pkgName of packages) {\n // Resolve the manifest the same way discovery does (honors `.smrt/` and\n // `src/manifest/`, not just `dist/`) so source-only workspace packages\n // still contribute base classes (#1378).\n const manifestPath = resolveManifestPath(pkgName, process.cwd());\n if (!manifestPath) {\n logger.info(`[smrt] ${pkgName}: no SMRT manifest resolved`);\n continue;\n }\n\n try {\n const manifestContent = readFileSync(manifestPath, 'utf-8');\n const manifest: SmartObjectManifest = JSON.parse(manifestContent);\n\n // Extract all class names from this package\n const foundClassNames: string[] = [];\n for (const objDef of Object.values(manifest.objects)) {\n if (objDef.className) {\n const className = objDef.className;\n foundClassNames.push(className);\n externalBaseClasses.push(className);\n }\n }\n\n logger.info(\n `[smrt] ${pkgName}: found ${foundClassNames.length} class(es) - ${foundClassNames.join(', ')}`,\n );\n } catch (error) {\n logger.info(\n `[smrt] ${pkgName}: manifest not found or invalid - ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n return externalBaseClasses;\n }\n\n /**\n * Read package.json from current working directory\n */\n private readPackageJson(): PackageInfo {\n try {\n const packageJsonPath = resolve(process.cwd(), 'package.json');\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n return {\n name: packageJson.name,\n version: packageJson.version,\n };\n } catch (error) {\n return {};\n }\n }\n\n /**\n * Create empty manifest when no files found\n */\n private createEmptyManifest(\n options: ManifestBuilderOptions,\n ): SmartObjectManifest {\n const manifest: SmartObjectManifest = {\n version: '1.0.0',\n timestamp: Date.now(),\n objects: {},\n moduleType: options.moduleType || 'smrt',\n };\n\n if (options.discoverExternalPackages) {\n manifest.smrtDependencies = discoverSmrtPackages();\n }\n\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n }\n }\n\n return manifest;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAsBA,IAAM,SAAS,aAAa,EAAE,OAAO,OAAO,CAAC;AAE7C,eAAe,gBAAgB;CAC7B,OAAO,sBAAqC;EAC1C,aAAa;EACb,WAAW;EACX,aAAa;EACb,SAAS;CACX,CAAC;AACH;;;;;;;AAsFA,IAAa,kBAAb,MAA6B;;;;CAI3B,MAAM,SACJ,UAAkC,CAAC,GACL;EAE9B,MAAM,QAAQ,KAAK,cAAc,OAAO;EAExC,IAAI,MAAM,WAAW,GAAG;GACtB,OAAO,KAAK,8BAA8B;GAC1C,OAAO,KAAK,oBAAoB,OAAO;EACzC;EAEA,OAAO,KAAK,mBAAmB,MAAM,OAAO,mBAAmB;EAG/D,MAAM,gBAAgB,MAAM,KAAK,iBAAiB,OAAO;EAGzD,MAAM,kBAAkB,MAAM,KAAK,qBACjC,eACA,OACF;EAGA,MAAM,WAAW,KAAK,eACpB,iBACA,SACA,aACF;EAGA,KAAK,mBAAmB,QAAQ;EAChC,MAAM,KAAK,YAAY,UAAU,OAAO;EAExC,OAAO;CACT;CAEA,mBAA2B,UAAqC;EAC9D,MAAM,gBAAgB,KAAK,kBAAkB,QAAQ,IAAI,CAAC;EAE1D,KAAK,MAAM,OAAO,OAAO,OAAO,SAAS,WAAW,CAAC,CAAC,GACpD,IAAI,IAAI,YAAY,WAAW,IAAI,QAAQ,GACzC,IAAI,WAAW,SAAS,eAAe,IAAI,QAAQ,CAAC,CAAC,QACnD,OACA,GACF;CAGN;CAEA,kBAA0B,UAA0B;EAClD,IAAI,UAAU,QAAQ,QAAQ;EAE9B,OAAO,MAAM;GACX,IACE,WAAW,QAAQ,SAAS,qBAAqB,CAAC,KAClD,WAAW,QAAQ,SAAS,MAAM,CAAC,GAEnC,OAAO;GAGT,MAAM,SAAS,QAAQ,OAAO;GAC9B,IAAI,WAAW,SACb,OAAO,QAAQ,QAAQ;GAEzB,UAAU;EACZ;CACF;;;;CAKA,cAAsB,SAA2C;EAC/D,MAAM,UAAU,QAAQ,WAAW,CAAC,aAAa;EACjD,MAAM,UAAU,QAAQ,WAAW,CAAC,iBAAiB,iBAAiB;EAEtE,OAAO,GAAG,KAAK,SAAS;GACtB,UAAU;GACV,QAAQ;EACV,CAAC;CACH;;;;CAKA,MAAc,iBACZ,SACwB;EACxB,IAAI,cAAc,QAAQ,eAAe;GACvC;GACA;GACA;EACF;EAGA,IAAI,QAAQ,gBAAgB;GAC1B,MAAM,kBAAkB,MAAM,KAAK,0BAA0B;GAC7D,IAAI,mBAAmB,gBAAgB,SAAS,GAAG;IACjD,cAAc;IACd,OAAO,KACL,iDAAiD,YAAY,KAAK,IAAI,GACxE;GACF;EACF;EAGA,IAAI,mBAA6B,CAAC;EAClC,IAAI,QAAQ,0BAA0B;GACpC,OAAO,KAAK,8CAA8C;GAC1D,mBAAmB,qBAAqB;GACxC,OAAO,KACL,gBAAgB,iBAAiB,OAAO,oBAAoB,iBAAiB,KAAK,IAAI,GACxF;GAGA,IAAI,QAAQ,4BAA4B;IACtC,MAAM,kBACJ,MAAM,KAAK,wBAAwB,gBAAgB;IACrD,IAAI,gBAAgB,SAAS,GAAG;KAC9B,cAAc,CAAC,GAAG,aAAa,GAAG,eAAe;KACjD,OAAO,KACL,gBAAgB,gBAAgB,OAAO,oCACzC;KACA,OAAO,KAAK,6BAA6B,YAAY,QAAQ;IAC/D;GACF;EACF;EAEA,OAAO;GACL;GACA,uBAAuB,QAAQ,yBAAyB;GACxD,sBAAsB,QAAQ,wBAAwB;GACtD,eAAe,QAAQ,iBAAiB;GACxC;EACF;CACF;;;;;;;CAQA,MAAc,qBACZ,QACA,SAC8B;EAC9B,MAAM,EAAE,YAAY,oBAAoB,MAAM,cAAc;EAY5D,MAAM,EAAE,SAAS,aAAa,MAAM,IAVhB,WAAW;GAC7B,KAAK,QAAQ,IAAI;GACjB,SAAS,QAAQ,WAAW,CAAC,aAAa;GAC1C,SAAS,QAAQ,WAAW,CAAC,iBAAiB,iBAAiB;GAC/D,aAAa,OAAO;GACpB,uBAAuB,OAAO;GAC9B,sBAAsB,OAAO;GAC7B,eAAe,OAAO;EACxB,CAEoC,CAAA,CAAQ,eAAe;EAG3D,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;GAEF,MAAM,aAAa,aADH,QAAQ,QAAQ,IAAI,GAAG,cACP,GAAS,OAAO;GAChD,cAAc,KAAK,MAAM,UAAU;GACnC,cAAc,aAAa,QAAQ,KAAA;GACnC,iBAAiB,aAAa,WAAW,KAAA;EAC3C,QAAQ,CAER;EAIA,MAAM,WAAW,IADG,gBACH,CAAA,CAAQ,WAAW,UAAU;GAC5C;GACA;GACA,aAAa,QAAQ;EACvB,CAAC;EAGD,IAAI,OAAO,oBAAoB,OAAO,iBAAiB,SAAS,GAC9D,SAAS,mBAAmB,OAAO;EAIrC,MAAM,cAAc,IAAI,kBAAkB;EAC1C,YAAY,yBAAyB,QAAQ;EAC7C,YAAY,qBAAqB,QAAQ;EACzC,YAAY,wBAAwB,QAAQ;EAC5C,YAAY,gBAAgB,QAAQ;EACpC,YAAY,iCAAiC,QAAQ;EACrD,YAAY,uBAAuB,UAAU,aAAa,WAAW;EAErE,OAAO;CACT;;;;;;;;CASA,eACE,UACA,SACA,eACqB;EAErB,SAAS,aAAa,QAAQ,cAAc;EAI5C,IACE,cAAc,oBACd,cAAc,iBAAiB,SAAS,GAExC,SAAS,mBAAmB,cAAc;EAI5C,IAAI,QAAQ,mBAAmB;GAC7B,MAAM,cAAc,KAAK,gBAAgB;GACzC,IAAI,YAAY,MAAM;IACpB,SAAS,cAAc,YAAY;IACnC,OAAO,KAAK,iCAAiC,YAAY,MAAM;GACjE,OACE,OAAO,KACL,4EACF;EAEJ;EAEA,OAAO;CACT;;;;CAKA,MAAc,YACZ,UACA,SACe;EACf,MAAM,UAAU,IAAI,gBAAgB,QAAQ,IAAI,CAAC;EAIjD,MAAM,OAFJ,QAAQ,YAAY,SAAS,MAAM,KACnC,QAAQ,UAAU,SAAS,MAAM,IACb,QAAQ;EAG9B,QAAQ,MAAM,UAAU,IAAI;EAG5B,MAAM,YAAY,QAAQ,aAAa;EACvC,MAAM,aAAa,QAAQ,cAAc;EAGzC,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;EAGxC,MAAM,eAAe,QAAQ,WAAW,UAAU;EAClD,MAAM,cAAc,QAAQ,cAAc,IAAI;EAE9C,IAAI,QAAQ,YAAY,MAAM,QAAQ,WAAW,GAC/C,cAAc,cAAc,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;EAI/D,IAAI,QAAQ,kBAAkB;GAC5B,MAAM,WAAW,QAAQ,YAAY;GACrC,MAAM,YAAY,KAAK,iBAAiB,UAAU,QAAQ;GAE1D,cADiB,QAAQ,WAAW,QACtB,GAAU,SAAS;EACnC;EAEA,MAAM,cAAc,OAAO,KAAK,SAAS,OAAO,CAAC,CAAC;EAClD,OAAO,KAAK,oCAAoC,YAAY,WAAW;EACvE,OAAO,KAAK,sBAAsB,aAAa;CACjD;;;;CAKA,iBACE,UACA,UACQ;EACR,MAAM,iBAAiB,SAAS,SAAS,MAAM;EAE/C,MAAM,UAAU,iBACZ;;;;;;OAOA;;;;;EAMJ,MAAM,aAAa,iBAAiB,iBAAiB;EASrD,OAAO,GAAG,QAAQ;;4CALI,SAAS,gBAAgB,6BAE3C,wBACA,yCAI+C;;eAExC,WAAW,0BAA0B,KAAK,UAAU,UAAU,MAAM,CAAC,EAAE;;iBAErE,WAAW;;CAE1B;;;;CAKA,MAAc,4BAAsD;EAClE,IAAI;GACF,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,GAAG,gBAAgB;GAC9D,IAAI,CAAC,WAAW,cAAc,GAAG;IAC/B,OAAO,KAAK,iCAAiC;IAC7C,OAAO;GACT;GAEA,OAAO,KAAK,oDAAoD;GAGhE,MAAM,EAAE,uBAAuB,MAAM,OAAO;GAC5C,MAAM,SAAS,MAAM,mBACnB;IAAE,SAAS;IAAS,MAAM;GAAO,GACjC,cACF;GAEA,IAAI,CAAC,QAAQ,QAAQ,SAAS;IAC5B,OAAO,KAAK,wCAAwC;IACpD,OAAO;GACT;GAKA,MAAM,UAAU,OAAO,OAAO;GAE9B,OAAO,KAAK,6BAA6B,EACvC,SAAS,QAAQ,KAAK,MAAM,GAAG,IAAI,EACrC,CAAC;GAGD,MAAM,aAAa,QAAQ,MAAM,MAAM,GAAG,SAAS,mBAAmB;GAEtE,IAAI,CAAC,YAAY;IACf,OAAO,KACL,4DACF;IACA,OAAO;GACT;GAEA,OAAO,KAAK,uCAAuC;GAGnD,MAAM,MAAM,WAAW;GACvB,IAAI,QACD,OAAO,QAAQ,YAAY,QAAQ,OAAO,IAAI,UAAU,KAAA,MACzD,WAAW,WACX,WAAW;GAEb,IAAI,CAAC,QAAQ,OAAO,QAAQ,YAC1B,IAAI;IACF,OAAO,IAAI,CAAC,CAAC;GACf,SAAS,GAAG;IACV,OAAO,KAAK,oCAAoC;GAClD;GAGF,IAAI,MAAM,aAAa;IACrB,OAAO,KAAK,yBAAyB;KACnC,aAAa,KAAK;KAClB,eAAe,KAAK;IACtB,CAAC;IACD,OAAO,KAAK;GACd;GAEA,OAAO,KAAK,wCAAwC;GACpD,OAAO;EACT,SAAS,OAAO;GACd,OAAO,KAAK,uCAAuC,EACjD,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAC9D,CAAC;GACD,OAAO,KAAK,kCAAkC;GAC9C,OAAO;EACT;CACF;;;;CAKA,MAAc,wBAAwB,UAAuC;EAC3E,MAAM,sBAAgC,CAAC;EAEvC,KAAK,MAAM,WAAW,UAAU;GAI9B,MAAM,eAAe,oBAAoB,SAAS,QAAQ,IAAI,CAAC;GAC/D,IAAI,CAAC,cAAc;IACjB,OAAO,KAAK,YAAY,QAAQ,4BAA4B;IAC5D;GACF;GAEA,IAAI;IACF,MAAM,kBAAkB,aAAa,cAAc,OAAO;IAC1D,MAAM,WAAgC,KAAK,MAAM,eAAe;IAGhE,MAAM,kBAA4B,CAAC;IACnC,KAAK,MAAM,UAAU,OAAO,OAAO,SAAS,OAAO,GACjD,IAAI,OAAO,WAAW;KACpB,MAAM,YAAY,OAAO;KACzB,gBAAgB,KAAK,SAAS;KAC9B,oBAAoB,KAAK,SAAS;IACpC;IAGF,OAAO,KACL,YAAY,QAAQ,UAAU,gBAAgB,OAAO,eAAe,gBAAgB,KAAK,IAAI,GAC/F;GACF,SAAS,OAAO;IACd,OAAO,KACL,YAAY,QAAQ,oCAAoC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAC/G;GACF;EACF;EAEA,OAAO;CACT;;;;CAKA,kBAAuC;EACrC,IAAI;GACF,MAAM,kBAAkB,QAAQ,QAAQ,IAAI,GAAG,cAAc;GAC7D,MAAM,cAAc,KAAK,MAAM,aAAa,iBAAiB,OAAO,CAAC;GACrE,OAAO;IACL,MAAM,YAAY;IAClB,SAAS,YAAY;GACvB;EACF,SAAS,OAAO;GACd,OAAO,CAAC;EACV;CACF;;;;CAKA,oBACE,SACqB;EACrB,MAAM,WAAgC;GACpC,SAAS;GACT,WAAW,KAAK,IAAI;GACpB,SAAS,CAAC;GACV,YAAY,QAAQ,cAAc;EACpC;EAEA,IAAI,QAAQ,0BACV,SAAS,mBAAmB,qBAAqB;EAGnD,IAAI,QAAQ,mBAAmB;GAC7B,MAAM,cAAc,KAAK,gBAAgB;GACzC,IAAI,YAAY,MACd,SAAS,cAAc,YAAY;EAEvC;EAEA,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"generator.js","names":[],"sources":["../../src/manifest/generator.ts"],"sourcesContent":["/**\n * Manifest generation orchestrator\n * Consolidates manifest generation logic for both build and test manifests\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, isAbsolute, relative, resolve } from 'node:path';\nimport { createLogger } from '@happyvertical/logger';\nimport fg from 'fast-glob';\nimport { ManifestGenerator } from '../scanner/manifest-generator.js';\nimport type { SmartObjectManifest } from '../scanner/types.js';\nimport { importWorkspaceModule } from '../utils/import-workspace-module.js';\nimport type { ScannerModule } from '../utils/scanner-module.js';\nimport {\n discoverSmrtPackages,\n resolveManifestPath,\n} from './discover-smrt-packages.js';\nimport { ManifestManager } from './manager.js';\n\n// Build-time manifest-generation diagnostics route through the shared logger\n// (S14 / dim-9, #1579) so consumers can control verbosity instead of receiving\n// unconditional stdout writes.\nconst logger = createLogger({ level: 'info' });\n\nasync function importScanner() {\n return importWorkspaceModule<ScannerModule>({\n packageName: '@happyvertical/smrt-scanner',\n distEntry: 'packages/scanner/dist/index.js',\n sourceEntry: 'packages/scanner/src/index.ts',\n purpose: 'manifest generation',\n });\n}\n\n/**\n * Options for ManifestBuilder.generate()\n *\n * Controls file discovery, scanner configuration, output generation, and metadata injection\n */\nexport interface ManifestBuilderOptions {\n // File Discovery\n include?: string[];\n exclude?: string[];\n followImports?: boolean;\n\n // Scanner Configuration\n baseClasses?: string[];\n loadViteConfig?: boolean;\n discoverExternalPackages?: boolean;\n includeExternalBaseClasses?: boolean;\n includePrivateMethods?: boolean;\n includeStaticMethods?: boolean;\n\n // Output Configuration\n outputDir?: string;\n outputName?: string;\n generateTypeStub?: boolean;\n stubName?: string;\n\n // Metadata\n injectPackageInfo?: boolean;\n moduleType?: string;\n\n // Tree Shaking (External Object Filtering)\n /**\n * @deprecated Tree shaking is no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n treeShake?: boolean;\n\n /**\n * @deprecated Whitelists are no longer needed. The AST scanner no longer includes external objects in the manifest by default.\n */\n externalObjectsWhitelist?: string[];\n}\n\ninterface ScannerConfig {\n baseClasses: string[];\n includePrivateMethods: boolean;\n includeStaticMethods: boolean;\n followImports: boolean;\n smrtDependencies?: string[];\n}\n\ninterface PackageInfo {\n name?: string;\n version?: string;\n}\n\n/**\n * Subset of the smrt-auto-service Vite plugin options we probe when reading\n * baseClasses out of a project's vite.config.ts.\n */\ninterface VitePluginOptions {\n baseClasses?: string[];\n followImports?: boolean;\n}\n\n/**\n * Structural shape of a loaded Vite plugin entry as probed by\n * loadViteConfigBaseClasses(). The plugin may expose its options directly, on\n * `_options`, or via an `api` object/function — all optional and discovered at\n * runtime, so each is modeled as optional here.\n */\ninterface VitePluginProbe {\n name?: string;\n options?: VitePluginOptions;\n _options?: VitePluginOptions;\n api?:\n | { options?: VitePluginOptions }\n | (() => { options?: VitePluginOptions });\n}\n\n/**\n * Orchestrates manifest generation with configurable options\n *\n * Consolidates logic from generate-manifest.js and generate-test-manifest.js\n * into a single, testable, and maintainable service.\n */\nexport class ManifestBuilder {\n /**\n * Generate manifest with specified options\n */\n async generate(\n options: ManifestBuilderOptions = {},\n ): Promise<SmartObjectManifest> {\n // 1. Discover files to scan\n const files = this.discoverFiles(options);\n\n if (files.length === 0) {\n logger.info('[smrt] No source files found');\n return this.createEmptyManifest(options);\n }\n\n logger.info(`[smrt] Scanning ${files.length} source file(s)...`);\n\n // 2. Configure scanner (baseClasses, external packages, vite config)\n const scannerConfig = await this.configureScanner(options);\n\n // 3. Scan files with OXC scanner\n const scannedManifest = await this.scanAndBuildManifest(\n scannerConfig,\n options,\n );\n\n // 4. Enrich manifest with metadata (smrtDependencies, packageInfo)\n const manifest = this.enrichManifest(\n scannedManifest,\n options,\n scannerConfig,\n );\n\n // 5. Write output files\n this.normalizeFilePaths(manifest);\n await this.writeOutput(manifest, options);\n\n return manifest;\n }\n\n private normalizeFilePaths(manifest: SmartObjectManifest): void {\n const workspaceRoot = this.findWorkspaceRoot(process.cwd());\n\n for (const obj of Object.values(manifest.objects || {})) {\n if (obj.filePath && isAbsolute(obj.filePath)) {\n obj.filePath = relative(workspaceRoot, obj.filePath).replace(\n /\\\\/g,\n '/',\n );\n }\n }\n }\n\n private findWorkspaceRoot(startDir: string): string {\n let current = resolve(startDir);\n\n while (true) {\n if (\n existsSync(resolve(current, 'pnpm-workspace.yaml')) ||\n existsSync(resolve(current, '.git'))\n ) {\n return current;\n }\n\n const parent = dirname(current);\n if (parent === current) {\n return resolve(startDir);\n }\n current = parent;\n }\n }\n\n /**\n * Discover source files based on include/exclude patterns\n */\n private discoverFiles(options: ManifestBuilderOptions): string[] {\n const include = options.include || ['src/**/*.ts'];\n const exclude = options.exclude || ['src/**/*.d.ts', 'node_modules/**'];\n\n return fg.sync(include, {\n absolute: true,\n ignore: exclude,\n });\n }\n\n /**\n * Configure AST scanner with baseClasses, external packages, and vite config\n */\n private async configureScanner(\n options: ManifestBuilderOptions,\n ): Promise<ScannerConfig> {\n let baseClasses = options.baseClasses || [\n 'SmrtObject',\n 'SmrtClass',\n 'SmrtCollection',\n ];\n\n // Load from vite.config.ts if requested\n if (options.loadViteConfig) {\n const viteBaseClasses = await this.loadViteConfigBaseClasses();\n if (viteBaseClasses && viteBaseClasses.length > 0) {\n baseClasses = viteBaseClasses;\n logger.info(\n `[smrt] Using baseClasses from vite.config.ts: ${baseClasses.join(', ')}`,\n );\n }\n }\n\n // Discover external SMRT packages\n let smrtDependencies: string[] = [];\n if (options.discoverExternalPackages) {\n logger.info('[smrt] Discovering external SMRT packages...');\n smrtDependencies = discoverSmrtPackages();\n logger.info(\n `[smrt] Found ${smrtDependencies.length} SMRT package(s): ${smrtDependencies.join(', ')}`,\n );\n\n // Optionally add external base classes\n if (options.includeExternalBaseClasses) {\n const externalClasses =\n await this.loadExternalBaseClasses(smrtDependencies);\n if (externalClasses.length > 0) {\n baseClasses = [...baseClasses, ...externalClasses];\n logger.info(\n `[smrt] Added ${externalClasses.length} external base class(es) to scanner`,\n );\n logger.info(`[smrt] Total baseClasses: ${baseClasses.length}`);\n }\n }\n }\n\n return {\n baseClasses,\n includePrivateMethods: options.includePrivateMethods ?? false,\n includeStaticMethods: options.includeStaticMethods ?? true,\n followImports: options.followImports ?? false,\n smrtDependencies,\n };\n }\n\n /**\n * Scan files using OxcScanner and build manifest via ManifestAdapter.\n *\n * This replaces the old ASTScanner-based scanFiles() method.\n * Uses the same OXC pipeline as the Vite plugin's scanWithOxc().\n */\n private async scanAndBuildManifest(\n config: ScannerConfig,\n options: ManifestBuilderOptions,\n ): Promise<SmartObjectManifest> {\n const { OxcScanner, ManifestAdapter } = await importScanner();\n\n const scanner = new OxcScanner({\n cwd: process.cwd(),\n include: options.include || ['src/**/*.ts'],\n exclude: options.exclude || ['src/**/*.d.ts', 'node_modules/**'],\n baseClasses: config.baseClasses,\n includePrivateMethods: config.includePrivateMethods,\n includeStaticMethods: config.includeStaticMethods,\n followImports: config.followImports,\n });\n\n const { results, resolved } = await scanner.scanAndResolve();\n\n // Read package.json for metadata\n let packageName: string | undefined;\n let packageVersion: string | undefined;\n let packageJson: { name?: string; version?: string } | undefined;\n try {\n const pkgPath = resolve(process.cwd(), 'package.json');\n const pkgContent = readFileSync(pkgPath, 'utf-8');\n packageJson = JSON.parse(pkgContent);\n packageName = packageJson?.name || undefined;\n packageVersion = packageJson?.version || undefined;\n } catch {\n // package.json not found - continue without packageName\n }\n\n // Convert to manifest format\n const adapter = new ManifestAdapter();\n const manifest = adapter.toManifest(resolved, {\n packageName,\n packageVersion,\n typeAliases: results.typeAliases,\n });\n\n // Set smrtDependencies BEFORE mergeInheritedFields so external packages can be loaded\n if (config.smrtDependencies && config.smrtDependencies.length > 0) {\n manifest.smrtDependencies = config.smrtDependencies;\n }\n\n // Run manifest generation passes (same as Vite plugin path)\n const manifestGen = new ManifestGenerator();\n manifestGen.injectTenantScopedFields(manifest);\n manifestGen.mergeInheritedFields(manifest);\n manifestGen.generateValidationRules(manifest);\n manifestGen.generateSchemas(manifest);\n manifestGen.assertTenantScopedSchemaContract(manifest);\n manifestGen.generateAgentManifests(manifest, packageName, packageJson);\n\n return manifest;\n }\n\n /**\n * Enrich manifest with metadata (moduleType, smrtDependencies, packageInfo).\n *\n * Note: As of Issue #1013, this method no longer merges external package\n * objects into the manifest. Dependencies are referenced via\n * `smrtDependencies` and resolved at runtime by manifest-loader.ts.\n */\n private enrichManifest(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n scannerConfig: ScannerConfig,\n ): SmartObjectManifest {\n // Add module type\n manifest.moduleType = options.moduleType || 'smrt';\n\n // Record dependency references (not their objects) so runtime\n // manifest-loader.ts can discover and load them on demand.\n if (\n scannerConfig.smrtDependencies &&\n scannerConfig.smrtDependencies.length > 0\n ) {\n manifest.smrtDependencies = scannerConfig.smrtDependencies;\n }\n\n // Inject package info\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n logger.info(`[smrt] Injected package name: ${packageInfo.name}`);\n } else {\n logger.warn(\n '[smrt] Warning: Could not read package.json, packageName will be undefined',\n );\n }\n }\n\n return manifest;\n }\n\n /**\n * Write manifest JSON and optional TypeScript stub\n */\n private async writeOutput(\n manifest: SmartObjectManifest,\n options: ManifestBuilderOptions,\n ): Promise<void> {\n const manager = new ManifestManager(process.cwd());\n const isTest =\n options.outputName?.includes('test') ||\n options.stubName?.includes('test');\n const mode = isTest ? 'dev' : 'build';\n\n // 1. Always write to the unified location via ManifestManager\n manager.write(manifest, mode);\n\n // 2. Legacy/Explicit Output (if requested or for stubs)\n const outputDir = options.outputDir || 'src/manifest';\n const outputName = options.outputName || 'manifest.json';\n\n // Ensure output directory exists for legacy/stubs\n mkdirSync(outputDir, { recursive: true });\n\n // Write JSON manifest to legacy location if different from unified path\n const manifestPath = resolve(outputDir, outputName);\n const unifiedPath = manager.getOutputPath(mode);\n\n if (resolve(manifestPath) !== resolve(unifiedPath)) {\n writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));\n }\n\n // Write TypeScript stub if requested\n if (options.generateTypeStub) {\n const stubName = options.stubName || 'manifest.ts';\n const tsContent = this.generateTypeStub(manifest, stubName);\n const stubPath = resolve(outputDir, stubName);\n writeFileSync(stubPath, tsContent);\n }\n\n const objectCount = Object.keys(manifest.objects).length;\n logger.info(`[smrt] ✅ Generated manifest with ${objectCount} object(s)`);\n logger.info(`[smrt] Unified: ${unifiedPath}`);\n }\n\n /**\n * Generate TypeScript stub file with manifest constant\n */\n private generateTypeStub(\n manifest: SmartObjectManifest,\n filename: string,\n ): string {\n const isTestManifest = filename.includes('test');\n\n const comment = isTestManifest\n ? `/**\n * Test manifest stub\n * This file is replaced by generate-test-manifest.js during pretest\n * If tests run without pretest, this empty manifest is used\n *\n * DO NOT EDIT MANUALLY - This file is automatically generated\n */`\n : `/**\n * Auto-generated static manifest\n * Generated at build time from SMRT object scanning\n * DO NOT EDIT - This file is automatically generated\n */`;\n\n const exportName = isTestManifest ? 'testManifest' : 'staticManifest';\n\n // Use relative import for core package (to avoid self-reference during build),\n // package import for all other packages\n const isCorePackage = manifest.packageName === '@happyvertical/smrt-core';\n const importPath = isCorePackage\n ? '../scanner/types.js'\n : '@happyvertical/smrt-core/scanner/types';\n\n return `${comment}\n\nimport type { SmartObjectManifest } from '${importPath}';\n\nexport const ${exportName}: SmartObjectManifest = ${JSON.stringify(manifest, null, 2)} as const;\n\nexport default ${exportName};\n`;\n }\n\n /**\n * Load baseClasses from vite.config.ts\n */\n private async loadViteConfigBaseClasses(): Promise<string[] | null> {\n try {\n const viteConfigPath = resolve(process.cwd(), 'vite.config.ts');\n if (!existsSync(viteConfigPath)) {\n logger.info('[smrt] vite.config.ts not found');\n return null;\n }\n\n logger.info('[smrt] Found vite.config.ts, attempting to load...');\n\n // Use vite to load config which handles TypeScript\n const { loadConfigFromFile } = await import('vite');\n const loaded = await loadConfigFromFile(\n { command: 'build', mode: 'test' },\n viteConfigPath,\n );\n\n if (!loaded?.config?.plugins) {\n logger.info('[smrt] No plugins found in vite config');\n return null;\n }\n\n // Vite's loaded plugins are a recursive PluginOption[] (plugins, arrays,\n // falsy values, promises). We only probe a few optional fields, so model\n // them structurally and narrow defensively instead of using `any`.\n const plugins = loaded.config.plugins as readonly VitePluginProbe[];\n\n logger.info('[smrt] Vite config loaded', {\n plugins: plugins.map((p) => p?.name),\n });\n\n // Find smrtPlugin in plugins array\n const smrtPlugin = plugins.find((p) => p?.name === 'smrt-auto-service');\n\n if (!smrtPlugin) {\n logger.info(\n '[smrt] smrt-auto-service plugin not found in plugins array',\n );\n return null;\n }\n\n logger.info('[smrt] Found smrt-auto-service plugin');\n\n // Try different ways to access options\n const api = smrtPlugin.api;\n let opts: VitePluginOptions | undefined =\n (typeof api === 'object' && api !== null ? api.options : undefined) ||\n smrtPlugin.options ||\n smrtPlugin._options;\n\n if (!opts && typeof api === 'function') {\n try {\n opts = api().options;\n } catch (e) {\n logger.info('[smrt] Could not call plugin.api()');\n }\n }\n\n if (opts?.baseClasses) {\n logger.info('[smrt] Plugin options', {\n baseClasses: opts.baseClasses,\n followImports: opts.followImports,\n });\n return opts.baseClasses;\n }\n\n logger.info('[smrt] Could not access plugin options');\n return null;\n } catch (error) {\n logger.warn('[smrt] Error loading vite.config.ts', {\n error: error instanceof Error ? error.message : String(error),\n });\n logger.info('[smrt] Using default baseClasses');\n return null;\n }\n }\n\n /**\n * Load base classes from external SMRT package manifests\n */\n private async loadExternalBaseClasses(packages: string[]): Promise<string[]> {\n const externalBaseClasses: string[] = [];\n\n for (const pkgName of packages) {\n // Resolve the manifest the same way discovery does (honors `.smrt/` and\n // `src/manifest/`, not just `dist/`) so source-only workspace packages\n // still contribute base classes (#1378).\n const manifestPath = resolveManifestPath(pkgName, process.cwd());\n if (!manifestPath) {\n logger.info(`[smrt] ${pkgName}: no SMRT manifest resolved`);\n continue;\n }\n\n try {\n const manifestContent = readFileSync(manifestPath, 'utf-8');\n const manifest: SmartObjectManifest = JSON.parse(manifestContent);\n\n // Extract all class names from this package\n const foundClassNames: string[] = [];\n for (const objDef of Object.values(manifest.objects)) {\n if (objDef.className) {\n const className = objDef.className;\n foundClassNames.push(className);\n externalBaseClasses.push(className);\n }\n }\n\n logger.info(\n `[smrt] ${pkgName}: found ${foundClassNames.length} class(es) - ${foundClassNames.join(', ')}`,\n );\n } catch (error) {\n logger.info(\n `[smrt] ${pkgName}: manifest not found or invalid - ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n return externalBaseClasses;\n }\n\n /**\n * Read package.json from current working directory\n */\n private readPackageJson(): PackageInfo {\n try {\n const packageJsonPath = resolve(process.cwd(), 'package.json');\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n return {\n name: packageJson.name,\n version: packageJson.version,\n };\n } catch (error) {\n return {};\n }\n }\n\n /**\n * Create empty manifest when no files found\n */\n private createEmptyManifest(\n options: ManifestBuilderOptions,\n ): SmartObjectManifest {\n const manifest: SmartObjectManifest = {\n version: '1.0.0',\n timestamp: Date.now(),\n objects: {},\n moduleType: options.moduleType || 'smrt',\n };\n\n if (options.discoverExternalPackages) {\n manifest.smrtDependencies = discoverSmrtPackages();\n }\n\n if (options.injectPackageInfo) {\n const packageInfo = this.readPackageJson();\n if (packageInfo.name) {\n manifest.packageName = packageInfo.name;\n }\n }\n\n return manifest;\n }\n}\n"],"mappings":"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"}