@objectstack/rest 4.0.3 → 4.0.5

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/route-manager.ts","../src/rest-server.ts","../src/rest-api-plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n// REST Server\nexport { RestServer } from './rest-server.js';\n\n// Route Management\nexport { RouteManager, RouteGroupBuilder } from './route-manager.js';\nexport type { RouteEntry } from './route-manager.js';\n\n// REST API Plugin\nexport { createRestApiPlugin } from './rest-api-plugin.js';\nexport type { RestApiPluginConfig } from './rest-api-plugin.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { RouteHandler, IHttpServer } from '@objectstack/core';\nimport { System, Shared } from '@objectstack/spec';\nimport { z } from 'zod';\n\ntype RouteHandlerMetadata = System.RouteHandlerMetadata;\ntype HttpMethod = z.infer<typeof Shared.HttpMethod>;\n\n/**\n * Route Entry\n * Internal representation of registered routes\n */\nexport interface RouteEntry {\n method: HttpMethod;\n path: string;\n handler: RouteHandler;\n metadata?: RouteHandlerMetadata['metadata'];\n security?: RouteHandlerMetadata['security'];\n}\n\n/**\n * RouteManager\n * \n * Manages route registration and organization for HTTP servers.\n * Provides:\n * - Route registration with metadata\n * - Route lookup and querying\n * - Bulk route registration\n * - Route grouping by prefix\n * \n * @example\n * const manager = new RouteManager(server);\n * \n * // Register individual route\n * manager.register({\n * method: 'GET',\n * path: '/api/users/:id',\n * handler: getUserHandler,\n * metadata: {\n * summary: 'Get user by ID',\n * tags: ['users']\n * }\n * });\n * \n * // Register route group\n * manager.group('/api/users', (group) => {\n * group.get('/', listUsersHandler);\n * group.post('/', createUserHandler);\n * group.get('/:id', getUserHandler);\n * });\n */\nexport class RouteManager {\n private server: IHttpServer;\n private routes: Map<string, RouteEntry>;\n \n constructor(server: IHttpServer) {\n this.server = server;\n this.routes = new Map();\n }\n \n /**\n * Register a route\n * @param entry - Route entry with method, path, handler, and metadata\n */\n register(entry: Omit<RouteEntry, 'handler'> & { handler: RouteHandler | string }): void {\n // Validate handler type - string handlers not yet supported\n if (typeof entry.handler === 'string') {\n throw new Error(\n `String-based route handlers are not supported yet. ` +\n `Received handler identifier \"${entry.handler}\". ` +\n `Please provide a RouteHandler function instead.`\n );\n }\n \n const handler: RouteHandler = entry.handler;\n \n const routeEntry: RouteEntry = {\n method: entry.method,\n path: entry.path,\n handler,\n metadata: entry.metadata,\n security: entry.security,\n };\n \n const key = this.getRouteKey(entry.method, entry.path);\n this.routes.set(key, routeEntry);\n \n // Register with underlying server\n this.registerWithServer(routeEntry);\n }\n \n /**\n * Register multiple routes\n * @param entries - Array of route entries\n */\n registerMany(entries: Array<Omit<RouteEntry, 'handler'> & { handler: RouteHandler | string }>): void {\n entries.forEach(entry => this.register(entry));\n }\n \n /**\n * Unregister a route\n * @param method - HTTP method\n * @param path - Route path\n */\n unregister(method: HttpMethod, path: string): void {\n const key = this.getRouteKey(method, path);\n this.routes.delete(key);\n // Note: Most server frameworks don't support unregistering routes at runtime\n // This just removes it from our registry\n }\n \n /**\n * Get route by method and path\n * @param method - HTTP method\n * @param path - Route path\n */\n get(method: HttpMethod, path: string): RouteEntry | undefined {\n const key = this.getRouteKey(method, path);\n return this.routes.get(key);\n }\n \n /**\n * Get all routes\n */\n getAll(): RouteEntry[] {\n return Array.from(this.routes.values());\n }\n \n /**\n * Get routes by method\n * @param method - HTTP method\n */\n getByMethod(method: HttpMethod): RouteEntry[] {\n return this.getAll().filter(route => route.method === method);\n }\n \n /**\n * Get routes by path prefix\n * @param prefix - Path prefix\n */\n getByPrefix(prefix: string): RouteEntry[] {\n return this.getAll().filter(route => route.path.startsWith(prefix));\n }\n \n /**\n * Get routes by tag\n * @param tag - Tag name\n */\n getByTag(tag: string): RouteEntry[] {\n return this.getAll().filter(route => \n route.metadata?.tags?.includes(tag)\n );\n }\n \n /**\n * Create a route group with common prefix\n * @param prefix - Common path prefix\n * @param configure - Function to configure routes in the group\n */\n group(prefix: string, configure: (group: RouteGroupBuilder) => void): void {\n const builder = new RouteGroupBuilder(this, prefix);\n configure(builder);\n }\n \n /**\n * Get route count\n */\n count(): number {\n return this.routes.size;\n }\n \n /**\n * Clear all routes\n */\n clear(): void {\n this.routes.clear();\n }\n \n /**\n * Get route key for storage\n */\n private getRouteKey(method: HttpMethod, path: string): string {\n return `${method}:${path}`;\n }\n \n /**\n * Register route with underlying server\n */\n private registerWithServer(entry: RouteEntry): void {\n const { method, path, handler } = entry;\n \n switch (method) {\n case 'GET':\n this.server.get(path, handler);\n break;\n case 'POST':\n this.server.post(path, handler);\n break;\n case 'PUT':\n this.server.put(path, handler);\n break;\n case 'DELETE':\n this.server.delete(path, handler);\n break;\n case 'PATCH':\n this.server.patch(path, handler);\n break;\n default:\n throw new Error(`Unsupported HTTP method: ${method}`);\n }\n }\n}\n\n/**\n * RouteGroupBuilder\n * \n * Builder for creating route groups with common prefix\n */\nexport class RouteGroupBuilder {\n private manager: RouteManager;\n private prefix: string;\n \n constructor(manager: RouteManager, prefix: string) {\n this.manager = manager;\n this.prefix = prefix;\n }\n \n /**\n * Register GET route in group\n */\n get(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this {\n this.manager.register({\n method: 'GET',\n path: this.resolvePath(path),\n handler,\n metadata,\n });\n return this;\n }\n \n /**\n * Register POST route in group\n */\n post(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this {\n this.manager.register({\n method: 'POST',\n path: this.resolvePath(path),\n handler,\n metadata,\n });\n return this;\n }\n \n /**\n * Register PUT route in group\n */\n put(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this {\n this.manager.register({\n method: 'PUT',\n path: this.resolvePath(path),\n handler,\n metadata,\n });\n return this;\n }\n \n /**\n * Register PATCH route in group\n */\n patch(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this {\n this.manager.register({\n method: 'PATCH',\n path: this.resolvePath(path),\n handler,\n metadata,\n });\n return this;\n }\n \n /**\n * Register DELETE route in group\n */\n delete(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this {\n this.manager.register({\n method: 'DELETE',\n path: this.resolvePath(path),\n handler,\n metadata,\n });\n return this;\n }\n \n /**\n * Resolve full path with prefix\n */\n private resolvePath(path: string): string {\n // Normalize slashes\n const normalizedPrefix = this.prefix.endsWith('/') \n ? this.prefix.slice(0, -1) \n : this.prefix;\n const normalizedPath = path.startsWith('/') \n ? path \n : '/' + path;\n \n return normalizedPrefix + normalizedPath;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { IHttpServer } from '@objectstack/core';\nimport { RouteManager } from './route-manager.js';\nimport { RestServerConfig, RestApiConfig, CrudEndpointsConfig, MetadataEndpointsConfig, BatchEndpointsConfig, RouteGenerationConfig } from '@objectstack/spec/api';\nimport { ObjectStackProtocol } from '@objectstack/spec/api';\n\n/**\n * Normalized REST Server Configuration\n * All nested properties are required after normalization\n */\ntype NormalizedRestServerConfig = {\n api: {\n version: string;\n basePath: string;\n apiPath: string | undefined;\n enableCrud: boolean;\n enableMetadata: boolean;\n enableUi: boolean;\n enableBatch: boolean;\n enableDiscovery: boolean;\n documentation: RestApiConfig['documentation'];\n responseFormat: RestApiConfig['responseFormat'];\n };\n crud: {\n operations: {\n create: boolean;\n read: boolean;\n update: boolean;\n delete: boolean;\n list: boolean;\n };\n patterns: CrudEndpointsConfig['patterns'];\n dataPrefix: string;\n objectParamStyle: 'path' | 'query';\n };\n metadata: {\n prefix: string;\n enableCache: boolean;\n cacheTtl: number;\n endpoints: {\n types: boolean;\n items: boolean;\n item: boolean;\n schema: boolean;\n };\n };\n batch: {\n maxBatchSize: number;\n enableBatchEndpoint: boolean;\n operations: {\n createMany: boolean;\n updateMany: boolean;\n deleteMany: boolean;\n upsertMany: boolean;\n };\n defaultAtomic: boolean;\n };\n routes: {\n includeObjects: string[] | undefined;\n excludeObjects: string[] | undefined;\n nameTransform: 'none' | 'plural' | 'kebab-case' | 'camelCase';\n overrides: RouteGenerationConfig['overrides'];\n };\n};\n\n/**\n * RestServer\n * \n * Provides automatic REST API endpoint generation for ObjectStack.\n * Generates standard RESTful CRUD endpoints, metadata endpoints, and batch operations\n * based on the configured protocol provider.\n * \n * Features:\n * - Automatic CRUD endpoint generation (GET, POST, PUT, PATCH, DELETE)\n * - Metadata API endpoints (/meta)\n * - Batch operation endpoints (/batch, /createMany, /updateMany, /deleteMany)\n * - Discovery endpoint\n * - Configurable path prefixes and patterns\n * \n * @example\n * const restServer = new RestServer(httpServer, protocolProvider, {\n * api: {\n * version: 'v1',\n * basePath: '/api'\n * },\n * crud: {\n * dataPrefix: '/data'\n * }\n * });\n * \n * restServer.registerRoutes();\n */\nexport class RestServer {\n private protocol: ObjectStackProtocol;\n private config: NormalizedRestServerConfig;\n private routeManager: RouteManager;\n \n constructor(\n server: IHttpServer, \n protocol: ObjectStackProtocol, \n config: RestServerConfig = {}\n ) {\n this.protocol = protocol;\n this.config = this.normalizeConfig(config);\n this.routeManager = new RouteManager(server);\n }\n \n /**\n * Normalize configuration with defaults\n */\n private normalizeConfig(config: RestServerConfig): NormalizedRestServerConfig {\n const api = (config.api ?? {}) as Partial<RestApiConfig>;\n const crud = (config.crud ?? {}) as Partial<CrudEndpointsConfig>;\n const metadata = (config.metadata ?? {}) as Partial<MetadataEndpointsConfig>;\n const batch = (config.batch ?? {}) as Partial<BatchEndpointsConfig>;\n const routes = (config.routes ?? {}) as Partial<RouteGenerationConfig>;\n \n return {\n api: {\n version: api.version ?? 'v1',\n basePath: api.basePath ?? '/api',\n apiPath: api.apiPath,\n enableCrud: api.enableCrud ?? true,\n enableMetadata: api.enableMetadata ?? true,\n enableUi: api.enableUi ?? true,\n enableBatch: api.enableBatch ?? true,\n enableDiscovery: api.enableDiscovery ?? true,\n documentation: api.documentation,\n responseFormat: api.responseFormat,\n },\n crud: {\n operations: crud.operations ?? {\n create: true,\n read: true,\n update: true,\n delete: true,\n list: true,\n },\n patterns: crud.patterns,\n dataPrefix: crud.dataPrefix ?? '/data',\n objectParamStyle: crud.objectParamStyle ?? 'path',\n },\n metadata: {\n prefix: metadata.prefix ?? '/meta',\n enableCache: metadata.enableCache ?? true,\n cacheTtl: metadata.cacheTtl ?? 3600,\n endpoints: metadata.endpoints ?? {\n types: true,\n items: true,\n item: true,\n schema: true,\n },\n },\n batch: {\n maxBatchSize: batch.maxBatchSize ?? 200,\n enableBatchEndpoint: batch.enableBatchEndpoint ?? true,\n operations: batch.operations ?? {\n createMany: true,\n updateMany: true,\n deleteMany: true,\n upsertMany: true,\n },\n defaultAtomic: batch.defaultAtomic ?? true,\n },\n routes: {\n includeObjects: routes.includeObjects,\n excludeObjects: routes.excludeObjects,\n nameTransform: routes.nameTransform ?? 'none',\n overrides: routes.overrides,\n },\n };\n }\n \n /**\n * Get the full API base path\n */\n private getApiBasePath(): string {\n const { api } = this.config;\n return api.apiPath ?? `${api.basePath}/${api.version}`;\n }\n \n /**\n * Register all REST API routes\n */\n registerRoutes(): void {\n const basePath = this.getApiBasePath();\n \n // Discovery endpoint\n if (this.config.api.enableDiscovery) {\n this.registerDiscoveryEndpoints(basePath);\n }\n \n // Metadata endpoints\n if (this.config.api.enableMetadata) {\n this.registerMetadataEndpoints(basePath);\n }\n\n // UI endpoints\n if (this.config.api.enableUi) {\n this.registerUiEndpoints(basePath);\n }\n \n // CRUD endpoints\n if (this.config.api.enableCrud) {\n this.registerCrudEndpoints(basePath);\n }\n \n // Batch endpoints\n if (this.config.api.enableBatch) {\n this.registerBatchEndpoints(basePath);\n }\n }\n \n /**\n * Register discovery endpoints\n */\n private registerDiscoveryEndpoints(basePath: string): void {\n const discoveryHandler = async (_req: any, res: any) => {\n try {\n const discovery = await this.protocol.getDiscovery();\n \n // Override discovery information with actual server configuration\n discovery.version = this.config.api.version;\n \n if (discovery.routes) {\n // Ensure routes match the actual mounted paths\n if (this.config.api.enableCrud) {\n discovery.routes.data = `${basePath}${this.config.crud.dataPrefix}`;\n }\n \n if (this.config.api.enableMetadata) {\n discovery.routes.metadata = `${basePath}${this.config.metadata.prefix}`;\n }\n\n if (this.config.api.enableUi) {\n discovery.routes.ui = `${basePath}/ui`;\n }\n\n // Align auth route with the versioned base path if present\n if (discovery.routes.auth) {\n discovery.routes.auth = `${basePath}/auth`;\n }\n }\n\n res.json(discovery);\n } catch (error: any) {\n res.status(500).json({ error: error.message });\n }\n };\n\n // Register at basePath (e.g. /api/v1)\n this.routeManager.register({\n method: 'GET',\n path: basePath,\n handler: discoveryHandler,\n metadata: {\n summary: 'Get API discovery information',\n tags: ['discovery'],\n },\n });\n\n // Register at basePath/discovery (e.g. /api/v1/discovery)\n this.routeManager.register({\n method: 'GET',\n path: `${basePath}/discovery`,\n handler: discoveryHandler,\n metadata: {\n summary: 'Get API discovery information',\n tags: ['discovery'],\n },\n });\n }\n \n /**\n * Register metadata endpoints\n */\n private registerMetadataEndpoints(basePath: string): void {\n const { metadata } = this.config;\n const metaPath = `${basePath}${metadata.prefix}`;\n \n // GET /meta - List all metadata types\n if (metadata.endpoints.types !== false) {\n this.routeManager.register({\n method: 'GET',\n path: metaPath,\n handler: async (_req: any, res: any) => {\n try {\n const types = await this.protocol.getMetaTypes();\n res.json(types);\n } catch (error: any) {\n res.status(500).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'List all metadata types',\n tags: ['metadata'],\n },\n });\n }\n \n // GET /meta/:type - List items of a type\n if (metadata.endpoints.items !== false) {\n this.routeManager.register({\n method: 'GET',\n path: `${metaPath}/:type`,\n handler: async (req: any, res: any) => {\n try {\n const packageId = req.query?.package || undefined;\n const items = await this.protocol.getMetaItems({ type: req.params.type, packageId });\n res.json(items);\n } catch (error: any) {\n res.status(404).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'List metadata items of a type',\n tags: ['metadata'],\n },\n });\n }\n \n // GET /meta/:type/:name - Get specific item\n if (metadata.endpoints.item !== false) {\n this.routeManager.register({\n method: 'GET',\n path: `${metaPath}/:type/:name`,\n handler: async (req: any, res: any) => {\n try {\n // Check if cached version is available\n if (metadata.enableCache && this.protocol.getMetaItemCached) {\n const cacheRequest = {\n ifNoneMatch: req.headers['if-none-match'] as string,\n ifModifiedSince: req.headers['if-modified-since'] as string,\n };\n \n const result = await this.protocol.getMetaItemCached({\n type: req.params.type,\n name: req.params.name,\n cacheRequest\n });\n \n if (result.notModified) {\n res.status(304).send();\n return;\n }\n \n // Set cache headers\n if (result.etag) {\n const etagValue = result.etag.weak \n ? `W/\"${result.etag.value}\"` \n : `\"${result.etag.value}\"`;\n res.header('ETag', etagValue);\n }\n if (result.lastModified) {\n res.header('Last-Modified', new Date(result.lastModified).toUTCString());\n }\n if (result.cacheControl) {\n const directives = result.cacheControl.directives.join(', ');\n const maxAge = result.cacheControl.maxAge \n ? `, max-age=${result.cacheControl.maxAge}` \n : '';\n res.header('Cache-Control', directives + maxAge);\n }\n \n res.json(result.data);\n } else {\n // Non-cached version\n const packageId = req.query?.package || undefined;\n const item = await this.protocol.getMetaItem({ type: req.params.type, name: req.params.name, packageId });\n res.json(item);\n }\n } catch (error: any) {\n res.status(404).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Get specific metadata item',\n tags: ['metadata'],\n },\n });\n }\n\n // PUT /meta/:type/:name - Save metadata item\n // We always register this route, but return 501 if protocol doesn't support it\n // This makes it discoverable even if not implemented\n this.routeManager.register({\n method: 'PUT',\n path: `${metaPath}/:type/:name`,\n handler: async (req: any, res: any) => {\n try {\n if (!this.protocol.saveMetaItem) {\n res.status(501).json({ error: 'Save operation not supported by protocol implementation' });\n return;\n }\n\n const result = await this.protocol.saveMetaItem({\n type: req.params.type,\n name: req.params.name,\n item: req.body\n });\n res.json(result);\n } catch (error: any) {\n res.status(400).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Save specific metadata item',\n tags: ['metadata'],\n },\n });\n }\n\n /**\n * Register UI endpoints\n */\n private registerUiEndpoints(basePath: string): void {\n const uiPath = `${basePath}/ui`;\n \n // GET /ui/view/:object/:type - Resolve view for object\n this.routeManager.register({\n method: 'GET',\n path: `${uiPath}/view/:object/:type`,\n handler: async (req: any, res: any) => {\n try {\n if (this.protocol.getUiView) {\n const view = await this.protocol.getUiView({ \n object: req.params.object, \n type: req.params.type as any \n });\n res.json(view);\n } else {\n res.status(501).json({ error: 'UI View resolution not supported by protocol implementation' });\n }\n } catch (error: any) {\n res.status(404).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Resolve UI View for object',\n tags: ['ui'],\n },\n });\n }\n \n /**\n * Register CRUD endpoints for data operations\n */\n private registerCrudEndpoints(basePath: string): void {\n const { crud } = this.config;\n const dataPath = `${basePath}${crud.dataPrefix}`;\n \n const operations = crud.operations;\n \n // GET /data/:object - List/query records\n if (operations.list) {\n this.routeManager.register({\n method: 'GET',\n path: `${dataPath}/:object`,\n handler: async (req: any, res: any) => {\n try {\n const result = await this.protocol.findData({\n object: req.params.object, \n query: req.query\n });\n res.json(result);\n } catch (error: any) {\n res.status(400).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Query records',\n tags: ['data', 'crud'],\n },\n });\n }\n \n // GET /data/:object/:id - Get single record\n if (operations.read) {\n this.routeManager.register({\n method: 'GET',\n path: `${dataPath}/:object/:id`,\n handler: async (req: any, res: any) => {\n try {\n const { select, expand } = req.query || {};\n const result = await this.protocol.getData({\n object: req.params.object, \n id: req.params.id,\n ...(select != null ? { select } : {}),\n ...(expand != null ? { expand } : {}),\n });\n res.json(result);\n } catch (error: any) {\n res.status(404).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Get record by ID',\n tags: ['data', 'crud'],\n },\n });\n }\n \n // POST /data/:object - Create record\n if (operations.create) {\n this.routeManager.register({\n method: 'POST',\n path: `${dataPath}/:object`,\n handler: async (req: any, res: any) => {\n try {\n const result = await this.protocol.createData({\n object: req.params.object, \n data: req.body\n });\n res.status(201).json(result);\n } catch (error: any) {\n res.status(400).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Create record',\n tags: ['data', 'crud'],\n },\n });\n }\n \n // PATCH /data/:object/:id - Update record\n if (operations.update) {\n this.routeManager.register({\n method: 'PATCH',\n path: `${dataPath}/:object/:id`,\n handler: async (req: any, res: any) => {\n try {\n const result = await this.protocol.updateData({\n object: req.params.object,\n id: req.params.id,\n data: req.body\n });\n res.json(result);\n } catch (error: any) {\n res.status(400).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Update record',\n tags: ['data', 'crud'],\n },\n });\n }\n \n // DELETE /data/:object/:id - Delete record\n if (operations.delete) {\n this.routeManager.register({\n method: 'DELETE',\n path: `${dataPath}/:object/:id`,\n handler: async (req: any, res: any) => {\n try {\n const result = await this.protocol.deleteData({\n object: req.params.object, \n id: req.params.id\n });\n res.json(result);\n } catch (error: any) {\n res.status(400).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Delete record',\n tags: ['data', 'crud'],\n },\n });\n }\n }\n \n /**\n * Register batch operation endpoints\n */\n private registerBatchEndpoints(basePath: string): void {\n const { crud, batch } = this.config;\n const dataPath = `${basePath}${crud.dataPrefix}`;\n \n const operations = batch.operations;\n \n // POST /data/:object/batch - Generic batch endpoint\n if (batch.enableBatchEndpoint && this.protocol.batchData) {\n this.routeManager.register({\n method: 'POST',\n path: `${dataPath}/:object/batch`,\n handler: async (req: any, res: any) => {\n try {\n const result = await this.protocol.batchData!({\n object: req.params.object, \n request: req.body\n });\n res.json(result);\n } catch (error: any) {\n res.status(400).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Batch operations',\n tags: ['data', 'batch'],\n },\n });\n }\n \n // POST /data/:object/createMany - Bulk create\n if (operations.createMany && this.protocol.createManyData) {\n this.routeManager.register({\n method: 'POST',\n path: `${dataPath}/:object/createMany`,\n handler: async (req: any, res: any) => {\n try {\n const result = await this.protocol.createManyData!({\n object: req.params.object,\n records: req.body || []\n });\n res.status(201).json(result);\n } catch (error: any) {\n res.status(400).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Create multiple records',\n tags: ['data', 'batch'],\n },\n });\n }\n \n // POST /data/:object/updateMany - Bulk update\n if (operations.updateMany && this.protocol.updateManyData) {\n this.routeManager.register({\n method: 'POST',\n path: `${dataPath}/:object/updateMany`,\n handler: async (req: any, res: any) => {\n try {\n const result = await this.protocol.updateManyData!({\n object: req.params.object,\n ...req.body\n });\n res.json(result);\n } catch (error: any) {\n res.status(400).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Update multiple records',\n tags: ['data', 'batch'],\n },\n });\n }\n \n // POST /data/:object/deleteMany - Bulk delete\n if (operations.deleteMany && this.protocol.deleteManyData) {\n this.routeManager.register({\n method: 'POST',\n path: `${dataPath}/:object/deleteMany`,\n handler: async (req: any, res: any) => {\n try {\n const result = await this.protocol.deleteManyData!({\n object: req.params.object, \n ...req.body\n });\n res.json(result);\n } catch (error: any) {\n res.status(400).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Delete multiple records',\n tags: ['data', 'batch'],\n },\n });\n }\n }\n\n \n /**\n * Get the route manager\n */\n getRouteManager(): RouteManager {\n return this.routeManager;\n }\n \n /**\n * Get all registered routes\n */\n getRoutes() {\n return this.routeManager.getAll();\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Plugin, PluginContext, IHttpServer } from '@objectstack/core';\nimport { RestServer } from './rest-server.js';\nimport { ObjectStackProtocol, RestServerConfig } from '@objectstack/spec/api';\n\nexport interface RestApiPluginConfig {\n serverServiceName?: string;\n protocolServiceName?: string;\n api?: RestServerConfig;\n}\n\n/**\n * REST API Plugin\n * \n * Responsibilities:\n * 1. Consumes 'http.server' (or configured service)\n * 2. Consumes 'protocol' (ObjectStackProtocol)\n * 3. Instantiates RestServer to auto-generate routes\n */\nexport function createRestApiPlugin(config: RestApiPluginConfig = {}): Plugin {\n return {\n name: 'com.objectstack.rest.api',\n version: '1.0.0',\n \n init: async (_ctx: PluginContext) => {\n // No service registration, this is a consumer plugin\n },\n \n start: async (ctx: PluginContext) => {\n const serverService = config.serverServiceName || 'http.server';\n const protocolService = config.protocolServiceName || 'protocol';\n \n let server: IHttpServer | undefined;\n let protocol: ObjectStackProtocol | undefined;\n\n try {\n server = ctx.getService<IHttpServer>(serverService);\n } catch (e) {\n // Ignore missing service\n }\n\n try {\n protocol = ctx.getService<ObjectStackProtocol>(protocolService);\n } catch (e) {\n // Ignore missing service\n }\n \n if (!server) {\n ctx.logger.warn(`RestApiPlugin: HTTP Server service '${serverService}' not found. REST routes skipped.`);\n return;\n }\n \n if (!protocol) {\n ctx.logger.warn(`RestApiPlugin: Protocol service '${protocolService}' not found. REST routes skipped.`);\n return;\n }\n \n ctx.logger.info('Hydrating REST API from Protocol...');\n \n try {\n const restServer = new RestServer(server, protocol, config.api as any);\n restServer.registerRoutes();\n \n ctx.logger.info('REST API successfully registered');\n } catch (err: any) {\n ctx.logger.error('Failed to register REST API routes', { error: err.message } as any);\n throw err;\n }\n }\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/route-manager.ts","../src/rest-server.ts","../src/package-routes.ts","../src/rest-api-plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n// REST Server\nexport { RestServer } from './rest-server.js';\n\n// Route Management\nexport { RouteManager, RouteGroupBuilder } from './route-manager.js';\nexport type { RouteEntry } from './route-manager.js';\n\n// REST API Plugin\nexport { createRestApiPlugin } from './rest-api-plugin.js';\nexport type { RestApiPluginConfig } from './rest-api-plugin.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { RouteHandler, IHttpServer } from '@objectstack/core';\nimport * as System from '@objectstack/spec/system';\nimport * as Shared from '@objectstack/spec/shared';\nimport { z } from 'zod';\n\ntype RouteHandlerMetadata = System.RouteHandlerMetadata;\ntype HttpMethod = z.infer<typeof Shared.HttpMethod>;\n\n/**\n * Route Entry\n * Internal representation of registered routes\n */\nexport interface RouteEntry {\n method: HttpMethod;\n path: string;\n handler: RouteHandler;\n metadata?: RouteHandlerMetadata['metadata'];\n security?: RouteHandlerMetadata['security'];\n}\n\n/**\n * RouteManager\n * \n * Manages route registration and organization for HTTP servers.\n * Provides:\n * - Route registration with metadata\n * - Route lookup and querying\n * - Bulk route registration\n * - Route grouping by prefix\n * \n * @example\n * const manager = new RouteManager(server);\n * \n * // Register individual route\n * manager.register({\n * method: 'GET',\n * path: '/api/users/:id',\n * handler: getUserHandler,\n * metadata: {\n * summary: 'Get user by ID',\n * tags: ['users']\n * }\n * });\n * \n * // Register route group\n * manager.group('/api/users', (group) => {\n * group.get('/', listUsersHandler);\n * group.post('/', createUserHandler);\n * group.get('/:id', getUserHandler);\n * });\n */\nexport class RouteManager {\n private server: IHttpServer;\n private routes: Map<string, RouteEntry>;\n \n constructor(server: IHttpServer) {\n this.server = server;\n this.routes = new Map();\n }\n \n /**\n * Register a route\n * @param entry - Route entry with method, path, handler, and metadata\n */\n register(entry: Omit<RouteEntry, 'handler'> & { handler: RouteHandler | string }): void {\n // Validate handler type - string handlers not yet supported\n if (typeof entry.handler === 'string') {\n throw new Error(\n `String-based route handlers are not supported yet. ` +\n `Received handler identifier \"${entry.handler}\". ` +\n `Please provide a RouteHandler function instead.`\n );\n }\n \n const handler: RouteHandler = entry.handler;\n \n const routeEntry: RouteEntry = {\n method: entry.method,\n path: entry.path,\n handler,\n metadata: entry.metadata,\n security: entry.security,\n };\n \n const key = this.getRouteKey(entry.method, entry.path);\n this.routes.set(key, routeEntry);\n \n // Register with underlying server\n this.registerWithServer(routeEntry);\n }\n \n /**\n * Register multiple routes\n * @param entries - Array of route entries\n */\n registerMany(entries: Array<Omit<RouteEntry, 'handler'> & { handler: RouteHandler | string }>): void {\n entries.forEach(entry => this.register(entry));\n }\n \n /**\n * Unregister a route\n * @param method - HTTP method\n * @param path - Route path\n */\n unregister(method: HttpMethod, path: string): void {\n const key = this.getRouteKey(method, path);\n this.routes.delete(key);\n // Note: Most server frameworks don't support unregistering routes at runtime\n // This just removes it from our registry\n }\n \n /**\n * Get route by method and path\n * @param method - HTTP method\n * @param path - Route path\n */\n get(method: HttpMethod, path: string): RouteEntry | undefined {\n const key = this.getRouteKey(method, path);\n return this.routes.get(key);\n }\n \n /**\n * Get all routes\n */\n getAll(): RouteEntry[] {\n return Array.from(this.routes.values());\n }\n \n /**\n * Get routes by method\n * @param method - HTTP method\n */\n getByMethod(method: HttpMethod): RouteEntry[] {\n return this.getAll().filter(route => route.method === method);\n }\n \n /**\n * Get routes by path prefix\n * @param prefix - Path prefix\n */\n getByPrefix(prefix: string): RouteEntry[] {\n return this.getAll().filter(route => route.path.startsWith(prefix));\n }\n \n /**\n * Get routes by tag\n * @param tag - Tag name\n */\n getByTag(tag: string): RouteEntry[] {\n return this.getAll().filter(route => \n route.metadata?.tags?.includes(tag)\n );\n }\n \n /**\n * Create a route group with common prefix\n * @param prefix - Common path prefix\n * @param configure - Function to configure routes in the group\n */\n group(prefix: string, configure: (group: RouteGroupBuilder) => void): void {\n const builder = new RouteGroupBuilder(this, prefix);\n configure(builder);\n }\n \n /**\n * Get route count\n */\n count(): number {\n return this.routes.size;\n }\n \n /**\n * Clear all routes\n */\n clear(): void {\n this.routes.clear();\n }\n \n /**\n * Get route key for storage\n */\n private getRouteKey(method: HttpMethod, path: string): string {\n return `${method}:${path}`;\n }\n \n /**\n * Register route with underlying server\n */\n private registerWithServer(entry: RouteEntry): void {\n const { method, path, handler } = entry;\n \n switch (method) {\n case 'GET':\n this.server.get(path, handler);\n break;\n case 'POST':\n this.server.post(path, handler);\n break;\n case 'PUT':\n this.server.put(path, handler);\n break;\n case 'DELETE':\n this.server.delete(path, handler);\n break;\n case 'PATCH':\n this.server.patch(path, handler);\n break;\n default:\n throw new Error(`Unsupported HTTP method: ${method}`);\n }\n }\n}\n\n/**\n * RouteGroupBuilder\n * \n * Builder for creating route groups with common prefix\n */\nexport class RouteGroupBuilder {\n private manager: RouteManager;\n private prefix: string;\n \n constructor(manager: RouteManager, prefix: string) {\n this.manager = manager;\n this.prefix = prefix;\n }\n \n /**\n * Register GET route in group\n */\n get(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this {\n this.manager.register({\n method: 'GET',\n path: this.resolvePath(path),\n handler,\n metadata,\n });\n return this;\n }\n \n /**\n * Register POST route in group\n */\n post(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this {\n this.manager.register({\n method: 'POST',\n path: this.resolvePath(path),\n handler,\n metadata,\n });\n return this;\n }\n \n /**\n * Register PUT route in group\n */\n put(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this {\n this.manager.register({\n method: 'PUT',\n path: this.resolvePath(path),\n handler,\n metadata,\n });\n return this;\n }\n \n /**\n * Register PATCH route in group\n */\n patch(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this {\n this.manager.register({\n method: 'PATCH',\n path: this.resolvePath(path),\n handler,\n metadata,\n });\n return this;\n }\n \n /**\n * Register DELETE route in group\n */\n delete(path: string, handler: RouteHandler, metadata?: RouteHandlerMetadata['metadata']): this {\n this.manager.register({\n method: 'DELETE',\n path: this.resolvePath(path),\n handler,\n metadata,\n });\n return this;\n }\n \n /**\n * Resolve full path with prefix\n */\n private resolvePath(path: string): string {\n // Normalize slashes\n const normalizedPrefix = this.prefix.endsWith('/') \n ? this.prefix.slice(0, -1) \n : this.prefix;\n const normalizedPath = path.startsWith('/') \n ? path \n : '/' + path;\n \n return normalizedPrefix + normalizedPath;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { IHttpServer } from '@objectstack/core';\nimport { RouteManager } from './route-manager.js';\nimport { RestServerConfig, RestApiConfig, CrudEndpointsConfig, MetadataEndpointsConfig, BatchEndpointsConfig, RouteGenerationConfig } from '@objectstack/spec/api';\nimport { ObjectStackProtocol } from '@objectstack/spec/api';\n\n// Node-safe logger — avoids importing 'console' which is absent from ES2020 lib typings.\nconst logError = (...args: unknown[]) => (globalThis as any).console?.error(...args);\n\n/**\n * Map a data-layer error to a clean HTTP response. Unknown-object errors\n * (SQLite \"no such table\", PG \"relation does not exist\", protocol\n * \"object not found\", etc.) are surfaced as a 404 with `code: 'object_not_found'`\n * so clients can distinguish \"object isn't registered\" from real server\n * faults. Anything else becomes a 400 (bad request) preserving prior\n * behavior. Genuine 500s are still logged.\n *\n * `PermissionDeniedError` (thrown by `SecurityPlugin`) MUST be caught\n * before the unknown-object heuristic, otherwise its message —\n * \"[Security] Access denied: operation 'insert' on object 'sys_user' is\n * not permitted …\" — trips the `'<obj>' … not` substring check and\n * returns a misleading 404.\n */\nfunction mapDataError(error: any, object?: string): { status: number; body: Record<string, unknown> } {\n // Short-circuit: explicit security denial → 403. Match by `code` /\n // `name` to avoid pulling a runtime dependency on plugin-security.\n if (\n error?.code === 'PERMISSION_DENIED' ||\n error?.name === 'PermissionDeniedError' ||\n (typeof error?.message === 'string' && error.message.startsWith('[Security] Access denied'))\n ) {\n return {\n status: 403,\n body: {\n error: error?.message ?? 'Permission denied',\n code: 'PERMISSION_DENIED',\n ...(object ? { object } : {}),\n },\n };\n }\n const raw = String(error?.message ?? error ?? '');\n const lower = raw.toLowerCase();\n\n // ProjectKernelFactory: project missing database_url/driver — typically\n // means provisioning is in flight or the project record was never\n // fully provisioned. 503 (with Retry-After implied) is more accurate\n // than the default 400/500: clients can poll until the project is\n // active.\n if (\n raw.includes('[ProjectKernelFactory]') &&\n (lower.includes('missing database_url') || lower.includes('not found'))\n ) {\n const isProvisioning = lower.includes(\"status='provisioning'\") || lower.includes(\"status='pending'\");\n const isFailed = lower.includes(\"status='failed'\");\n return {\n status: isProvisioning ? 503 : isFailed ? 502 : 404,\n body: {\n error: raw,\n code: isProvisioning\n ? 'PROJECT_PROVISIONING'\n : isFailed\n ? 'PROJECT_PROVISIONING_FAILED'\n : 'PROJECT_NOT_FOUND',\n },\n };\n }\n\n const looksLikeUnknownObject =\n lower.includes('no such table') ||\n lower.includes('relation') && lower.includes('does not exist') ||\n lower.includes('table not found') ||\n lower.includes('unknown object') ||\n lower.includes('object not found') ||\n lower.includes('no driver available') ||\n (object !== undefined && lower.includes(`'${object.toLowerCase()}'`) && lower.includes('not'));\n if (looksLikeUnknownObject) {\n return {\n status: 404,\n body: {\n error: object ? `Object '${object}' is not registered` : 'Object not found',\n code: 'object_not_found',\n object,\n },\n };\n }\n return { status: 400, body: { error: raw || 'Bad request' } };\n}\n\n/**\n * Whether a mapped data-error status represents an *expected* client/lifecycle\n * outcome (and therefore shouldn't be logged as \"[REST] Unhandled error\").\n * - 403 PERMISSION_DENIED is a normal RBAC denial\n * - 404 unknown object / project not found is a normal client mistake\n * - 502/503 mean the underlying project is provisioning or failed; the\n * handler will emit the response and the operator can inspect\n * sys_project.metadata.provisioningError if needed.\n */\nfunction isExpectedDataStatus(status: number): boolean {\n return status === 403 || status === 404 || status === 502 || status === 503;\n}\n\n/**\n * Structural subset of `KernelManager` that RestServer needs in order to\n * resolve a per-project protocol at request time. Typed locally to avoid\n * an @objectstack/runtime → @objectstack/rest → @objectstack/runtime\n * package cycle.\n */\nexport interface RestKernelManager {\n getOrCreate(projectId: string): Promise<{\n getServiceAsync<T = unknown>(name: string): Promise<T>;\n }>;\n}\n\n/**\n * Normalized REST Server Configuration\n * All nested properties are required after normalization\n */\ntype NormalizedRestServerConfig = {\n api: {\n version: string;\n basePath: string;\n apiPath: string | undefined;\n enableCrud: boolean;\n enableMetadata: boolean;\n enableUi: boolean;\n enableBatch: boolean;\n enableDiscovery: boolean;\n enableProjectScoping: boolean;\n projectResolution: 'required' | 'optional' | 'auto';\n documentation: RestApiConfig['documentation'];\n responseFormat: RestApiConfig['responseFormat'];\n };\n crud: {\n operations: {\n create: boolean;\n read: boolean;\n update: boolean;\n delete: boolean;\n list: boolean;\n };\n patterns: CrudEndpointsConfig['patterns'];\n dataPrefix: string;\n objectParamStyle: 'path' | 'query';\n };\n metadata: {\n prefix: string;\n enableCache: boolean;\n cacheTtl: number;\n endpoints: {\n types: boolean;\n items: boolean;\n item: boolean;\n schema: boolean;\n };\n };\n batch: {\n maxBatchSize: number;\n enableBatchEndpoint: boolean;\n operations: {\n createMany: boolean;\n updateMany: boolean;\n deleteMany: boolean;\n upsertMany: boolean;\n };\n defaultAtomic: boolean;\n };\n routes: {\n includeObjects: string[] | undefined;\n excludeObjects: string[] | undefined;\n nameTransform: 'none' | 'plural' | 'kebab-case' | 'camelCase';\n overrides: RouteGenerationConfig['overrides'];\n };\n};\n\n/**\n * RestServer\n * \n * Provides automatic REST API endpoint generation for ObjectStack.\n * Generates standard RESTful CRUD endpoints, metadata endpoints, and batch operations\n * based on the configured protocol provider.\n * \n * Features:\n * - Automatic CRUD endpoint generation (GET, POST, PUT, PATCH, DELETE)\n * - Metadata API endpoints (/meta)\n * - Batch operation endpoints (/batch, /createMany, /updateMany, /deleteMany)\n * - Discovery endpoint\n * - Configurable path prefixes and patterns\n * \n * @example\n * const restServer = new RestServer(httpServer, protocolProvider, {\n * api: {\n * version: 'v1',\n * basePath: '/api'\n * },\n * crud: {\n * dataPrefix: '/data'\n * }\n * });\n * \n * restServer.registerRoutes();\n */\n/**\n * Minimal env registry shape consumed by the REST server for hostname →\n * projectId resolution and `X-Project-Id` header validation on unscoped\n * routes. Mirrors the surface of `EnvironmentDriverRegistry` defined in\n * `@objectstack/service-cloud`.\n */\nexport interface RestEnvRegistry {\n resolveByHostname(hostname: string): Promise<{ projectId: string } | null | undefined>;\n /**\n * Look up a project by id. Returns a truthy value (typically an\n * `IDataDriver`) when the project exists and is bound, `null` when\n * unknown. The REST server only uses the truthiness; it does not\n * touch the driver itself (the actual driver is loaded later via\n * `KernelManager.getOrCreate(projectId)`).\n */\n resolveById?(projectId: string): Promise<unknown | null>;\n}\n\nexport class RestServer {\n private protocol: ObjectStackProtocol;\n private config: NormalizedRestServerConfig;\n private routeManager: RouteManager;\n private kernelManager?: RestKernelManager;\n private envRegistry?: RestEnvRegistry;\n private defaultProjectIdProvider?: () => string | undefined;\n private authServiceProvider?: (projectId?: string) => Promise<any | undefined>;\n private objectQLProvider?: (projectId?: string) => Promise<any | undefined>;\n\n constructor(\n server: IHttpServer,\n protocol: ObjectStackProtocol,\n config: RestServerConfig = {},\n kernelManager?: RestKernelManager,\n envRegistry?: RestEnvRegistry,\n defaultProjectIdProvider?: () => string | undefined,\n authServiceProvider?: (projectId?: string) => Promise<any | undefined>,\n objectQLProvider?: (projectId?: string) => Promise<any | undefined>,\n ) {\n this.protocol = protocol;\n this.config = this.normalizeConfig(config);\n this.routeManager = new RouteManager(server);\n this.kernelManager = kernelManager;\n this.envRegistry = envRegistry;\n this.defaultProjectIdProvider = defaultProjectIdProvider;\n this.authServiceProvider = authServiceProvider;\n this.objectQLProvider = objectQLProvider;\n }\n\n /**\n * Resolve the protocol for a given request. When `projectId` is present\n * and a KernelManager is wired, fetch the per-project kernel's\n * `protocol` service so metadata / data / UI reads hit the project's\n * own registry and datastore.\n *\n * When `projectId` is absent on an unscoped route and an `envRegistry`\n * is wired (runtime mode), the resolution chain is:\n * 1. Hostname → projectId (`envRegistry.resolveByHostname`)\n * 2. `X-Project-Id` header → projectId (`envRegistry.resolveById`)\n * 3. Default-project fallback (`defaultProjectIdProvider`, set by\n * `createSingleProjectPlugin`)\n * 4. Control-plane protocol captured at boot.\n *\n * Special case: `projectId === 'platform'` is a reserved virtual id used\n * by Studio to address the control plane through the regular project\n * URL shape (`/projects/platform/...`). It is NOT a row in the projects\n * table, so we must never call `KernelManager.getOrCreate('platform')`.\n * Instead, return the control-plane protocol directly. This lets Studio\n * (and any other client) speak a single, uniform URL family without\n * duplicating route logic for the platform surface.\n */\n private async resolveProtocol(projectId?: string, req?: any): Promise<ObjectStackProtocol> {\n if (projectId === 'platform') return this.protocol;\n if (!projectId && req && this.envRegistry && this.kernelManager) {\n const host = this.extractHostname(req);\n if (host) {\n try {\n const result = await this.envRegistry.resolveByHostname(host);\n if (result?.projectId) projectId = result.projectId;\n } catch {\n // fall through to next strategy\n }\n }\n // 2. `X-Project-Id` request header → projectId. Lets clients\n // explicitly target a project when the URL is unscoped and\n // no hostname binding exists (e.g. a single shared origin\n // serving multiple compiled bundles via OS_PROJECT_ARTIFACTS).\n // We validate the id through the env registry to avoid\n // routing to a non-existent kernel.\n if (!projectId && typeof this.envRegistry.resolveById === 'function') {\n const headerVal = this.extractProjectIdHeader(req);\n if (headerVal) {\n try {\n const driver = await this.envRegistry.resolveById(headerVal);\n if (driver) projectId = headerVal;\n } catch {\n // fall through to default fallback\n }\n }\n }\n }\n // 3. Single-project default fallback. Registered by\n // `createSingleProjectPlugin()` so bare `/api/v1/data/...` URLs\n // (no `/projects/<id>` prefix, no hostname mapping, no header)\n // resolve to the lone project's kernel rather than the control\n // plane.\n if (!projectId && this.defaultProjectIdProvider) {\n try {\n const def = this.defaultProjectIdProvider();\n if (def) projectId = def;\n } catch { /* fall through */ }\n }\n if (!projectId || !this.kernelManager) return this.protocol;\n const kernel = await this.kernelManager.getOrCreate(projectId);\n return kernel.getServiceAsync<ObjectStackProtocol>('protocol');\n }\n\n /**\n * Resolve the i18n service for the request's project (or control plane\n * when no project id is in scope). Returns `undefined` when no service is\n * registered, so callers can short-circuit and skip translation rather\n * than failing.\n *\n * Mirrors `resolveProtocol`'s lookup chain: explicit `projectId` from the\n * route → kernel-managed `i18n` service. Control-plane / unscoped\n * requests intentionally return `undefined` because the platform kernel\n * does not own per-app translation bundles.\n */\n private async resolveI18nService(projectId?: string): Promise<any | undefined> {\n if (!projectId || projectId === 'platform' || !this.kernelManager) return undefined;\n try {\n const kernel = await this.kernelManager.getOrCreate(projectId);\n return await kernel.getServiceAsync<any>('i18n');\n } catch {\n return undefined;\n }\n }\n\n /**\n * Resolve the request's execution context (RBAC/RLS/FLS) by looking up\n * the better-auth session via the project's `auth` service. Returns\n * `undefined` for anonymous requests so callers can pass `context` as-is\n * to the protocol layer (the SecurityPlugin treats undefined as anon).\n */\n private async resolveExecCtx(projectId: string | undefined, req: any): Promise<any | undefined> {\n try {\n // Look up the auth service in the right kernel. For unscoped\n // single-project apps the kernelManager will hand us the lone\n // tenant kernel; for multi-project hosts we use the resolved\n // projectId.\n let authService: any;\n let kernel: any;\n if (projectId && projectId !== 'platform' && this.kernelManager) {\n kernel = await this.kernelManager.getOrCreate(projectId);\n authService = await kernel.getServiceAsync('auth').catch(() => undefined);\n }\n if (!authService && this.defaultProjectIdProvider && this.kernelManager) {\n try {\n const def = this.defaultProjectIdProvider();\n if (def) {\n kernel = await this.kernelManager.getOrCreate(def);\n authService = await kernel.getServiceAsync('auth').catch(() => undefined);\n }\n } catch { /* fall through */ }\n }\n // Single-kernel deployment fallback — no kernelManager, but\n // the plugin wired an `authServiceProvider` that hits the\n // local kernel directly.\n if (!authService && this.authServiceProvider) {\n authService = await this.authServiceProvider(projectId).catch(() => undefined);\n }\n if (!authService) return undefined;\n // The auth service may be the AuthManager wrapper (which exposes\n // `getApi()`) or the raw better-auth instance (which exposes\n // `.api` directly). Normalize to the raw API object.\n let api: any = authService.api;\n if (!api && typeof authService.getApi === 'function') {\n api = await authService.getApi();\n }\n if (!api?.getSession) return undefined;\n\n // better-auth's `getSession` requires a Web `Headers` instance\n // (it calls `headers.get('cookie')`). Adapter req.headers may\n // already be one, or a plain object — normalize.\n const rawHeaders: any = req?.headers;\n let headers: any;\n if (rawHeaders && typeof rawHeaders.get === 'function') {\n headers = rawHeaders;\n } else if (rawHeaders && typeof rawHeaders === 'object') {\n headers = new (globalThis as any).Headers();\n for (const [k, v] of Object.entries(rawHeaders)) {\n if (Array.isArray(v)) v.forEach((x) => headers.append(k, String(x)));\n else if (v != null) headers.set(k, String(v));\n }\n } else {\n return undefined;\n }\n\n const session = await api.getSession({ headers });\n if (!session?.user?.id) return undefined;\n const userId = session.user.id;\n const tenantId = session.session?.activeOrganizationId ?? undefined;\n const permissions: string[] = [];\n const roles: string[] = [];\n // Look up the link tables to surface roles + permission set names.\n // Skipping this lookup would silently ignore admin/role grants —\n // including the platform-admin promotion seeded by\n // `bootstrapPlatformAdmin` — and force every authenticated user\n // through the `member_default` fallback path.\n try {\n let ql: any;\n if (kernel) {\n ql = await kernel.getServiceAsync('objectql').catch(() => undefined);\n }\n if (!ql && this.objectQLProvider) {\n ql = await this.objectQLProvider(projectId).catch(() => undefined);\n }\n if (ql && typeof ql.find === 'function') {\n const sysOpts = { context: { isSystem: true } };\n const memberRows = await ql.find('sys_member', {\n where: tenantId ? { user_id: userId, organization_id: tenantId } : { user_id: userId },\n limit: 50,\n ...sysOpts,\n } as any).catch(() => []);\n for (const m of (memberRows ?? []) as any[]) {\n if (typeof m.role === 'string') {\n for (const r of m.role.split(',').map((s: string) => s.trim()).filter(Boolean)) {\n if (!roles.includes(r)) roles.push(r);\n }\n }\n }\n const upsRows = await ql.find('sys_user_permission_set', {\n where: { user_id: userId },\n limit: 100,\n ...sysOpts,\n } as any).catch(() => []);\n const psIds = new Set<string>();\n for (const r of (upsRows ?? []) as any[]) {\n const orgScope = r.organization_id ?? null;\n if (!orgScope || (tenantId && orgScope === tenantId)) {\n const pid = r.permission_set_id ?? r.permissionSetId;\n if (pid) psIds.add(pid);\n }\n }\n if (psIds.size > 0) {\n const psRows = await ql.find('sys_permission_set', {\n where: { id: { $in: Array.from(psIds) } },\n limit: 500,\n ...sysOpts,\n } as any).catch(() => []);\n for (const ps of (psRows ?? []) as any[]) {\n if (ps.name && !permissions.includes(ps.name)) permissions.push(ps.name);\n }\n }\n }\n } catch { /* fall through with empty perms */ }\n return {\n userId,\n tenantId,\n roles,\n permissions,\n isSystem: false,\n };\n } catch {\n return undefined;\n }\n }\n\n /**\n * Build a `TranslationBundle` (`Record<locale, TranslationData>`) from an\n * `II18nService` instance. Returns `undefined` when no locales are\n * registered so callers can avoid translation work.\n */\n private buildTranslationBundle(i18n: any): any | undefined {\n if (!i18n || typeof i18n.getLocales !== 'function' || typeof i18n.getTranslations !== 'function') {\n return undefined;\n }\n const locales: string[] = i18n.getLocales();\n if (!locales.length) return undefined;\n const bundle: Record<string, any> = {};\n for (const locale of locales) {\n const data = i18n.getTranslations(locale);\n if (data && typeof data === 'object') bundle[locale] = data;\n }\n return Object.keys(bundle).length ? bundle : undefined;\n }\n\n /**\n * Parse the highest-priority locale from an `Accept-Language` header.\n * Falls back to a `?locale=` query parameter, then to the i18n service's\n * default locale. Returns `undefined` when no preference is expressed\n * (callers will then return untranslated metadata).\n */\n private extractLocale(req: any, i18n?: any): string | undefined {\n const headers = req?.headers;\n let header: string | undefined;\n if (headers) {\n header = typeof headers.get === 'function'\n ? headers.get('accept-language') ?? undefined\n : headers['accept-language'] ?? headers['Accept-Language'];\n }\n if (typeof header === 'string' && header.length > 0) {\n const top = header.split(',')[0]?.split(';')[0]?.trim();\n if (top) return top;\n }\n const queryLocale = req?.query?.locale;\n if (typeof queryLocale === 'string' && queryLocale.length > 0) return queryLocale;\n if (i18n && typeof i18n.getDefaultLocale === 'function') {\n const def = i18n.getDefaultLocale();\n if (typeof def === 'string' && def.length > 0) return def;\n }\n return undefined;\n }\n\n /**\n * Translate a single metadata document (view or action) when an i18n\n * service is registered for the request's project and the requested\n * locale yields a match. Falls through unchanged for unsupported types\n * or missing translations.\n */\n private async translateMetaItem(req: any, type: string, projectId: string | undefined, item: any): Promise<any> {\n if (!item || typeof item !== 'object') return item;\n if (type !== 'view' && type !== 'action') return item;\n const i18n = await this.resolveI18nService(projectId);\n const bundle = this.buildTranslationBundle(i18n);\n if (!bundle) return item;\n const locale = this.extractLocale(req, i18n);\n if (!locale) return item;\n const { translateMetadataDocument } = await import('@objectstack/spec/system');\n return translateMetadataDocument(type, item, bundle, { locale });\n }\n\n /**\n * Translate a list of metadata documents using `translateMetaItem`.\n */\n private async translateMetaItems(req: any, type: string, projectId: string | undefined, items: any): Promise<any> {\n if (!Array.isArray(items)) return items;\n if (type !== 'view' && type !== 'action') return items;\n const i18n = await this.resolveI18nService(projectId);\n const bundle = this.buildTranslationBundle(i18n);\n if (!bundle) return items;\n const locale = this.extractLocale(req, i18n);\n if (!locale) return items;\n const { translateMetadataDocument } = await import('@objectstack/spec/system');\n return items.map((item) => translateMetadataDocument(type, item, bundle, { locale }));\n }\n\n /**\n * Pull the request hostname (without port) from a Node-style `req` or\n * a Fetch-style request wrapper. Returns undefined when no Host header\n * is available.\n */\n private extractHostname(req: any): string | undefined {\n const headers = req?.headers;\n let host: string | undefined;\n if (headers) {\n if (typeof headers.get === 'function') {\n host = headers.get('host') ?? undefined;\n } else {\n host = headers.host ?? headers.Host;\n }\n }\n if (!host && typeof req?.hostname === 'string') host = req.hostname;\n if (!host && typeof req?.url === 'string') {\n // Fetch-style requests expose the hostname via `req.url` even\n // when the (forbidden) `Host` header has been stripped by the\n // runtime. This branch keeps hostname-routing working when\n // tests build a `Request` object through `app.fetch(...)`.\n try {\n host = new (globalThis as any).URL(req.url).host;\n } catch { /* ignore */ }\n }\n if (!host) return undefined;\n return String(host).split(':')[0].toLowerCase();\n }\n\n /**\n * Pull the `X-Project-Id` header from a Node- or Fetch-style request.\n * Header names are case-insensitive; we probe both casings to cover\n * adapters that don't normalize headers (e.g. raw Node http).\n */\n private extractProjectIdHeader(req: any): string | undefined {\n const headers = req?.headers;\n if (!headers) return undefined;\n let val: unknown;\n if (typeof headers.get === 'function') {\n val = headers.get('x-project-id') ?? headers.get('X-Project-Id');\n } else {\n val = headers['x-project-id'] ?? headers['X-Project-Id'];\n }\n if (Array.isArray(val)) val = val[0];\n if (typeof val !== 'string') return undefined;\n const trimmed = val.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n \n /**\n * Normalize configuration with defaults\n */\n private normalizeConfig(config: RestServerConfig): NormalizedRestServerConfig {\n const api = (config.api ?? {}) as Partial<RestApiConfig>;\n const crud = (config.crud ?? {}) as Partial<CrudEndpointsConfig>;\n const metadata = (config.metadata ?? {}) as Partial<MetadataEndpointsConfig>;\n const batch = (config.batch ?? {}) as Partial<BatchEndpointsConfig>;\n const routes = (config.routes ?? {}) as Partial<RouteGenerationConfig>;\n \n return {\n api: {\n version: api.version ?? 'v1',\n basePath: api.basePath ?? '/api',\n apiPath: api.apiPath,\n enableCrud: api.enableCrud ?? true,\n enableMetadata: api.enableMetadata ?? true,\n enableUi: api.enableUi ?? true,\n enableBatch: api.enableBatch ?? true,\n enableDiscovery: api.enableDiscovery ?? true,\n enableProjectScoping: api.enableProjectScoping ?? false,\n projectResolution: api.projectResolution ?? 'auto',\n documentation: api.documentation,\n responseFormat: api.responseFormat,\n },\n crud: {\n operations: crud.operations ?? {\n create: true,\n read: true,\n update: true,\n delete: true,\n list: true,\n },\n patterns: crud.patterns,\n dataPrefix: crud.dataPrefix ?? '/data',\n objectParamStyle: crud.objectParamStyle ?? 'path',\n },\n metadata: {\n prefix: metadata.prefix ?? '/meta',\n enableCache: metadata.enableCache ?? true,\n cacheTtl: metadata.cacheTtl ?? 3600,\n endpoints: metadata.endpoints ?? {\n types: true,\n items: true,\n item: true,\n schema: true,\n },\n },\n batch: {\n maxBatchSize: batch.maxBatchSize ?? 200,\n enableBatchEndpoint: batch.enableBatchEndpoint ?? true,\n operations: batch.operations ?? {\n createMany: true,\n updateMany: true,\n deleteMany: true,\n upsertMany: true,\n },\n defaultAtomic: batch.defaultAtomic ?? true,\n },\n routes: {\n includeObjects: routes.includeObjects,\n excludeObjects: routes.excludeObjects,\n nameTransform: routes.nameTransform ?? 'none',\n overrides: routes.overrides,\n },\n };\n }\n \n /**\n * Get the full API base path\n */\n private getApiBasePath(): string {\n const { api } = this.config;\n return api.apiPath ?? `${api.basePath}/${api.version}`;\n }\n\n /**\n * Get the project-scoped base path for a given unscoped base.\n * Example: `/api/v1` → `/api/v1/projects/:projectId`.\n */\n private getScopedBasePath(basePath: string): string {\n return `${basePath}/projects/:projectId`;\n }\n\n /**\n * Register all REST API routes\n *\n * When `enableProjectScoping` is true, routes are registered under\n * `/api/v1/projects/:projectId/...`. The `projectResolution` strategy\n * controls whether unscoped legacy routes remain available:\n * - `required` → only scoped routes registered.\n * - `optional` / `auto` → both scoped and unscoped routes registered.\n */\n registerRoutes(): void {\n const basePath = this.getApiBasePath();\n const { enableProjectScoping, projectResolution } = this.config.api;\n\n const registerForBase = (bp: string) => {\n if (this.config.api.enableDiscovery) {\n this.registerDiscoveryEndpoints(bp);\n }\n if (this.config.api.enableMetadata) {\n this.registerMetadataEndpoints(bp);\n }\n if (this.config.api.enableUi) {\n this.registerUiEndpoints(bp);\n }\n if (this.config.api.enableCrud) {\n this.registerCrudEndpoints(bp);\n }\n if (this.config.api.enableBatch) {\n this.registerBatchEndpoints(bp);\n }\n };\n\n if (enableProjectScoping) {\n const scopedBase = this.getScopedBasePath(basePath);\n if (projectResolution === 'required') {\n // Strict: only scoped routes\n registerForBase(scopedBase);\n } else {\n // 'optional' | 'auto' — keep both so legacy callers keep working\n registerForBase(basePath);\n registerForBase(scopedBase);\n }\n } else {\n registerForBase(basePath);\n }\n }\n \n /**\n * Register discovery endpoints\n */\n private registerDiscoveryEndpoints(basePath: string): void {\n const isScoped = basePath.includes('/projects/:projectId');\n const discoveryHandler = async (req: any, res: any) => {\n try {\n const discovery = await this.protocol.getDiscovery();\n\n // Override discovery information with actual server configuration\n discovery.version = this.config.api.version;\n\n // Substitute the resolved projectId into the advertised routes so\n // clients can consume them verbatim (e.g. /api/v1/projects/abc/data).\n const realBase = isScoped\n ? basePath.replace(':projectId', req.params?.projectId ?? ':projectId')\n : basePath;\n\n if (discovery.routes) {\n // Ensure routes match the actual mounted paths\n if (this.config.api.enableCrud) {\n discovery.routes.data = `${realBase}${this.config.crud.dataPrefix}`;\n }\n\n if (this.config.api.enableMetadata) {\n discovery.routes.metadata = `${realBase}${this.config.metadata.prefix}`;\n }\n\n if (this.config.api.enableUi) {\n discovery.routes.ui = `${realBase}/ui`;\n }\n\n // Align auth route with the versioned base path if present.\n // Auth is a control-plane concern, so use the unscoped base.\n if (discovery.routes.auth) {\n const unscopedBase = isScoped\n ? basePath.replace(/\\/projects\\/:projectId$/, '')\n : basePath;\n discovery.routes.auth = `${unscopedBase}/auth`;\n }\n }\n\n // Attach scoping metadata so clients can detect dual-mode routing.\n (discovery as any).scoping = {\n enabled: this.config.api.enableProjectScoping,\n resolution: this.config.api.projectResolution,\n scoped: isScoped,\n projectId: isScoped ? req.params?.projectId : undefined,\n };\n\n res.json(discovery);\n } catch (error: any) {\n logError(\"[REST] Unhandled error:\", error);\n res.status(500).json({ error: error.message });\n }\n };\n\n // Register at basePath (e.g. /api/v1)\n this.routeManager.register({\n method: 'GET',\n path: basePath,\n handler: discoveryHandler,\n metadata: {\n summary: 'Get API discovery information',\n tags: ['discovery'],\n },\n });\n\n // Register at basePath/discovery (e.g. /api/v1/discovery)\n this.routeManager.register({\n method: 'GET',\n path: `${basePath}/discovery`,\n handler: discoveryHandler,\n metadata: {\n summary: 'Get API discovery information',\n tags: ['discovery'],\n },\n });\n }\n \n /**\n * Register metadata endpoints\n */\n private registerMetadataEndpoints(basePath: string): void {\n const { metadata } = this.config;\n const metaPath = `${basePath}${metadata.prefix}`;\n const isScoped = basePath.includes('/projects/:projectId');\n\n // GET /meta - List all metadata types\n if (metadata.endpoints.types !== false) {\n this.routeManager.register({\n method: 'GET',\n path: metaPath,\n handler: async (req: any, res: any) => {\n try {\n const projectId = isScoped ? req.params?.projectId : undefined;\n const p = await this.resolveProtocol(projectId, req);\n const types = await p.getMetaTypes();\n res.json(types);\n } catch (error: any) {\n logError(\"[REST] Unhandled error:\", error);\n res.status(500).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'List all metadata types',\n tags: ['metadata'],\n },\n });\n }\n\n // GET /meta/:type - List items of a type\n if (metadata.endpoints.items !== false) {\n this.routeManager.register({\n method: 'GET',\n path: `${metaPath}/:type`,\n handler: async (req: any, res: any) => {\n try {\n const packageId = req.query?.package || undefined;\n const projectId = isScoped ? req.params?.projectId : undefined;\n const p = await this.resolveProtocol(projectId, req);\n const items = await p.getMetaItems({\n type: req.params.type,\n packageId,\n ...(projectId ? { projectId } : {}),\n } as any);\n const translated = await this.translateMetaItems(req, req.params.type, projectId, items);\n res.header('Vary', 'Accept-Language');\n res.json(translated);\n } catch (error: any) {\n logError(\"[REST] Unhandled error:\", error);\n res.status(404).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'List metadata items of a type',\n tags: ['metadata'],\n },\n });\n }\n\n // GET /meta/:type/:name - Get specific item\n if (metadata.endpoints.item !== false) {\n this.routeManager.register({\n method: 'GET',\n path: `${metaPath}/:type/:name`,\n handler: async (req: any, res: any) => {\n try {\n const projectId = isScoped ? req.params?.projectId : undefined;\n const p = await this.resolveProtocol(projectId, req);\n // Check if cached version is available\n if (metadata.enableCache && p.getMetaItemCached) {\n const cacheRequest = {\n ifNoneMatch: req.headers['if-none-match'] as string,\n ifModifiedSince: req.headers['if-modified-since'] as string,\n };\n\n const result = await p.getMetaItemCached({\n type: req.params.type,\n name: req.params.name,\n cacheRequest,\n ...(projectId ? { projectId } : {}),\n } as any);\n\n if (result.notModified) {\n res.status(304).send();\n return;\n }\n\n // Set cache headers\n if (result.etag) {\n const etagValue = result.etag.weak\n ? `W/\"${result.etag.value}\"`\n : `\"${result.etag.value}\"`;\n res.header('ETag', etagValue);\n }\n if (result.lastModified) {\n res.header('Last-Modified', new Date(result.lastModified).toUTCString());\n }\n if (result.cacheControl) {\n const directives = result.cacheControl.directives.join(', ');\n const maxAge = result.cacheControl.maxAge\n ? `, max-age=${result.cacheControl.maxAge}`\n : '';\n res.header('Cache-Control', directives + maxAge);\n }\n\n res.header('Vary', 'Accept-Language');\n res.json(await this.translateMetaItem(req, req.params.type, projectId, result.data));\n } else {\n // Non-cached version\n const packageId = req.query?.package || undefined;\n const item = await p.getMetaItem({\n type: req.params.type,\n name: req.params.name,\n packageId,\n } as any);\n res.header('Vary', 'Accept-Language');\n res.json(await this.translateMetaItem(req, req.params.type, projectId, item));\n }\n } catch (error: any) {\n logError(\"[REST] Unhandled error:\", error);\n res.status(404).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Get specific metadata item',\n tags: ['metadata'],\n },\n });\n }\n\n // PUT /meta/:type/:name - Save metadata item\n // We always register this route, but return 501 if protocol doesn't support it\n // This makes it discoverable even if not implemented\n this.routeManager.register({\n method: 'PUT',\n path: `${metaPath}/:type/:name`,\n handler: async (req: any, res: any) => {\n try {\n const projectId = isScoped ? req.params?.projectId : undefined;\n const p = await this.resolveProtocol(projectId, req);\n if (!p.saveMetaItem) {\n res.status(501).json({ error: 'Save operation not supported by protocol implementation' });\n return;\n }\n\n const result = await p.saveMetaItem({\n type: req.params.type,\n name: req.params.name,\n item: req.body,\n ...(projectId ? { projectId } : {}),\n } as any);\n res.json(result);\n } catch (error: any) {\n logError(\"[REST] Unhandled error:\", error);\n res.status(400).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Save specific metadata item',\n tags: ['metadata'],\n },\n });\n }\n\n /**\n * Register UI endpoints\n */\n private registerUiEndpoints(basePath: string): void {\n const uiPath = `${basePath}/ui`;\n const isScoped = basePath.includes('/projects/:projectId');\n\n // GET /ui/view/:object/:type - Resolve view for object\n this.routeManager.register({\n method: 'GET',\n path: `${uiPath}/view/:object/:type`,\n handler: async (req: any, res: any) => {\n try {\n const projectId = isScoped ? req.params?.projectId : undefined;\n const p = await this.resolveProtocol(projectId, req);\n if (p.getUiView) {\n const view = await p.getUiView({\n object: req.params.object,\n type: req.params.type as any,\n ...(projectId ? { projectId } : {}),\n } as any);\n res.json(view);\n } else {\n res.status(501).json({ error: 'UI View resolution not supported by protocol implementation' });\n }\n } catch (error: any) {\n logError(\"[REST] Unhandled error:\", error);\n res.status(404).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Resolve UI View for object',\n tags: ['ui'],\n },\n });\n }\n \n /**\n * Register CRUD endpoints for data operations\n */\n private registerCrudEndpoints(basePath: string): void {\n const { crud } = this.config;\n const dataPath = `${basePath}${crud.dataPrefix}`;\n const isScoped = basePath.includes('/projects/:projectId');\n\n const operations = crud.operations;\n\n // GET /data/:object - List/query records\n if (operations.list) {\n this.routeManager.register({\n method: 'GET',\n path: `${dataPath}/:object`,\n handler: async (req: any, res: any) => {\n try {\n const projectId = isScoped ? req.params?.projectId : undefined;\n const p = await this.resolveProtocol(projectId, req);\n const context = await this.resolveExecCtx(projectId, req);\n const result = await p.findData({\n object: req.params.object,\n query: req.query,\n ...(projectId ? { projectId } : {}),\n ...(context ? { context } : {}),\n } as any);\n res.json(result);\n } catch (error: any) {\n const mapped = mapDataError(error, req.params?.object);\n if (mapped.status === 404 || mapped.status === 503 || mapped.status === 502) {\n res.status(mapped.status).json(mapped.body);\n } else {\n logError(\"[REST] Unhandled error:\", error);\n res.status(mapped.status).json(mapped.body);\n }\n }\n },\n metadata: {\n summary: 'Query records',\n tags: ['data', 'crud'],\n },\n });\n }\n\n // GET /data/:object/:id - Get single record\n if (operations.read) {\n this.routeManager.register({\n method: 'GET',\n path: `${dataPath}/:object/:id`,\n handler: async (req: any, res: any) => {\n try {\n const projectId = isScoped ? req.params?.projectId : undefined;\n const p = await this.resolveProtocol(projectId, req);\n const { select, expand } = req.query || {};\n const context = await this.resolveExecCtx(projectId, req);\n const result = await p.getData({\n object: req.params.object,\n id: req.params.id,\n ...(select != null ? { select } : {}),\n ...(expand != null ? { expand } : {}),\n ...(projectId ? { projectId } : {}),\n ...(context ? { context } : {}),\n } as any);\n res.json(result);\n } catch (error: any) {\n const mapped = mapDataError(error, req.params?.object);\n if (!isExpectedDataStatus(mapped.status)) logError(\"[REST] Unhandled error:\", error);\n res.status(mapped.status === 400 ? 404 : mapped.status).json(mapped.body);\n }\n },\n metadata: {\n summary: 'Get record by ID',\n tags: ['data', 'crud'],\n },\n });\n }\n\n // POST /data/:object - Create record\n if (operations.create) {\n this.routeManager.register({\n method: 'POST',\n path: `${dataPath}/:object`,\n handler: async (req: any, res: any) => {\n try {\n const projectId = isScoped ? req.params?.projectId : undefined;\n const p = await this.resolveProtocol(projectId, req);\n const context = await this.resolveExecCtx(projectId, req);\n const result = await p.createData({\n object: req.params.object,\n data: req.body,\n ...(projectId ? { projectId } : {}),\n ...(context ? { context } : {}),\n } as any);\n res.status(201).json(result);\n } catch (error: any) {\n const mapped = mapDataError(error, req.params?.object);\n if (!isExpectedDataStatus(mapped.status)) logError(\"[REST] Unhandled error:\", error);\n res.status(mapped.status).json(mapped.body);\n }\n },\n metadata: {\n summary: 'Create record',\n tags: ['data', 'crud'],\n },\n });\n }\n\n // PATCH /data/:object/:id - Update record\n if (operations.update) {\n this.routeManager.register({\n method: 'PATCH',\n path: `${dataPath}/:object/:id`,\n handler: async (req: any, res: any) => {\n try {\n const projectId = isScoped ? req.params?.projectId : undefined;\n const p = await this.resolveProtocol(projectId, req);\n const context = await this.resolveExecCtx(projectId, req);\n const result = await p.updateData({\n object: req.params.object,\n id: req.params.id,\n data: req.body,\n ...(projectId ? { projectId } : {}),\n ...(context ? { context } : {}),\n } as any);\n res.json(result);\n } catch (error: any) {\n const mapped = mapDataError(error, req.params?.object);\n if (!isExpectedDataStatus(mapped.status)) logError(\"[REST] Unhandled error:\", error);\n res.status(mapped.status).json(mapped.body);\n }\n },\n metadata: {\n summary: 'Update record',\n tags: ['data', 'crud'],\n },\n });\n }\n\n // DELETE /data/:object/:id - Delete record\n if (operations.delete) {\n this.routeManager.register({\n method: 'DELETE',\n path: `${dataPath}/:object/:id`,\n handler: async (req: any, res: any) => {\n try {\n const projectId = isScoped ? req.params?.projectId : undefined;\n const p = await this.resolveProtocol(projectId, req);\n const context = await this.resolveExecCtx(projectId, req);\n const result = await p.deleteData({\n object: req.params.object,\n id: req.params.id,\n ...(projectId ? { projectId } : {}),\n ...(context ? { context } : {}),\n } as any);\n res.json(result);\n } catch (error: any) {\n const mapped = mapDataError(error, req.params?.object);\n if (!isExpectedDataStatus(mapped.status)) logError(\"[REST] Unhandled error:\", error);\n res.status(mapped.status).json(mapped.body);\n }\n },\n metadata: {\n summary: 'Delete record',\n tags: ['data', 'crud'],\n },\n });\n }\n }\n \n /**\n * Register batch operation endpoints\n */\n private registerBatchEndpoints(basePath: string): void {\n const { crud, batch } = this.config;\n const dataPath = `${basePath}${crud.dataPrefix}`;\n const isScoped = basePath.includes('/projects/:projectId');\n\n const operations = batch.operations;\n\n // POST /data/:object/batch - Generic batch endpoint\n if (batch.enableBatchEndpoint && this.protocol.batchData) {\n this.routeManager.register({\n method: 'POST',\n path: `${dataPath}/:object/batch`,\n handler: async (req: any, res: any) => {\n try {\n const projectId = isScoped ? req.params?.projectId : undefined;\n const p = await this.resolveProtocol(projectId, req);\n const result = await p.batchData!({\n object: req.params.object,\n request: req.body,\n ...(projectId ? { projectId } : {}),\n } as any);\n res.json(result);\n } catch (error: any) {\n logError(\"[REST] Unhandled error:\", error);\n res.status(400).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Batch operations',\n tags: ['data', 'batch'],\n },\n });\n }\n\n // POST /data/:object/createMany - Bulk create\n if (operations.createMany && this.protocol.createManyData) {\n this.routeManager.register({\n method: 'POST',\n path: `${dataPath}/:object/createMany`,\n handler: async (req: any, res: any) => {\n try {\n const projectId = isScoped ? req.params?.projectId : undefined;\n const p = await this.resolveProtocol(projectId, req);\n const result = await p.createManyData!({\n object: req.params.object,\n records: req.body || [],\n ...(projectId ? { projectId } : {}),\n } as any);\n res.status(201).json(result);\n } catch (error: any) {\n logError(\"[REST] Unhandled error:\", error);\n res.status(400).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Create multiple records',\n tags: ['data', 'batch'],\n },\n });\n }\n\n // POST /data/:object/updateMany - Bulk update\n if (operations.updateMany && this.protocol.updateManyData) {\n this.routeManager.register({\n method: 'POST',\n path: `${dataPath}/:object/updateMany`,\n handler: async (req: any, res: any) => {\n try {\n const projectId = isScoped ? req.params?.projectId : undefined;\n const p = await this.resolveProtocol(projectId, req);\n const result = await p.updateManyData!({\n object: req.params.object,\n ...req.body,\n ...(projectId ? { projectId } : {}),\n } as any);\n res.json(result);\n } catch (error: any) {\n logError(\"[REST] Unhandled error:\", error);\n res.status(400).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Update multiple records',\n tags: ['data', 'batch'],\n },\n });\n }\n\n // POST /data/:object/deleteMany - Bulk delete\n if (operations.deleteMany && this.protocol.deleteManyData) {\n this.routeManager.register({\n method: 'POST',\n path: `${dataPath}/:object/deleteMany`,\n handler: async (req: any, res: any) => {\n try {\n const projectId = isScoped ? req.params?.projectId : undefined;\n const p = await this.resolveProtocol(projectId, req);\n const result = await p.deleteManyData!({\n object: req.params.object,\n ...req.body,\n ...(projectId ? { projectId } : {}),\n } as any);\n res.json(result);\n } catch (error: any) {\n logError(\"[REST] Unhandled error:\", error);\n res.status(400).json({ error: error.message });\n }\n },\n metadata: {\n summary: 'Delete multiple records',\n tags: ['data', 'batch'],\n },\n });\n }\n }\n\n \n /**\n * Get the route manager\n */\n getRouteManager(): RouteManager {\n return this.routeManager;\n }\n \n /**\n * Get all registered routes\n */\n getRoutes() {\n return this.routeManager.getAll();\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { IHttpServer } from '@objectstack/core';\nimport type { PackageService } from '@objectstack/service-package';\n\n/**\n * Options for package route registration.\n */\nexport interface PackageRoutesOptions {\n /**\n * Protocol service (ObjectStackProtocol) — provides access to in-memory\n * SchemaRegistry packages loaded via defineStack()/AppPlugin at boot time.\n */\n protocol?: { getMetaItems?(req: { type: string }): Promise<{ items: any[] }> };\n}\n\n/**\n * Register package management API routes\n *\n * Provides endpoints for publishing, retrieving, and managing packages.\n * Routes:\n * - POST /api/v1/packages - Publish a package\n * - GET /api/v1/packages - List all packages (merges registry + database)\n * - GET /api/v1/packages/:id - Get a specific package\n * - DELETE /api/v1/packages/:id - Delete a package\n */\nexport function registerPackageRoutes(\n server: IHttpServer,\n packageService: PackageService,\n basePath: string = '/api/v1',\n options: PackageRoutesOptions = {},\n) {\n const packagesPath = `${basePath}/packages`;\n\n // POST /api/v1/packages - Publish a package\n server.post(packagesPath, async (req, res) => {\n try {\n const { manifest, metadata } = req.body || {};\n\n if (!manifest || !metadata) {\n res.status(400).json({ error: 'Missing required fields: manifest, metadata' });\n return;\n }\n\n if (!manifest.id || !manifest.version) {\n res.status(400).json({ error: 'Invalid manifest: id and version are required' });\n return;\n }\n\n const result = await packageService.publish({ manifest, metadata });\n\n if (result.success) {\n res.json({\n success: true,\n message: `Published ${manifest.id}@${manifest.version}`,\n package: {\n id: manifest.id,\n version: manifest.version,\n },\n });\n return;\n }\n\n res.status(400).json({ success: false, error: result.error });\n } catch (error) {\n res.status(500).json({ error: (error as Error).message });\n }\n });\n\n // GET /api/v1/packages - List all packages (merges registry + database)\n server.get(packagesPath, async (_req, res) => {\n try {\n // Merge two sources:\n // 1. Registry packages (in-memory, loaded at boot via defineStack/AppPlugin)\n // 2. Database packages (published via POST /packages)\n const packagesMap = new Map<string, any>();\n\n // Registry packages (via protocol service → SchemaRegistry)\n if (options.protocol && typeof options.protocol.getMetaItems === 'function') {\n try {\n const result = await options.protocol.getMetaItems({ type: 'package' });\n if (result?.items) {\n for (const item of result.items) {\n const id = item.manifest?.id || item.id;\n if (id) {\n packagesMap.set(id, {\n ...item,\n source: 'registry',\n });\n }\n }\n }\n } catch {\n // Protocol unavailable — continue with database only\n }\n }\n\n // Database packages (published artifacts)\n try {\n const dbPackages = await packageService.list();\n for (const pkg of dbPackages) {\n const id = pkg.manifest?.id || pkg.id;\n if (id) {\n // Database entry takes precedence (has richer metadata from publish)\n packagesMap.set(id, {\n ...packagesMap.get(id),\n ...pkg,\n source: packagesMap.has(id) ? 'both' : 'database',\n });\n }\n }\n } catch {\n // Database query failed — continue with registry-only packages\n }\n\n const packages = Array.from(packagesMap.values());\n res.json({ packages, total: packages.length });\n } catch (error) {\n res.status(500).json({ error: (error as Error).message });\n }\n });\n\n // GET /api/v1/packages/:id - Get a specific package\n server.get(`${packagesPath}/:id`, async (req, res) => {\n try {\n const packageId = req.params.id;\n const version = req.query?.version || 'latest';\n\n // Try database first (richer data from publish)\n const pkg = await packageService.get(packageId, version);\n if (pkg) {\n res.json({ package: { ...pkg, source: 'database' } });\n return;\n }\n\n // Fall back to registry (in-memory loaded packages)\n if (options.protocol && typeof options.protocol.getMetaItems === 'function') {\n try {\n const result = await options.protocol.getMetaItems({ type: 'package' });\n const match = result?.items?.find((item: any) =>\n (item.manifest?.id || item.id) === packageId\n );\n if (match) {\n res.json({ package: { ...match, source: 'registry' } });\n return;\n }\n } catch {\n // Protocol unavailable\n }\n }\n\n res.status(404).json({ error: 'Package not found' });\n } catch (error) {\n res.status(500).json({ error: (error as Error).message });\n }\n });\n\n // DELETE /api/v1/packages/:id - Delete a package\n server.delete(`${packagesPath}/:id`, async (req, res) => {\n try {\n const packageId = req.params.id;\n const version = req.query?.version;\n\n const result = await packageService.delete(packageId, version);\n\n if (result.success) {\n res.json({\n success: true,\n message: `Deleted ${packageId}${version ? `@${version}` : ''}`,\n });\n return;\n }\n\n res.status(400).json({ success: false });\n } catch (error) {\n res.status(500).json({ error: (error as Error).message });\n }\n });\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Plugin, PluginContext, IHttpServer } from '@objectstack/core';\nimport { RestServer, RestKernelManager } from './rest-server.js';\nimport { ObjectStackProtocol, RestServerConfig } from '@objectstack/spec/api';\nimport { registerPackageRoutes } from './package-routes.js';\nimport type { PackageService } from '@objectstack/service-package';\n\nexport interface RestApiPluginConfig {\n serverServiceName?: string;\n protocolServiceName?: string;\n /**\n * Optional override for the kernel-manager service name. When the service\n * is registered (by @objectstack/runtime's MultiProjectPlugin), scoped\n * routes resolve per-project protocols at request time.\n */\n kernelManagerServiceName?: string;\n api?: RestServerConfig;\n}\n\n/**\n * REST API Plugin\n * \n * Responsibilities:\n * 1. Consumes 'http.server' (or configured service)\n * 2. Consumes 'protocol' (ObjectStackProtocol)\n * 3. Instantiates RestServer to auto-generate routes\n */\nexport function createRestApiPlugin(config: RestApiPluginConfig = {}): Plugin {\n return {\n name: 'com.objectstack.rest.api',\n version: '1.0.0',\n \n init: async (_ctx: PluginContext) => {\n // No service registration, this is a consumer plugin\n },\n \n start: async (ctx: PluginContext) => {\n const serverService = config.serverServiceName || 'http.server';\n const protocolService = config.protocolServiceName || 'protocol';\n \n let server: IHttpServer | undefined;\n let protocol: ObjectStackProtocol | undefined;\n\n try {\n server = ctx.getService<IHttpServer>(serverService);\n } catch (e) {\n // Ignore missing service\n }\n\n try {\n protocol = ctx.getService<ObjectStackProtocol>(protocolService);\n } catch (e) {\n // Ignore missing service\n }\n\n // Optional — only present when MultiProjectPlugin is mounted. When\n // available, RestServer will resolve a per-project protocol at\n // request time for scoped (`/projects/:projectId/...`) routes.\n let kernelManager: RestKernelManager | undefined;\n const kernelManagerService = config.kernelManagerServiceName || 'kernel-manager';\n try {\n kernelManager = ctx.getService<RestKernelManager>(kernelManagerService);\n } catch (e) {\n // Single-kernel deployment — fall back to the control protocol\n }\n\n // Optional — only present in runtime mode. When available,\n // RestServer will resolve hostname → projectId on unscoped\n // routes so a remote runtime node can dispatch every request\n // to the matching per-project kernel without requiring callers\n // to know the projectId.\n let envRegistry: any;\n try {\n envRegistry = ctx.getService<any>('env-registry');\n } catch (e) {\n // Not running in runtime/multi-project mode — fine.\n }\n\n // Optional default-project provider — registered by\n // `createSingleProjectPlugin` in single-project local mode.\n // Lets RestServer route bare `/api/v1/data/...` URLs into the\n // lone project's kernel.\n const defaultProjectIdProvider = (): string | undefined => {\n try {\n const dp: any = ctx.getService('default-project');\n return dp?.projectId;\n } catch { return undefined; }\n };\n\n // Auth service resolver — used by RestServer.resolveExecCtx in\n // single-kernel deployments where there is no kernelManager.\n // Multi-kernel paths look up auth via kernelManager.getOrCreate,\n // so this provider is the single-kernel fallback.\n const authServiceProvider = async (_projectId?: string): Promise<any | undefined> => {\n try {\n return ctx.getService<any>('auth');\n } catch { return undefined; }\n };\n\n // ObjectQL resolver — single-kernel fallback so resolveExecCtx\n // can run sys_member / sys_user_permission_set lookups when\n // there is no kernelManager wired (e.g. `pnpm dev:crm`).\n const objectQLProvider = async (_projectId?: string): Promise<any | undefined> => {\n try {\n return ctx.getService<any>('objectql');\n } catch { return undefined; }\n };\n\n if (!server) {\n ctx.logger.warn(`RestApiPlugin: HTTP Server service '${serverService}' not found. REST routes skipped.`);\n return;\n }\n \n if (!protocol) {\n ctx.logger.warn(`RestApiPlugin: Protocol service '${protocolService}' not found. REST routes skipped.`);\n return;\n }\n \n ctx.logger.info('Hydrating REST API from Protocol...');\n \n try {\n const restServer = new RestServer(server, protocol, config.api as any, kernelManager, envRegistry, defaultProjectIdProvider, authServiceProvider, objectQLProvider);\n restServer.registerRoutes();\n\n ctx.logger.info('REST API successfully registered');\n } catch (err: any) {\n ctx.logger.error('Failed to register REST API routes', { error: err.message } as any);\n throw err;\n }\n\n // Register package management routes if service is available\n try {\n const packageService = ctx.getService<PackageService>('package');\n if (packageService) {\n const basePath = config.api?.api?.basePath || '/api';\n const version = config.api?.api?.version || 'v1';\n const versionedBase = `${basePath}/${version}`;\n const enableProjectScoping = config.api?.api?.enableProjectScoping ?? false;\n const projectResolution = config.api?.api?.projectResolution ?? 'auto';\n\n if (enableProjectScoping && projectResolution === 'required') {\n // Only register the scoped variant\n registerPackageRoutes(server, packageService, `${versionedBase}/projects/:projectId`, {\n protocol,\n });\n } else {\n registerPackageRoutes(server, packageService, versionedBase, { protocol });\n if (enableProjectScoping) {\n registerPackageRoutes(server, packageService, `${versionedBase}/projects/:projectId`, {\n protocol,\n });\n }\n }\n ctx.logger.info('Package management routes registered');\n }\n } catch (e) {\n // Package service not available, skip\n ctx.logger.debug('Package service not available, package routes skipped');\n }\n }\n };\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { IHttpServer, RouteHandler, Plugin } from '@objectstack/core';
2
- import { Shared, System } from '@objectstack/spec';
2
+ import * as System from '@objectstack/spec/system';
3
+ import * as Shared from '@objectstack/spec/shared';
3
4
  import { z } from 'zod';
4
5
  import { ObjectStackProtocol, RestServerConfig } from '@objectstack/spec/api';
5
6
 
@@ -154,6 +155,17 @@ declare class RouteGroupBuilder {
154
155
  private resolvePath;
155
156
  }
156
157
 
158
+ /**
159
+ * Structural subset of `KernelManager` that RestServer needs in order to
160
+ * resolve a per-project protocol at request time. Typed locally to avoid
161
+ * an @objectstack/runtime → @objectstack/rest → @objectstack/runtime
162
+ * package cycle.
163
+ */
164
+ interface RestKernelManager {
165
+ getOrCreate(projectId: string): Promise<{
166
+ getServiceAsync<T = unknown>(name: string): Promise<T>;
167
+ }>;
168
+ }
157
169
  /**
158
170
  * RestServer
159
171
  *
@@ -181,11 +193,113 @@ declare class RouteGroupBuilder {
181
193
  *
182
194
  * restServer.registerRoutes();
183
195
  */
196
+ /**
197
+ * Minimal env registry shape consumed by the REST server for hostname →
198
+ * projectId resolution and `X-Project-Id` header validation on unscoped
199
+ * routes. Mirrors the surface of `EnvironmentDriverRegistry` defined in
200
+ * `@objectstack/service-cloud`.
201
+ */
202
+ interface RestEnvRegistry {
203
+ resolveByHostname(hostname: string): Promise<{
204
+ projectId: string;
205
+ } | null | undefined>;
206
+ /**
207
+ * Look up a project by id. Returns a truthy value (typically an
208
+ * `IDataDriver`) when the project exists and is bound, `null` when
209
+ * unknown. The REST server only uses the truthiness; it does not
210
+ * touch the driver itself (the actual driver is loaded later via
211
+ * `KernelManager.getOrCreate(projectId)`).
212
+ */
213
+ resolveById?(projectId: string): Promise<unknown | null>;
214
+ }
184
215
  declare class RestServer {
185
216
  private protocol;
186
217
  private config;
187
218
  private routeManager;
188
- constructor(server: IHttpServer, protocol: ObjectStackProtocol, config?: RestServerConfig);
219
+ private kernelManager?;
220
+ private envRegistry?;
221
+ private defaultProjectIdProvider?;
222
+ private authServiceProvider?;
223
+ private objectQLProvider?;
224
+ constructor(server: IHttpServer, protocol: ObjectStackProtocol, config?: RestServerConfig, kernelManager?: RestKernelManager, envRegistry?: RestEnvRegistry, defaultProjectIdProvider?: () => string | undefined, authServiceProvider?: (projectId?: string) => Promise<any | undefined>, objectQLProvider?: (projectId?: string) => Promise<any | undefined>);
225
+ /**
226
+ * Resolve the protocol for a given request. When `projectId` is present
227
+ * and a KernelManager is wired, fetch the per-project kernel's
228
+ * `protocol` service so metadata / data / UI reads hit the project's
229
+ * own registry and datastore.
230
+ *
231
+ * When `projectId` is absent on an unscoped route and an `envRegistry`
232
+ * is wired (runtime mode), the resolution chain is:
233
+ * 1. Hostname → projectId (`envRegistry.resolveByHostname`)
234
+ * 2. `X-Project-Id` header → projectId (`envRegistry.resolveById`)
235
+ * 3. Default-project fallback (`defaultProjectIdProvider`, set by
236
+ * `createSingleProjectPlugin`)
237
+ * 4. Control-plane protocol captured at boot.
238
+ *
239
+ * Special case: `projectId === 'platform'` is a reserved virtual id used
240
+ * by Studio to address the control plane through the regular project
241
+ * URL shape (`/projects/platform/...`). It is NOT a row in the projects
242
+ * table, so we must never call `KernelManager.getOrCreate('platform')`.
243
+ * Instead, return the control-plane protocol directly. This lets Studio
244
+ * (and any other client) speak a single, uniform URL family without
245
+ * duplicating route logic for the platform surface.
246
+ */
247
+ private resolveProtocol;
248
+ /**
249
+ * Resolve the i18n service for the request's project (or control plane
250
+ * when no project id is in scope). Returns `undefined` when no service is
251
+ * registered, so callers can short-circuit and skip translation rather
252
+ * than failing.
253
+ *
254
+ * Mirrors `resolveProtocol`'s lookup chain: explicit `projectId` from the
255
+ * route → kernel-managed `i18n` service. Control-plane / unscoped
256
+ * requests intentionally return `undefined` because the platform kernel
257
+ * does not own per-app translation bundles.
258
+ */
259
+ private resolveI18nService;
260
+ /**
261
+ * Resolve the request's execution context (RBAC/RLS/FLS) by looking up
262
+ * the better-auth session via the project's `auth` service. Returns
263
+ * `undefined` for anonymous requests so callers can pass `context` as-is
264
+ * to the protocol layer (the SecurityPlugin treats undefined as anon).
265
+ */
266
+ private resolveExecCtx;
267
+ /**
268
+ * Build a `TranslationBundle` (`Record<locale, TranslationData>`) from an
269
+ * `II18nService` instance. Returns `undefined` when no locales are
270
+ * registered so callers can avoid translation work.
271
+ */
272
+ private buildTranslationBundle;
273
+ /**
274
+ * Parse the highest-priority locale from an `Accept-Language` header.
275
+ * Falls back to a `?locale=` query parameter, then to the i18n service's
276
+ * default locale. Returns `undefined` when no preference is expressed
277
+ * (callers will then return untranslated metadata).
278
+ */
279
+ private extractLocale;
280
+ /**
281
+ * Translate a single metadata document (view or action) when an i18n
282
+ * service is registered for the request's project and the requested
283
+ * locale yields a match. Falls through unchanged for unsupported types
284
+ * or missing translations.
285
+ */
286
+ private translateMetaItem;
287
+ /**
288
+ * Translate a list of metadata documents using `translateMetaItem`.
289
+ */
290
+ private translateMetaItems;
291
+ /**
292
+ * Pull the request hostname (without port) from a Node-style `req` or
293
+ * a Fetch-style request wrapper. Returns undefined when no Host header
294
+ * is available.
295
+ */
296
+ private extractHostname;
297
+ /**
298
+ * Pull the `X-Project-Id` header from a Node- or Fetch-style request.
299
+ * Header names are case-insensitive; we probe both casings to cover
300
+ * adapters that don't normalize headers (e.g. raw Node http).
301
+ */
302
+ private extractProjectIdHeader;
189
303
  /**
190
304
  * Normalize configuration with defaults
191
305
  */
@@ -194,8 +308,19 @@ declare class RestServer {
194
308
  * Get the full API base path
195
309
  */
196
310
  private getApiBasePath;
311
+ /**
312
+ * Get the project-scoped base path for a given unscoped base.
313
+ * Example: `/api/v1` → `/api/v1/projects/:projectId`.
314
+ */
315
+ private getScopedBasePath;
197
316
  /**
198
317
  * Register all REST API routes
318
+ *
319
+ * When `enableProjectScoping` is true, routes are registered under
320
+ * `/api/v1/projects/:projectId/...`. The `projectResolution` strategy
321
+ * controls whether unscoped legacy routes remain available:
322
+ * - `required` → only scoped routes registered.
323
+ * - `optional` / `auto` → both scoped and unscoped routes registered.
199
324
  */
200
325
  registerRoutes(): void;
201
326
  /**
@@ -231,6 +356,12 @@ declare class RestServer {
231
356
  interface RestApiPluginConfig {
232
357
  serverServiceName?: string;
233
358
  protocolServiceName?: string;
359
+ /**
360
+ * Optional override for the kernel-manager service name. When the service
361
+ * is registered (by @objectstack/runtime's MultiProjectPlugin), scoped
362
+ * routes resolve per-project protocols at request time.
363
+ */
364
+ kernelManagerServiceName?: string;
234
365
  api?: RestServerConfig;
235
366
  }
236
367
  /**